Deployment
This guide covers running Momo in production. For a quick local setup, see Getting Started.
Quick Start (Docker Compose)
The recommended way to self-host Momo.
Prerequisites
- Docker Engine 24+ and Docker Compose v2
- A domain name (optional, but recommended for production)
- At least one OAuth App configured (see OAuth Setup)
Steps
- Clone the repository:
git clone https://github.com/jp1337/momo.git cd momo - Copy and configure environment variables:
cp .env.example .env.local # Edit .env.local with your credentials - Start all services:
docker compose up -d - Open
http://localhost:3000(or your domain if behind a reverse proxy)
Migrations run automatically. The container runs all pending database migrations before the Next.js server starts. Check
docker compose logs appafter deployment to confirm.
Container Images
Images are published to three registries on every release:
| Registry | Image |
|---|---|
| GitHub Container Registry | ghcr.io/jp1337/momo |
| Docker Hub | docker.io/jp1337/momo |
| Quay.io | quay.io/jp1337/momo |
To use a pre-built image instead of building locally, replace the build section in docker-compose.yml with:
app:
image: ghcr.io/jp1337/momo:latest
Dockerfile Notes
- Multi-stage build:
depsβbuilderβrunner - Base image:
node:22-alpine(Node.js 22 LTS, supported until April 2027) - Runs as non-root user (
nextjs:1001) - Uses
output: standalonefor a minimal production bundle - No dev dependencies in the final image
docker-entrypoint.shrunsscripts/migrate.mjson every container start β applies any pending Drizzle migrations before the server starts- HEALTHCHECK hits
/api/healthevery 30 seconds (start-period: 30sto allow migrations to finish)
Production Checklist
Before going live, complete all items below:
- Generate AUTH_SECRET β must be at least 32 random bytes:
openssl rand -base64 32 - Set
AUTH_TRUST_HOST=trueβ required when the app runs behind any reverse proxy (nginx, Caddy, Traefik) or in Kubernetes. Auth.js v5 rejects requests from unrecognised hosts unless this is set. - Set all required environment variables β see Environment Variables
- Generate VAPID keys for push notifications:
npx web-push generate-vapid-keys - Register OAuth apps for your production domain (callback URLs must match):
- GitHub:
https://your-domain.com/api/auth/callback/github - Discord:
https://your-domain.com/api/auth/callback/discord - Google:
https://your-domain.com/api/auth/callback/google
- GitHub:
- Set CRON_SECRET to protect cron endpoints from unauthorized access:
openssl rand -hex 32 - Configure TLS β use a reverse proxy (nginx, Caddy) or cert-manager in Kubernetes
- Configure HSTS β the app sets
Strict-Transport-Securityheaders automatically - Never commit real secrets to git β use
.env.local(gitignored) or a secrets manager - Migrations run automatically β the container applies all pending migrations on startup. No manual step needed; check
docker compose logs appto verify.
Reverse Proxy with Caddy
Caddy is the simplest way to add HTTPS in front of Momo. Create a Caddyfile:
momo.example.com {
reverse_proxy localhost:3000
}
Caddy automatically provisions a Letβs Encrypt certificate. Run:
caddy run --config Caddyfile
Important: When running behind Caddy (or any reverse proxy), set
AUTH_TRUST_HOST=truein.env.local. Auth.js v5 requires this to accept requests forwarded by the proxy.
Reverse Proxy with nginx
server {
listen 443 ssl;
server_name momo.example.com;
ssl_certificate /etc/letsencrypt/live/momo.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/momo.example.com/privkey.pem;
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;
}
}
Important: When running behind nginx (or any reverse proxy), set
AUTH_TRUST_HOST=truein.env.local. Auth.js v5 requires this to accept requests forwarded by the proxy.
AUTH_SECRET Rotation
To rotate the AUTH_SECRET (e.g. after a suspected compromise):
- Generate a new secret:
openssl rand -base64 32 - Update the secret in your environment:
- Docker Compose: update
AUTH_SECRETin.env.local, then restart the app:docker compose up -d app - Kubernetes: update the Secret and trigger a rollout:
kubectl patch secret momo-secrets -n momo \ --type=merge \ -p '{"stringData":{"AUTH_SECRET":"<new-secret>"}}' kubectl rollout restart deployment/momo-app -n momo
- Docker Compose: update
-
Effect: All existing sessions are immediately invalidated. All users will be signed out and must log in again. This is expected and safe.
- Frequency: Rotate at minimum once per year. Rotate immediately if the secret is exposed.
Kubernetes
For full Kubernetes deployment instructions, see the Kubernetes guide.
The example manifests are in deploy/examples/ in the repository:
| File | Purpose |
|---|---|
namespace.yaml |
Creates the momo namespace |
deployment.yaml |
App deployment with 2 replicas, liveness/readiness probes |
service.yaml |
ClusterIP service for the app |
ingress.yaml |
Ingress with TLS (cert-manager + ingress-nginx) |
secret.example.yaml |
Template for required Kubernetes secrets |
postgres-statefulset.yaml |
PostgreSQL 18 StatefulSet with persistent volume |