Files
classroomio/docker-compose.images.yaml
T
Abdulmuizz Hamzat 2e402efa78 chore(ci): publish latest from main and retire the edge tag (#711)
* fix(docs): update versioning strategy and tag policies for Docker images

* fix(docs): clarify versioning strategy in environment configuration
fix(docker): update docker-push script to remove local latest tag handling

* fix(docs): clarify VERSION variable usage in Docker push script
2026-07-07 12:59:33 +01:00

225 lines
9.0 KiB
YAML

# ClassroomIO — pull-based stack (pre-built images, no local build).
#
# This is the same topology as docker-compose.yaml but the app services pull
# published images from Docker Hub instead of building from source. You only need
# THIS file + a .env — no repository checkout, no 8 GB dashboard build.
#
# CIO_VERSION=1.4.2 docker compose -f docker-compose.images.yaml --env-file .env pull
# CIO_VERSION=1.4.2 docker compose -f docker-compose.images.yaml --env-file .env up -d
#
# CIO_VERSION pins the release. Use a specific version (e.g. 1.4.2) in production;
# "latest" = freshest main build (moves on every merge — rolling, may include unreleased code).
#
# KEEP IN SYNC with docker-compose.yaml: the api/dashboard/jobs `environment`,
# healthchecks, depends_on, and volumes must match the build-based file. The only
# intended difference is build: -> image:.
services:
postgres:
image: postgres:16-alpine
container_name: cio-postgres
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
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:
image: classroomio/api:${CIO_VERSION:-latest}
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. Set strong values in .env (the pull path has no script to
# generate them); the stack refuses to start otherwise.
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:
image: classroomio/dashboard:${CIO_VERSION:-latest}
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:
image: classroomio/jobs:${CIO_VERSION:-latest}
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:-}
restart: unless-stopped
volumes:
postgres-data:
redis-data:
minio-data: