Files
classroomio/docker-compose.yaml
T
Ulises Vargas 4348e89860
Publish Docker Images / Smoke test (api) (push) Canceled after 0s
Publish Docker Images / Smoke test (dashboard) (push) Canceled after 0s
Publish Docker Images / Smoke test (jobs) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.api, api, api) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.dashboard, dashboard, dashboard) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.jobs, jobs, jobs) (push) Canceled after 0s
MCP package build check / build (push) Canceled after 0s
Package build check / build (push) Canceled after 0s
Upload OpenAPI Spec to R2 / generate-and-upload-openapi (push) Canceled after 0s
chore: prepare for deployment
2026-08-24 08:56:48 -07:00

228 lines
8.7 KiB
YAML

services:
postgres:
image: postgres:16-alpine
container_name: cio-postgres
# Bound to localhost only so host tools (pnpm api:dev, db:setup) can reach it
# without exposing the database to the LAN.
ports:
- '127.0.0.1:5432:5432'
environment:
POSTGRES_DB: ${POSTGRES_DB:-classroomio}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
# Require a successful query so we only pass once Postgres is out of recovery
# (pg_isready can pass while DB is still in recovery, causing 57P03 on sign-in)
test:
[
'CMD-SHELL',
"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-classroomio} && psql -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-classroomio} -tAc 'SELECT 1'"
]
interval: 5s
timeout: 5s
retries: 15
start_period: 10s
redis:
image: redis:7-alpine
container_name: cio-redis
# Bound to localhost only (see postgres note above).
ports:
- '127.0.0.1:6379:6379'
command: ['redis-server', '--appendonly', 'yes']
volumes:
- redis-data:/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 10
start_period: 5s
minio:
image: minio/minio:latest
container_name: cio-minio
command: server /data --console-address ":9001"
ports:
- '9000:9000'
- '9001:9001'
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
volumes:
- minio-data:/data
profiles:
- minio
minio-init:
image: minio/mc:latest
container_name: cio-minio-init
depends_on:
- minio
entrypoint: >
/bin/sh -c "
echo 'Waiting for MinIO to be ready...';
sleep 5;
mc alias set local http://minio:9000 $${MINIO_ROOT_USER:-minioadmin} $${MINIO_ROOT_PASSWORD:-minioadmin};
mc mb local/videos --ignore-existing;
mc mb local/documents --ignore-existing;
mc mb local/media --ignore-existing;
mc anonymous set download local/media;
echo 'MinIO buckets created';
exit 0;
"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
profiles:
- minio
api:
build:
context: .
dockerfile: docker/Dockerfile.api
container_name: cio-api
ports:
- '3081:3081'
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
# node:slim has no curl/wget; use Node's global fetch (Node 20+).
test:
[
'CMD',
'node',
'-e',
"fetch('http://localhost:3081/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
environment:
NODE_ENV: production
PORT: 3081
PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:-http://localhost:3081}
TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-}
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio}
REDIS_URL: redis://redis:6379
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-docker-local-client-id}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-docker-local-client-secret}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_PORT: ${SMTP_PORT:-}
SMTP_SENDER: ${SMTP_SENDER:-}
# Object storage (S3-compatible). Works with the bundled MinIO, AWS S3, or Cloudflare R2
# — for R2, point OBJECT_STORAGE_ENDPOINT at your R2 endpoint.
OBJECT_STORAGE_ENDPOINT: ${OBJECT_STORAGE_ENDPOINT:-}
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${OBJECT_STORAGE_PUBLIC_ENDPOINT:-}
OBJECT_STORAGE_ACCESS_KEY_ID: ${OBJECT_STORAGE_ACCESS_KEY_ID:-}
OBJECT_STORAGE_SECRET_ACCESS_KEY: ${OBJECT_STORAGE_SECRET_ACCESS_KEY:-}
OBJECT_STORAGE_FORCE_PATH_STYLE: ${OBJECT_STORAGE_FORCE_PATH_STYLE:-true}
OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL: ${OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL:-}
# Auth secrets — required. Generated by classroomio.sh; set strong values
# in .env if you start the stack with `docker compose` directly.
PRIVATE_SERVER_KEY: ${PRIVATE_SERVER_KEY:?set a strong PRIVATE_SERVER_KEY in .env}
DASHBOARD_ORIGIN: ${DASHBOARD_ORIGIN:-}
LICENSE_KEY: ${LICENSE_KEY:-}
UNSPLASH_API_KEY: ${UNSPLASH_API_KEY:-}
# Upload size limits (megabytes). Unset = built-in defaults — see .env.example.
UPLOAD_MAX_DOCUMENT_MB: ${UPLOAD_MAX_DOCUMENT_MB:-}
UPLOAD_MAX_IMAGE_MB: ${UPLOAD_MAX_IMAGE_MB:-}
UPLOAD_MAX_VIDEO_MB: ${UPLOAD_MAX_VIDEO_MB:-}
UPLOAD_MAX_EXERCISE_FILE_MB: ${UPLOAD_MAX_EXERCISE_FILE_MB:-}
UPLOAD_MAX_AGENT_DOCUMENT_MB: ${UPLOAD_MAX_AGENT_DOCUMENT_MB:-}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set a strong BETTER_AUTH_SECRET in .env}
SKIP_DB_SETUP: ${SKIP_DB_SETUP:-}
restart: unless-stopped
dashboard:
build:
context: .
dockerfile: docker/Dockerfile.dashboard
args:
PUBLIC_IS_SELFHOSTED: ${PUBLIC_IS_SELFHOSTED:-true}
container_name: cio-dashboard
ports:
- '3082:3082'
depends_on:
api:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 3082
ORIGIN: ${DASHBOARD_ORIGIN:-http://localhost:3082}
PUBLIC_IS_SELFHOSTED: ${PUBLIC_IS_SELFHOSTED:-true}
PRIVATE_SERVER_URL: ${PRIVATE_SERVER_URL:-http://api:3081}
PRIVATE_SERVER_KEY: ${PRIVATE_SERVER_KEY:?set a strong PRIVATE_SERVER_KEY in .env}
PRIVATE_APP_HOST: ${PRIVATE_APP_HOST:-localhost}
PRIVATE_APP_SUBDOMAINS: ${PRIVATE_APP_SUBDOMAINS:-app}
ALLOWED_EXTERNAL_DOMAINS: ${ALLOWED_EXTERNAL_DOMAINS:-}
CSP_SCRIPT_SRC_DOMAINS: ${CSP_SCRIPT_SRC_DOMAINS:-}
CSP_STYLE_SRC_DOMAINS: ${CSP_STYLE_SRC_DOMAINS:-}
CSP_CONNECT_SRC_DOMAINS: ${CSP_CONNECT_SRC_DOMAINS:-}
CSP_FRAME_SRC_DOMAINS: ${CSP_FRAME_SRC_DOMAINS:-}
CSP_FONT_SRC_DOMAINS: ${CSP_FONT_SRC_DOMAINS:-}
CSP_MEDIA_SRC_DOMAINS: ${CSP_MEDIA_SRC_DOMAINS:-}
# Upload size limits (megabytes). Unset = built-in defaults — see .env.example.
UPLOAD_MAX_DOCUMENT_MB: ${UPLOAD_MAX_DOCUMENT_MB:-}
UPLOAD_MAX_IMAGE_MB: ${UPLOAD_MAX_IMAGE_MB:-}
UPLOAD_MAX_VIDEO_MB: ${UPLOAD_MAX_VIDEO_MB:-}
UPLOAD_MAX_EXERCISE_FILE_MB: ${UPLOAD_MAX_EXERCISE_FILE_MB:-}
UPLOAD_MAX_AGENT_DOCUMENT_MB: ${UPLOAD_MAX_AGENT_DOCUMENT_MB:-}
UPLOAD_MAX_LANDING_IMAGE_MB: ${UPLOAD_MAX_LANDING_IMAGE_MB:-}
UPLOAD_MAX_THUMBNAIL_MB: ${UPLOAD_MAX_THUMBNAIL_MB:-}
restart: unless-stopped
# Background worker (BullMQ). Drains the Redis queue: video processing, transcription,
# AI course generation, and emails.
jobs:
build:
context: .
dockerfile: docker/Dockerfile.jobs
container_name: cio-jobs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
NODE_ENV: production
REDIS_URL: redis://redis:6379
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio}
# Object storage (workers stream object bytes). Mirrors the api service.
OBJECT_STORAGE_ENDPOINT: ${OBJECT_STORAGE_ENDPOINT:-}
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${OBJECT_STORAGE_PUBLIC_ENDPOINT:-}
OBJECT_STORAGE_REGION: ${OBJECT_STORAGE_REGION:-}
OBJECT_STORAGE_ACCESS_KEY_ID: ${OBJECT_STORAGE_ACCESS_KEY_ID:-}
OBJECT_STORAGE_SECRET_ACCESS_KEY: ${OBJECT_STORAGE_SECRET_ACCESS_KEY:-}
OBJECT_STORAGE_FORCE_PATH_STYLE: ${OBJECT_STORAGE_FORCE_PATH_STYLE:-true}
OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL: ${OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL:-}
# Bucket names match those created by minio-init above.
OBJECT_STORAGE_BUCKET_VIDEOS: ${OBJECT_STORAGE_BUCKET_VIDEOS:-videos}
OBJECT_STORAGE_BUCKET_DOCUMENTS: ${OBJECT_STORAGE_BUCKET_DOCUMENTS:-documents}
OBJECT_STORAGE_BUCKET_MEDIA: ${OBJECT_STORAGE_BUCKET_MEDIA:-media}
# Email (worker sends invites, notifications, etc.).
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_SENDER: ${SMTP_SENDER:-}
# Transcription + OpenAI-backed agent runs.
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
GOOGLE_API_KEY: ${GOOGLE_API_KEY:-}
restart: unless-stopped
volumes:
postgres-data:
redis-data:
minio-data: