Coomander Docs

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 up

Local Docker deployment (Mac Mini + Caddy)

Architecture

  • Docker -- containerized app isolation
  • Caddy -- reverse proxy routing subdomains
  • Cloudflare Tunnel -- secure HTTPS without port forwarding

Prerequisites

  1. Docker + Docker Compose installed
  2. Caddy configured as reverse proxy (port 8080)
  3. Cloudflare Tunnel routing to localhost:8080
  4. Domain with wildcard DNS

Steps

1. Choose a port

Pick an unused port. Check existing ports:

grep -r "ports:" docker-compose.yml

2. 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: MyApp

4. 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/github

6. Build and deploy

docker-compose up -d --build your-app-name
docker logs -f your-app-name

Railway 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 init

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 main

Or manually: railway up

Database backups (Litestream)

Litestream continuously streams SQLite WAL changes to S3-compatible storage.

Setup

  1. Create an S3 bucket (AWS S3, Cloudflare R2, or any S3-compatible provider)
  2. 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
  1. Start the production stack:
docker-compose up -d prod litestream

Restore from backup

bash scripts/litestream-restore.sh ./data/restored.db

Environment variables reference

Required

VariableDescription
BETTER_AUTH_SECRETAuth session signing key (run openssl rand -base64 32)
BETTER_AUTH_URLYour app's public URL
DATABASE_PATHSQLite database file path
APP_URLYour app's public URL
APP_NAMEDisplay name in emails and MFA

Optional

VariableDescription
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETGitHub OAuth
RESEND_API_KEYResend transactional email
STRIPE_SECRET_KEYStripe API key
STRIPE_PRICE_IDStripe subscription price ID
STRIPE_WEBHOOK_SECRETStripe webhook signing secret

Health check endpoint

GET /api/health

Returns 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.

On this page