Docker Compose Setup
Production Docker Compose configuration for deploying oCore with PostgreSQL, the Go backend, and the Next.js frontend.
oCore ships with a production-ready docker-compose.prod.yml that runs all three services: PostgreSQL, the backend API server, and the Next.js frontend dashboard.
Production Docker Compose File
Here is the complete production Docker Compose file with annotations for each service.
services:
postgres:
image: postgres:16-alpine
hostname: ocore-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-ocore}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-ocore}
volumes:
- ocore_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ocore}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
backend:
build:
context: .
dockerfile: Dockerfile.backend
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-ocore}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-ocore}?sslmode=disable
JWT_SECRET: ${JWT_SECRET}
SSH_ENCRYPTION_KEY: ${SSH_ENCRYPTION_KEY}
APP_URL: ${APP_URL}
ENVIRONMENT: ${ENVIRONMENT:-production}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_FROM: ${SMTP_FROM}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
OAUTH_GITHUB_CLIENT_ID: ${OAUTH_GITHUB_CLIENT_ID:-}
OAUTH_GITHUB_CLIENT_SECRET: ${OAUTH_GITHUB_CLIENT_SECRET:-}
OAUTH_GOOGLE_CLIENT_ID: ${OAUTH_GOOGLE_CLIENT_ID:-}
OAUTH_GOOGLE_CLIENT_SECRET: ${OAUTH_GOOGLE_CLIENT_SECRET:-}
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-}
WEBAUTHN_RP_ORIGIN: ${WEBAUTHN_RP_ORIGIN:-}
VAPID_CONTACT: ${VAPID_CONTACT:-}
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
SSH_GATEWAY_ENABLED: ${SSH_GATEWAY_ENABLED:-true}
SSH_HOST_KEY_PATH: /app/data/ssh_host_ed25519_key
SSH_LISTEN_ADDR: ${SSH_LISTEN_ADDR:-:2222}
expose:
- "8080"
ports:
- "2222:2222"
depends_on:
postgres:
condition: service_healthy
volumes:
- ocore_backend_data:/app/data
restart: unless-stopped
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
NEXT_PUBLIC_SSH_PORT: ${NEXT_PUBLIC_SSH_PORT:-2222}
depends_on:
- backend
restart: unless-stopped
volumes:
ocore_postgres_data:
name: ocore_postgres_data
ocore_backend_data:
name: ocore_backend_dataService Breakdown
PostgreSQL
The database service uses the official PostgreSQL 16 Alpine image for a small footprint.
| Setting | Value | Purpose |
|---|---|---|
POSTGRES_USER | ocore (default) | Database superuser name |
POSTGRES_PASSWORD | Required | Database password -- set in .env |
POSTGRES_DB | ocore (default) | Database name created on first start |
The ocore_postgres_data volume persists database files across container restarts. The health check uses pg_isready to confirm PostgreSQL is accepting connections before the backend starts.
Backend
The Go backend handles the REST API, background job processing (River queue), and the SSH gateway for Odoo instance access.
Key configuration:
DATABASE_URL-- Constructed from the Postgres credentials. Thesslmode=disableis safe because the connection stays within the Docker network.JWT_SECRET-- Must be at least 32 characters. Used for signing access and refresh tokens.SSH_ENCRYPTION_KEY-- Encrypts stored SSH credentials at rest. Required in production.APP_URL-- The public URL where users access the dashboard (e.g.,https://ocore.example.com).- Port 8080 -- The API server listens internally on 8080 but is only
exposed (not published). Route traffic through a reverse proxy. - Port 2222 -- The SSH gateway is published directly since reverse proxying SSH is not typical.
The ocore_backend_data volume stores the SSH host key. This key must persist across restarts so that SSH clients do not see host key change warnings.
See the Environment Variables reference for every available variable.
Frontend
The Next.js dashboard is built with environment variables baked in at build time via Docker build args:
NEXT_PUBLIC_API_URL-- The public URL of the backend API (e.g.,https://ocore.example.com/apior the URL your reverse proxy routes to the backend).NEXT_PUBLIC_SSH_PORT-- The SSH gateway port shown in the dashboard UI (default2222).
The frontend container runs the Next.js standalone server and depends on the backend being available.
Environment File
Create a .env file in the same directory as docker-compose.prod.yml:
# Database
POSTGRES_PASSWORD=your-strong-database-password
# Backend (required)
JWT_SECRET=your-jwt-secret-at-least-32-characters-long
SSH_ENCRYPTION_KEY=your-encryption-key-at-least-32-characters
APP_URL=https://ocore.example.com
ENVIRONMENT=production
# SMTP (required for email verification, password reset)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_FROM=noreply@example.com
SMTP_USERNAME=your-smtp-username
SMTP_PASSWORD=your-smtp-password
# Frontend
NEXT_PUBLIC_API_URL=https://ocore.example.com
NEXT_PUBLIC_SSH_PORT=2222
# Optional: OAuth providers
# OAUTH_GITHUB_CLIENT_ID=
# OAUTH_GITHUB_CLIENT_SECRET=
# OAUTH_GOOGLE_CLIENT_ID=
# OAUTH_GOOGLE_CLIENT_SECRET=
# Optional: WebAuthn (passkey/security key login)
# WEBAUTHN_RP_ID=ocore.example.com
# WEBAUTHN_RP_ORIGIN=https://ocore.example.com
# Optional: Web Push notifications
# VAPID_CONTACT=mailto:admin@example.com
# VAPID_PUBLIC_KEY=
# VAPID_PRIVATE_KEY=Generate strong secrets:
# Generate a 64-character random secret
openssl rand -base64 48Starting the Stack
# Pull or build images
docker compose -f docker-compose.prod.yml build
# Start all services in the background
docker compose -f docker-compose.prod.yml up -d
# Run database migrations
docker compose -f docker-compose.prod.yml exec backend /app/server migrate
# Check service health
docker compose -f docker-compose.prod.yml psUpdating oCore
To update to a new version:
# Pull the latest code
git pull origin main
# Rebuild images
docker compose -f docker-compose.prod.yml build
# Apply any new migrations
docker compose -f docker-compose.prod.yml exec backend /app/server migrate
# Restart with the new images
docker compose -f docker-compose.prod.yml up -dCustomization
Using an External Database
To use an existing PostgreSQL server instead of the bundled container, remove the postgres service and volume, then set DATABASE_URL directly:
services:
backend:
# ...
environment:
DATABASE_URL: postgres://user:password@your-db-host:5432/ocore?sslmode=require
# ... other vars
# Remove depends_on: postgresUsing Pre-built Images
If you publish container images to a registry, replace the build directives with image:
services:
backend:
image: ghcr.io/ocore-sh/ocore-backend:latest
# ... rest of config
frontend:
image: ghcr.io/ocore-sh/ocore-frontend:latest
# ... rest of configDisabling the SSH Gateway
If you do not need SSH access to Odoo instances:
services:
backend:
environment:
SSH_GATEWAY_ENABLED: "false"
# Remove the 2222 port mappingTroubleshooting
Backend cannot connect to PostgreSQL
Verify the database is healthy:
docker compose -f docker-compose.prod.yml exec postgres pg_isready -U ocoreIf using an external database, confirm the DATABASE_URL is reachable from the backend container:
docker compose -f docker-compose.prod.yml exec backend sh -c \
'nc -zv your-db-host 5432'Frontend shows API connection errors
Ensure NEXT_PUBLIC_API_URL is set to a URL accessible from the user's browser -- not a Docker-internal hostname. For example, use https://ocore.example.com (through your reverse proxy), not http://backend:8080.
SSH gateway not reachable
Confirm port 2222 is published and not blocked by your firewall:
# Test from a remote machine
ssh -p 2222 test@ocore.example.comIf using a cloud provider, check security group rules for port 2222/TCP.