Docker Compose
Production Docker Compose deployment
Prerequisites
- Docker Engine 20.10+
- Docker Compose v2+
- 1 CPU core minimum (2+ recommended), 512 MB RAM minimum (1 GB+ recommended)
docker --version
docker compose versionArchitecture
Three containers make up the system:
┌──────────────────────────────────────────────────────┐
│ Docker Network │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ database │──▶│ migrate │──▶│ backend │ │
│ │(postgres) │ │ (prisma) │ │(api + frontend)│ │
│ └──────────┘ └──────────┘ └────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ [pgdata] [backend_data] │
│ volume volume │
└──────────────────────────────────────────────────────┘
│
▼
Port 3000| Service | Image | Purpose |
|---|---|---|
database | postgres:17-alpine | Application data |
migrate | ghcr.io/januarylabs/limerence-migrate | Schema migrations |
backend | ghcr.io/januarylabs/limerence | API + web interface |
Quick Start
- Create a project directory:
mkdir limerence && cd limerence- Create
compose.yml:
services:
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: limerence
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-limerence}
POSTGRES_DB: limerence
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U limerence']
interval: 10s
timeout: 5s
retries: 5
migrate:
image: ghcr.io/januarylabs/limerence-migrate:latest
environment:
DATABASE_URL: postgresql://limerence:${POSTGRES_PASSWORD:-limerence}@database:5432/limerence
depends_on:
database:
condition: service_healthy
backend:
image: ghcr.io/januarylabs/limerence:latest
restart: unless-stopped
ports:
- '${PORT:-3000}:3000'
environment:
CONNECTION_STRING: postgresql://limerence:${POSTGRES_PASSWORD:-limerence}@database:5432/limerence
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: ${PUBLIC_URL:-http://localhost:3000}
BACKEND_BASE_URL: ${PUBLIC_URL:-http://localhost:3000}
FRONTEND_BASE_URL: ${PUBLIC_URL:-http://localhost:3000}
NODE_ENV: production
volumes:
- backend_data:/app/data
depends_on:
migrate:
condition: service_completed_successfully
healthcheck:
test:
['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/healthz']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
pgdata:
backend_data:- Generate a secret and create
.env:
openssl rand -hex 32Paste the output into .env:
BETTER_AUTH_SECRET=<paste-secret-here>
POSTGRES_PASSWORD=your-secure-password
PUBLIC_URL=http://localhost:3000
PORT=3000- Start:
docker compose up -d- Open http://localhost:3000.
Deployment Scenarios
External Database
Use a managed PostgreSQL service (AWS RDS, Supabase, Neon) instead of the bundled container:
services:
migrate:
image: ghcr.io/januarylabs/limerence-migrate:latest
environment:
DATABASE_URL: ${DATABASE_URL}
backend:
image: ghcr.io/januarylabs/limerence:latest
restart: unless-stopped
ports:
- '3000:3000'
environment:
CONNECTION_STRING: ${DATABASE_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: ${PUBLIC_URL}
BACKEND_BASE_URL: ${PUBLIC_URL}
FRONTEND_BASE_URL: ${PUBLIC_URL}
NODE_ENV: production
volumes:
- backend_data:/app/data
depends_on:
migrate:
condition: service_completed_successfully
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
volumes:
backend_data:# .env
DATABASE_URL=postgresql://user:password@your-db-host.com:5432/limerence?sslmode=require
BETTER_AUTH_SECRET=your-64-character-production-secret-here
PUBLIC_URL=https://limerence.yourdomain.comReverse Proxy with Caddy
Caddy provides automatic HTTPS:
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- backend
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: limerence
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: limerence
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U limerence']
interval: 10s
timeout: 5s
retries: 5
migrate:
image: ghcr.io/januarylabs/limerence-migrate:latest
environment:
DATABASE_URL: postgresql://limerence:${POSTGRES_PASSWORD}@database:5432/limerence
depends_on:
database:
condition: service_healthy
backend:
image: ghcr.io/januarylabs/limerence:latest
restart: unless-stopped
expose:
- '3000'
environment:
CONNECTION_STRING: postgresql://limerence:${POSTGRES_PASSWORD}@database:5432/limerence
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: https://${DOMAIN}
BACKEND_BASE_URL: https://${DOMAIN}
FRONTEND_BASE_URL: https://${DOMAIN}
NODE_ENV: production
volumes:
- backend_data:/app/data
depends_on:
migrate:
condition: service_completed_successfully
volumes:
pgdata:
backend_data:
caddy_data:
caddy_config:Caddyfile:
{$DOMAIN} {
reverse_proxy backend:3000
}# .env
DOMAIN=limerence.yourdomain.com
POSTGRES_PASSWORD=your-secure-password
BETTER_AUTH_SECRET=your-64-character-production-secretProduction Hardening
Resource Limits
backend:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
database:
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.25'
memory: 256MNetwork Isolation
Expose only the backend; keep the database on an internal network:
services:
database:
networks:
- internal
backend:
networks:
- internal
- external
ports:
- '3000:3000'
networks:
internal:
internal: true
external:Volumes
| Volume | Contents | Backup Priority |
|---|---|---|
pgdata | PostgreSQL data | Critical |
backend_data | Chat history (SQLite) | Important |
docker volume inspect limerence_pgdataOperations
Logs
docker compose logs -f
docker compose logs -f backend
docker compose logs --tail 100 backend
docker compose logs --since 2024-01-01T00:00:00 backendBackup
Database:
docker compose exec database pg_dump -U limerence limerence > backup.sql
# Compressed
docker compose exec database pg_dump -U limerence limerence | gzip > backup.sql.gzRestore:
cat backup.sql | docker compose exec -T database psql -U limerence limerence
# From compressed
gunzip -c backup.sql.gz | docker compose exec -T database psql -U limerence limerenceChat history:
docker compose cp backend:/app/data/.history.sqlite ./history-backup.sqliteUpgrade
docker compose pull
docker compose up -d
docker compose psPin a specific version by editing the image tag in compose.yml:
image: ghcr.io/januarylabs/limerence:v1.2.3Rollback
docker compose down
# Edit compose.yml to the previous image tag
docker compose up -d database
cat backup.sql | docker compose exec -T database psql -U limerence limerence
docker compose up -dTroubleshooting
Container fails to start
docker compose ps -a
docker compose logs backendCommon causes: missing environment variables, port conflict, volume permissions.
Database connection fails
docker compose exec backend wget -qO- http://localhost:3000/api/healthz
docker compose exec database pg_isready -U limerence
docker compose exec backend printenv CONNECTION_STRINGMigration errors
docker compose logs migrate
docker compose run --rm migrate
docker compose exec database psql -U limerence -c \
"SELECT version FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"Health check fails
docker inspect --format='{{json .State.Health}}' limerence-backend-1
docker compose exec backend wget -qO- http://localhost:3000/api/healthz
docker statsFull reset
docker compose down -v
docker compose down --rmi all
docker compose up -d