Deployment
Deploy your Coomander application to Docker or Railway.
Quick start
Local Docker:
docker-compose up -d --build <app-name>Railway Cloud:
railway login && railway link && railway upLocal Docker deployment (Mac Mini + Caddy)
Architecture
- Docker -- containerized app isolation
- Caddy -- reverse proxy routing subdomains
- Cloudflare Tunnel -- secure HTTPS without port forwarding
Prerequisites
- Docker + Docker Compose installed
- Caddy configured as reverse proxy (port 8080)
- Cloudflare Tunnel routing to
localhost:8080 - Domain with wildcard DNS
Steps
1. Choose a port
Pick an unused port. Check existing ports:
grep -r "ports:" docker-compose.yml2. Update Dockerfile
EXPOSE 3XXX
ENV PORT=3XXX
CMD ["node_modules/.bin/next", "start", "-p", "3XXX"]3. Add to docker-compose.yml
services:
your-app-name:
build:
context: ./your-app-name
dockerfile: Dockerfile
container_name: your-app-name
restart: unless-stopped
ports:
- "3XXX:3XXX"
volumes:
- ./your-app-name/data:/data
environment:
DATABASE_PATH: /data/coomander.db
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: https://yourapp.example.com
APP_URL: https://yourapp.example.com
APP_NAME: MyApp4. Update Caddyfile
yourapp.example.com {
reverse_proxy localhost:3XXX
}Reload: caddy reload --config ~/.config/caddy/Caddyfile
5. Update OAuth redirect URIs
Add to Google Console + GitHub OAuth App:
https://yourapp.example.com/api/auth/callback/google
https://yourapp.example.com/api/auth/callback/github6. Build and deploy
docker-compose up -d --build your-app-name
docker logs -f your-app-nameRailway cloud deployment
Why Railway?
- Auto-deploy from git push
- Persistent volumes for SQLite
- HTTPS + custom domains included
- Free tier: 500 hours/month, 500MB disk
Steps
1. Initialize
railway login
cd your-app-folder
railway init2. Link GitHub repo
In Railway dashboard: Settings > Connect GitHub repo > Choose your repo > Set branch: main
3. Add persistent volume
In Railway dashboard: Go to your service > Variables > Add Volume > Mount path: /data
Critical: Volume must exist BEFORE setting DATABASE_PATH=/data/coomander.db
4. Set environment variables
railway variables set BETTER_AUTH_SECRET="$(openssl rand -base64 32)"
railway variables set BETTER_AUTH_URL="https://yourapp.up.railway.app"
railway variables set APP_URL="https://yourapp.up.railway.app"
railway variables set APP_NAME="MyApp"
railway variables set DATABASE_PATH="/data/coomander.db"5. Deploy
Railway auto-deploys on git push:
git push origin mainOr manually: railway up
Database backups (Litestream)
Litestream continuously streams SQLite WAL changes to S3-compatible storage.
Setup
- Create an S3 bucket (AWS S3, Cloudflare R2, or any S3-compatible provider)
- Set environment variables:
LITESTREAM_ACCESS_KEY_ID=your-access-key
LITESTREAM_SECRET_ACCESS_KEY=your-secret-key
LITESTREAM_REPLICA_BUCKET=my-app-backups
LITESTREAM_REPLICA_PATH=db
LITESTREAM_REPLICA_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
LITESTREAM_REPLICA_REGION=us-east-1- Start the production stack:
docker-compose up -d prod litestreamRestore from backup
bash scripts/litestream-restore.sh ./data/restored.dbEnvironment variables reference
Required
| Variable | Description |
|---|---|
BETTER_AUTH_SECRET | Auth session signing key (run openssl rand -base64 32) |
BETTER_AUTH_URL | Your app's public URL |
DATABASE_PATH | SQLite database file path |
APP_URL | Your app's public URL |
APP_NAME | Display name in emails and MFA |
Optional
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Google OAuth |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | GitHub OAuth |
RESEND_API_KEY | Resend transactional email |
STRIPE_SECRET_KEY | Stripe API key |
STRIPE_PRICE_ID | Stripe subscription price ID |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret |
Health check endpoint
GET /api/healthReturns 200 when healthy, 503 when degraded:
{
"ok": true,
"db": true,
"auth": true,
"timestamp": "2026-03-19T12:00:00.000Z"
}Deployment checklist
- Environment variables set (BETTER_AUTH_SECRET, BETTER_AUTH_URL, APP_URL)
- Database volume configured
- OAuth redirect URIs updated
- Stripe webhooks configured (if using payments)
- Test signup/login flows
- Test database persistence (restart container, data survives)
- SSL certificate working (HTTPS)
- Litestream configured for backups (optional but recommended)
Troubleshooting
"Environment validation failed"
One or more required env vars are missing. The error output tells you exactly which.
Health check returns auth: false
Better Auth migrations didn't run. Trigger manually:
docker exec coomander-node npx @better-auth/cli@latest migrate --config lib/auth.ts"Address already in use"
Port conflict. Check what's using the port: docker ps
"SQLITE_CANTOPEN"
Ensure the volume exists at /data and DATABASE_PATH=/data/coomander.db is set.