Self-hosting

Going to production

The defaults are tuned for trying Clerq on localhost. Before anyone else can reach your instance, there are a few things to get right: real secrets, HTTPS, and the correct public URL. This page walks through all of it.

1. Set real secrets

Never expose an instance with the default password and dev secret. In your .env next to the compose file:

POSTGRES_PASSWORD=a-strong-random-password
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
BETTER_AUTH_URL=https://clerq.example.com
  • POSTGRES_PASSWORD - the database password. The database port is bound to 127.0.0.1 in the compose file, so it is not directly reachable from the network, but a strong password is still the right baseline.
  • BETTER_AUTH_SECRET - signs sessions and invoice-PDF links. Keep it secret and stable; rotating it logs everyone out.
  • BETTER_AUTH_URL - the exact external origin users will type, including https://. Auth callbacks and redirects depend on this being correct.

2. Put it behind a reverse proxy with HTTPS

The app speaks plain HTTP on port 3000. In production you terminate TLS at a reverse proxy in front of it (Caddy, nginx, Traefik, or a cloud load balancer) and proxy to the app.

The simplest option is Caddy, which obtains and renews certificates automatically:

# Caddyfile
clerq.example.com {
    reverse_proxy 127.0.0.1:3000
}

An equivalent nginx server block:

server {
    listen 443 ssl;
    server_name clerq.example.com;

    # ssl_certificate / ssl_certificate_key managed by certbot or similar

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Whichever proxy you use, make sure:

  • it forwards the original Host header,
  • it sets X-Forwarded-Proto: https, and
  • BETTER_AUTH_URL matches the public https:// address exactly.

If you change APP_PORT in your .env, point the proxy at that host port instead of 3000.

3. Optional: enable Google sign-in

Email and password sign-in always works, so this step is optional. To add a "Continue with Google" button:

  1. In the Google Cloud console, create an OAuth 2.0 Client ID (type: Web application).
  2. Add the authorized redirect URI: https://clerq.example.com/api/auth/callback/google (use your real BETTER_AUTH_URL).
  3. Put the credentials in your .env:
    GOOGLE_CLIENT_ID=your-client-id
    GOOGLE_CLIENT_SECRET=your-client-secret
    
  4. Restart: docker compose up -d.

The button appears automatically once both values are present, and disappears if you remove them.

4. Think about open sign-ups

Clerq has no built-in switch to disable registration today. Any visitor who can reach the sign-in page can create an account (they will not see your business data - everything is scoped per business and new accounts start empty), but on a public instance you may still not want open registration.

Options:

  • Keep the instance on a private network or VPN.
  • Put an authentication layer (your proxy's basic auth, an identity-aware proxy, or an allowlist) in front of it.
  • Run it public and accept that anyone can register their own empty business.

5. Back it up

Set up the nightly dump and off-host storage described in Backups & upgrades before you start entering real data, not after.

Pre-flight checklist

  • POSTGRES_PASSWORD changed from the default
  • BETTER_AUTH_SECRET is a fresh 32+ character random value
  • BETTER_AUTH_URL is your real https:// origin
  • TLS terminates at a reverse proxy in front of the app
  • The proxy forwards Host and X-Forwarded-Proto
  • No low-entropy-secret warning in docker compose logs app
  • A health check hits /api/trpc/health.ping and gets ok: true
  • Nightly backups run and are stored off the host
  • You have decided how to handle open sign-ups

Once this list is green, you have a production instance: your data, your infrastructure, fully under your control.