Limerence

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 version

Architecture

Three containers make up the system:

┌──────────────────────────────────────────────────────┐
│                    Docker Network                     │
│                                                      │
│  ┌──────────┐    ┌──────────┐    ┌────────────────┐ │
│  │ database  │──▶│ migrate  │──▶│    backend      │ │
│  │(postgres) │    │ (prisma) │    │(api + frontend)│ │
│  └──────────┘    └──────────┘    └────────────────┘ │
│       │                                   │          │
│       ▼                                   ▼          │
│   [pgdata]                          [backend_data]   │
│    volume                             volume         │
└──────────────────────────────────────────────────────┘


                                       Port 3000
ServiceImagePurpose
databasepostgres:17-alpineApplication data
migrateghcr.io/januarylabs/limerence-migrateSchema migrations
backendghcr.io/januarylabs/limerenceAPI + web interface

Quick Start

  1. Create a project directory:
mkdir limerence && cd limerence
  1. 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:
  1. Generate a secret and create .env:
openssl rand -hex 32

Paste the output into .env:

BETTER_AUTH_SECRET=<paste-secret-here>
POSTGRES_PASSWORD=your-secure-password
PUBLIC_URL=http://localhost:3000
PORT=3000
  1. Start:
docker compose up -d
  1. 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.com

Reverse 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-secret

Production 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: 256M

Network 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

VolumeContentsBackup Priority
pgdataPostgreSQL dataCritical
backend_dataChat history (SQLite)Important
docker volume inspect limerence_pgdata

Operations

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 backend

Backup

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

Restore:

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 limerence

Chat history:

docker compose cp backend:/app/data/.history.sqlite ./history-backup.sqlite

Upgrade

docker compose pull
docker compose up -d
docker compose ps

Pin a specific version by editing the image tag in compose.yml:

image: ghcr.io/januarylabs/limerence:v1.2.3

Rollback

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 -d

Troubleshooting

Container fails to start

docker compose ps -a
docker compose logs backend

Common 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_STRING

Migration 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 stats

Full reset

docker compose down -v
docker compose down --rmi all
docker compose up -d

On this page