answerLoopsanswerLoopsDocs
Self-hosting guide

Production Deployment

Deploy answerLoops to a production server.

This page shows how to run answerLoops on a real domain with HTTPS. When you are done, your team signs in at your own address, the bot runs in the background, and a health check confirms the app can reach its database.

It assumes you have already worked through the self-host quickstart locally. Production is the same app with real secrets, a real domain, and a compose file that does not include a database.

How the pieces fit

A production deployment. Browsers and services that send webhooks reach the app through your reverse proxy. The bot has no public address: it talks to the app and the database, and connects out to chat platforms.

The app is the only service that needs to be reachable from the internet. The bot is the background worker: it listens to Discord, polls Slack where configured, and runs the jobs that sync knowledge-base sources and re-index content. Run both.

What you need

  • A Linux server with Docker (for example Ubuntu 22.04 or newer), or a platform such as Railway.
  • A Postgres database with the pgvector extension. The production compose files do not start one, so use a managed Postgres or one you run yourself. See Prerequisites.
  • A reverse proxy for HTTPS. Caddy gets certificates automatically; nginx works too. A platform like Railway provides HTTPS for you.

Set up the server

Clone the repository on the server and create a production .env:

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

Use .env for production values, not .env.local.

Fill in the production values using the checklist below.

Put a reverse proxy in front of port 3000 (see the next section).

Start the stack.

docker compose -f docker-compose.ghcr.yml up -d

Pin a version with ANSWERLOOPS_IMAGE=ghcr.io/answerloops/answerloops:<version> for anything you want to stay put. See Docker setup.

Check that it is healthy (see Health check).

Database migrations run automatically when the app starts, so there is no separate migration step.

Production checklist

Before going live, confirm these in your production .env:

  • AUTH_URL is your public address, for example https://app.yourdomain.com
  • AUTH_SECRET is a new random value, not the one from development
  • ENCRYPTION_KEY is a new random value, not the one from development
  • DATABASE_URL points at your production database (plus DIRECT_DATABASE_URL if that connection goes through a pooler)
  • The Google redirect URI is https://<your-domain>/api/auth/callback/google (see OAuth setup)
  • OPENAI_API_KEY is set, or every workspace saves its own key in Settings
  • BOT_SECRET is set, and BOT_TARGET_URL points at the app as the bot sees it
  • TRUST_PROXY_HOPS matches the number of proxies in front of the app (the default 1 fits a single proxy)

All of these are described in Environment variables.

Add HTTPS with a reverse proxy

Caddy obtains and renews certificates for you. Put this in a Caddyfile:

app.yourdomain.com {
    reverse_proxy localhost:3000
}

Then run caddy run --config Caddyfile.

The bot must be running

Keep the bot service running. It is the worker that picks up queued knowledge-base syncs (Notion, GitHub), retries stuck ingests, and re-indexes content after an embedding model change. Without it those jobs never start.

The bot reaches the app at BOT_TARGET_URL (default http://localhost:3000) and sends BOT_SECRET with its requests. Use the same BOT_SECRET value on both services. If the two are not on the same Docker network or host, set BOT_TARGET_URL to the app's address as the bot sees it. In the bundled compose files, the bot uses http://app:3000.

The sync itself runs inside the app's POST /api/kb/sync-jobs/run route, which keeps working after it responds. Deploy the app on a runtime without a short per-request time limit. 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}

The endpoint runs a trivial query against the database, so a failure can mean the database is unreachable and not just that the app is down. The Docker image checks it every 30 seconds for the app container. The bot has no web port, so its health check is turned off in the compose files. Watch its logs instead.

Alternative: Railway or another platform

If you would rather not manage a server, reverse proxy, and certificates, deploy to Railway or a similar platform. Both services build from the repository's Dockerfile. The repository includes .railway/railway.ts, which describes both services in Railway's Infrastructure as Code format. It is the file the project's own hosted deployment uses, so treat it as a reference and adjust the repository, domains and region for your own project:

  • App: starts with pnpm start (the Dockerfile default) and uses /api/health as its deploy health check.
  • Bot: starts with pnpm bot:start, the same image started as the bot. It has no web port, so it has no health check.

Railway does not read this file when it deploys. Settings reach Railway only when you run railway config apply (Railway CLI 5.42.1 or newer, logged in and linked to your project), so preview any change with railway config plan first. Environment variables stay in Railway and are never written to the file. If a service still has a "Config file path" set in the Railway dashboard, clear it before you apply, because a service cannot be managed by both systems.

The platform provides HTTPS, so you skip the proxy section above. You still set the same environment variables, and you need a managed Postgres with pgvector. Set BOT_TARGET_URL on the bot to the app's public address, since the two run as separate services.

Where to go next

On this page