Self-hosting

Backups & upgrades

All of your business data lives in Postgres - clients, projects, time, invoices, expenses, logos and receipts included. That means backups and upgrades are ordinary Postgres and Docker operations, with nothing Clerq-specific to learn.

The examples assume the bundled docker-compose.yml, whose database service is named postgres and whose default role and database are both clerq. Adjust the names if you overrode POSTGRES_USER or POSTGRES_DB.

Back up

Dump the database on a schedule. A nightly cron is plenty for most setups:

docker compose exec -T postgres \
  pg_dump -U clerq clerq | gzip > clerq-$(date +%F).sql.gz

Store the dumps off the host - object storage or another machine - so a single disk failure cannot take both the instance and its backups. Test a restore into a throwaway database now and then; an untested backup is a guess.

Restore

Restore a dump into a running, empty database:

gunzip -c clerq-2026-06-14.sql.gz | \
  docker compose exec -T postgres psql -U clerq clerq

To restore onto a fresh instance, bring the stack up once so the database and schema exist, then load your dump over it.

Upgrade

Clerq is run from source, so an upgrade is a git pull and a rebuild:

# 1. take a fresh backup first (see above)
# 2. get the new code
git pull

# 3. rebuild and restart
docker compose up -d --build

On startup the one-shot migrate service runs any new migrations before the app container starts, so the schema is always brought up to date for you. Migrations are forward-only.

Always take a fresh backup immediately before upgrading a production instance, and read the release notes for any manual steps.

Health checks

The app exposes a public health endpoint over tRPC that returns JSON without authentication - useful for uptime monitors and container health checks:

curl http://localhost:3000/api/trpc/health.ping
# {"result":{"data":{"json":{"ok":true,"time":"..."}}}}

A plain TCP or HTTP check against the app port works too. The database has its own pg_isready health check built into the compose file.

Troubleshooting

The app container exits or restarts immediately. Almost always a missing or wrong DATABASE_URL, or the database not being ready. Check the logs:

docker compose logs app
docker compose logs migrate
docker compose logs postgres

"DATABASE_URL is not set." The app could not find a connection string. Under compose this is built from the POSTGRES_* variables - make sure your .env is next to the compose file and the values are set.

"BETTER_AUTH_SECRET is not set; cannot sign invoice PDF links." Set a BETTER_AUTH_SECRET. See Environment variables.

A low-entropy secret warning in the logs. Your BETTER_AUTH_SECRET is too short or too predictable. Generate a real one with openssl rand -base64 32 and restart. Note that changing it signs everyone out.

Sign-in redirects fail or loop after going public. BETTER_AUTH_URL does not match the URL users actually reach the instance on. Set it to the exact external origin, including https://, and restart.

Migrations did not apply. Re-run the one-shot step explicitly:

docker compose run --rm migrate