Self-Hosting
Run Tindra on your own infrastructure. One binary, one Postgres database.
Requirements
- Docker with the Docker Compose plugin
- 512 MB RAM minimum, 1 GB recommended
Install script
The fastest way to get up and running. Run this on the server you want to host Tindra on:
bash -c "$(curl -sSL https://install.tindra.sh)"
The script asks a few questions, then writes a docker-compose.yml, pulls the images, and optionally starts the stack:
Tindra self-hosted install
→ Checking prerequisites
✓ docker found
✓ docker compose found
→ Install directory
Path [/opt/tindra]:
→ Public URL
URL (e.g. https://tindra.example.com): https://tindra.yourcompany.com
→ Host port
Port [8080]:
→ First account
Email: you@yourcompany.com
Name: Your Name
Password: (hidden)
Confirm: (hidden)
→ Generating database password
✓ 48-character random password generated
→ Writing docker-compose.yml
✓ Written to /opt/tindra/docker-compose.yml
→ Pulling images
→ Starting
→ Creating your account
✓ Account created: you@yourcompany.com
Tindra is running.
Open https://tindra.yourcompany.com and log in.
The database password is auto-generated and written into docker-compose.yml. There is no sign-up page. The installer creates your admin account directly via the CLI.
Reverse proxy: The installer binds Tindra to port 8080 by default. It does not configure a reverse proxy or TLS for you. See Reverse proxy below.
Manual install
If you prefer to set things up yourself, create a directory for the stack and a .env file beside docker-compose.yml:
POSTGRES_PASSWORD=replace-with-a-long-random-hex-password
PUBLIC_URL=https://tindra.example.com
PORT=8080
COOKIE_SECURE=true
For a new database, generate a URL-safe password with openssl rand -hex 24 and replace the placeholder. Keep this file private. If you are adopting this example for an existing database, use its current password: changing POSTGRES_PASSWORD does not change credentials in an initialized Postgres volume. Passwords containing URL-reserved characters need percent-encoding in DATABASE_URL; a hex password avoids that extra step.
The example binds the application to host loopback for a reverse proxy on the same host. For a proxy on another host or a container network, adapt the bind address/network and configure trusted proxies as described below.
# docker-compose.yml
services:
postgres:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_DB: tindra
POSTGRES_USER: tindra
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD to a nonempty database password}"
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tindra -d tindra"]
interval: 5s
timeout: 5s
retries: 5
tindra:
image: ghcr.io/blendbyte/tindra:latest
restart: unless-stopped
stop_grace_period: 75s
ports:
- "127.0.0.1:${PORT:-8080}:8080"
environment:
DATABASE_URL: "postgres://tindra:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@postgres:5432/tindra?sslmode=disable"
PUBLIC_URL: "${PUBLIC_URL:?Set PUBLIC_URL}"
BIND_ADDR: ":8080"
DATA_DIR: /data
COOKIE_SECURE: "${COOKIE_SECURE:-false}"
LOG_FORMAT: json
TRUSTED_PROXIES: "${TRUSTED_PROXIES:-}"
UPTIME_ALLOW_PRIVATE_IPS: "${UPTIME_ALLOW_PRIVATE_IPS:-false}"
STATS_API_KEY: "${STATS_API_KEY:-}"
volumes:
- tindra_data:/data
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
tindra_data:
docker compose up -d
Migrations run automatically on startup. Inspect docker compose logs tindra and check http://127.0.0.1:8080/healthz from the host (adjust the port if needed) before creating your first account. Replace the example password with one of at least 12 characters:
docker compose exec tindra /tindra users create \
--email you@example.com \
--name "Your Name" \
--password "replace-with-your-admin-password"
There is no sign-up page. users create creates an administrator with all management permissions. Bootstrap this account before configuring SSO, then arrange verified-email or explicit provider linking as described in Authentication.
Environment variables
Use Configuration for the complete server reference, including email, OAuth/SSO, independent retention limits, buffering, and metrics authentication. Add optional runtime variables to the service's environment block, then recreate the container with docker compose up -d.
The .env file beside Compose supplies interpolation values; variables are not automatically passed into the container unless the service references them. Production Postgres is reachable on the Compose network and does not publish a host port.
Persistent storage
Keep both named volumes. Postgres stores telemetry and account/configuration data; /data stores source maps and attachments. The image initializes /data for the application user, UID/GID 65532:65532, and checks writability at startup.
Existing volumes and host bind mounts retain their existing ownership. If startup reports that DATA_DIR cannot be written, correct access for UID/GID 65532 on that mount and restart. Changing the image alone does not repair a previously created volume. See Backup for preserving both stores.
Graceful shutdown
Keep stop_grace_period: 75s so Tindra can finish HTTP shutdown and drain its bounded ingestion queues. These queues are in memory; a forced stop or crash can lose accepted data that has not reached Postgres. Queue retries do not replace persistent storage or backups.
Reverse proxy
Run Tindra behind nginx or Caddy for TLS termination, and set COOKIE_SECURE=true for an HTTPS public URL. For client IP detection and rate limiting, pass X-Forwarded-For and set TRUSTED_PROXIES to the actual proxy addresses Tindra sees.
A proxy on the host may appear to a bridged container as the Docker network gateway rather than 127.0.0.1. Determine that address for your deployment and put it in the Compose .env, for example TRUSTED_PROXIES=172.18.0.1 only if that is your actual trusted peer. For a directly run binary with a local proxy, the peer may be 127.0.0.1 or ::1. Do not copy a broad private-network range just to make headers work.
Tindra trusts forwarded headers only from a configured proxy and walks the chain right to left to the first untrusted hop. Leave TRUSTED_PROXIES unset when Tindra is exposed directly.
If you prefer a Unix socket over a TCP port, set BIND_ADDR=unix:/run/tindra/tindra.sock and point your proxy at the socket path instead.
Caddy
your-hostname.example.com {
reverse_proxy localhost:8080
}
Caddy handles TLS automatically.
nginx
server {
listen 443 ssl;
server_name your-hostname.example.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Updating
docker compose pull tindra
docker compose up -d tindra
Migrations run on startup, and downtime depends on the database size and migrations in the target version. Back up first, allow graceful shutdown, and check startup logs and /healthz before returning traffic. See Upgrades for the authentication, storage, and networking changes to check on existing installations.
Local development
Development uses a separate loopback-bound Postgres service and a writable ./data directory. Follow the local development instructions in the repository README and contributor guide for the supported Go/Bun versions, frontend build, make db, make run, and CLI account creation. Keep the development database credentials and Compose file separate from production.
Next steps
- Configuration: full environment variable reference
- Authentication: set up OAuth and disable password login
- Backup: back up your Postgres data
Monitor ingestion health
Use Ingestion Monitoring for authenticated Prometheus scrapes, queue and write metrics, and a recovery checklist when accepted data is not appearing. Pending in-memory data is not part of a database backup.