oCoreoCore Docs

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.

docker-compose.prod.yml
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_data

Service Breakdown

PostgreSQL

The database service uses the official PostgreSQL 16 Alpine image for a small footprint.

SettingValuePurpose
POSTGRES_USERocore (default)Database superuser name
POSTGRES_PASSWORDRequiredDatabase password -- set in .env
POSTGRES_DBocore (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. The sslmode=disable is 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/api or the URL your reverse proxy routes to the backend).
  • NEXT_PUBLIC_SSH_PORT -- The SSH gateway port shown in the dashboard UI (default 2222).

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:

.env
# 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 48

Starting 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 ps

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

Customization

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:

docker-compose.prod.yml (modified)
services:
  backend:
    # ...
    environment:
      DATABASE_URL: postgres://user:password@your-db-host:5432/ocore?sslmode=require
      # ... other vars
    # Remove depends_on: postgres

Using 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 config

Disabling the SSH Gateway

If you do not need SSH access to Odoo instances:

services:
  backend:
    environment:
      SSH_GATEWAY_ENABLED: "false"
    # Remove the 2222 port mapping

Troubleshooting

Backend cannot connect to PostgreSQL

Verify the database is healthy:

docker compose -f docker-compose.prod.yml exec postgres pg_isready -U ocore

If 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.com

If using a cloud provider, check security group rules for port 2222/TCP.

Was this page helpful?