answerLoopsanswerLoops Docs
Self-Hosting

Production Deployment

Deploy answerLoops to a production server.

This assumes you've already worked through the self-host quickstart locally and know your setup works — production is the same app, just with real secrets, a real domain, and docker-compose.prod.yml instead of the dev compose file.

  • Server: Any Linux VPS (Ubuntu 22.04+)
  • Reverse proxy: Caddy (auto SSL) or nginx
  • Process: Docker Compose
  • Database: Docker Postgres with named volume, or managed Postgres (Neon, Supabase, Railway)

Get the code onto the server

Clone the repo on your server the same way you did locally, then create a production .env (not .env.local — that's the dev-only file):

git clone https://github.com/answerLoops/answerLoops.git
cd answerLoops
cp .env.local.example .env

Fill in .env with your production values — the checklist below covers what has to be different from your dev setup.

Environment checklist

Before going live, verify these are set in your production .env:

  • AUTH_URL set to your production domain (e.g. https://app.yourdomain.com)
  • AUTH_SECRET — unique 32-byte hex, not the same as dev
  • ENCRYPTION_KEY — unique 32-byte hex, not the same as dev
  • DATABASE_URL pointing to production database
  • OAuth callback URLs updated in GitHub/Google dashboards
  • OPENAI_API_KEY set
app.yourdomain.com {
    reverse_proxy localhost:3000
}

Run: caddy run --config Caddyfile

nginx

server {
    listen 443 ssl;
    server_name app.yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

Start in production

Use docker-compose.prod.yml, not the plain docker-compose.yml from local dev — it builds the multi-stage production image and runs the bot in bot:start mode instead of the dev watch mode:

docker compose -f docker-compose.prod.yml up -d --build

Drizzle migrations run automatically against your production database on first start, same as local dev — no separate migration step.

The bot service must be running for knowledge-base syncs (Notion, GitHub) to complete — it's the worker that picks up queued sync jobs and drives them. It reaches the app at BOT_TARGET_URL (default http://localhost:3000) and authenticates with BOT_SECRET; set both if the two services aren't on the same host. The sync itself runs inside POST /api/kb/sync-jobs/run on the app, which does real work after responding to auth — deploy the app on a runtime without a short per-request timeout (the standard pnpm start Node server is fine; a serverless function platform may cut a large sync off).

Health check

curl https://app.yourdomain.com/api/health
# → {"ok":true}

Alternative: Railway or another PaaS

If you'd rather not manage a VPS, reverse proxy, and TLS certs yourself, push the repo to Railway (or another PaaS) — Railway reads the committed railway.toml / railway.bot.toml, builds each service with Nixpacks, handles HTTPS automatically, and you skip the Caddy/nginx section above entirely. You'll still need to set the same environment variables, and a managed Postgres (Neon, Railway's own Postgres) instead of the Docker-volume one.

On this page