2026-06-23 10:17:09 +01:00
|
|
|
# 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" = newest stable release, "edge" = bleeding edge (last main build).
|
|
|
|
|
#
|
|
|
|
|
# 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:.
|
2024-09-18 04:26:24 +01:00
|
|
|
services:
|
2026-03-09 11:18:22 +01:00
|
|
|
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
|
|
|
|
|
|
2025-09-21 19:35:41 +01:00
|
|
|
api:
|
2026-06-23 10:17:09 +01:00
|
|
|
image: classroomio/api:${CIO_VERSION:-latest}
|
2026-03-09 11:18:22 +01:00
|
|
|
container_name: cio-api
|
2025-05-20 01:37:58 +01:00
|
|
|
ports:
|
2025-10-11 13:27:50 +01:00
|
|
|
- "3081:3081"
|
2026-03-09 11:18:22 +01:00
|
|
|
depends_on:
|
|
|
|
|
postgres:
|
|
|
|
|
condition: service_healthy
|
|
|
|
|
redis:
|
|
|
|
|
condition: service_healthy
|
2026-06-23 10:17:09 +01:00
|
|
|
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
|
2024-09-18 04:26:24 +01:00
|
|
|
environment:
|
2026-03-09 11:18:22 +01:00
|
|
|
NODE_ENV: production
|
|
|
|
|
PORT: 3081
|
|
|
|
|
PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:-http://localhost:3081}
|
2026-06-07 18:43:05 +01:00
|
|
|
TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-}
|
2026-03-09 11:18:22 +01:00
|
|
|
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:-}
|
2026-06-23 10:17:09 +01:00
|
|
|
# Object storage (S3-compatible). Works with the bundled MinIO, AWS S3, or Cloudflare R2
|
|
|
|
|
# — for R2, point OBJECT_STORAGE_ENDPOINT at your R2 endpoint.
|
2026-03-09 11:18:22 +01:00
|
|
|
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:-}
|
2026-06-23 10:17:09 +01:00
|
|
|
# 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}
|
2026-03-11 17:00:44 +01:00
|
|
|
DASHBOARD_ORIGIN: ${DASHBOARD_ORIGIN:-}
|
2026-03-09 11:18:22 +01:00
|
|
|
LICENSE_KEY: ${LICENSE_KEY:-}
|
|
|
|
|
UNSPLASH_API_KEY: ${UNSPLASH_API_KEY:-}
|
2026-06-23 10:17:09 +01:00
|
|
|
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set a strong BETTER_AUTH_SECRET in .env}
|
2026-03-18 23:39:12 +01:00
|
|
|
SKIP_DB_SETUP: ${SKIP_DB_SETUP:-}
|
2026-03-09 11:18:22 +01:00
|
|
|
restart: unless-stopped
|
2024-09-18 04:26:24 +01:00
|
|
|
|
|
|
|
|
dashboard:
|
2026-06-23 10:17:09 +01:00
|
|
|
image: classroomio/dashboard:${CIO_VERSION:-latest}
|
2026-03-09 11:18:22 +01:00
|
|
|
container_name: cio-dashboard
|
2025-05-20 01:37:58 +01:00
|
|
|
ports:
|
2025-10-11 13:27:50 +01:00
|
|
|
- "3082:3082"
|
2024-09-18 04:26:24 +01:00
|
|
|
depends_on:
|
2026-03-09 11:18:22 +01:00
|
|
|
api:
|
2026-06-23 10:17:09 +01:00
|
|
|
condition: service_healthy
|
2026-03-09 11:18:22 +01:00
|
|
|
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}
|
2026-06-23 10:17:09 +01:00
|
|
|
PRIVATE_SERVER_KEY: ${PRIVATE_SERVER_KEY:?set a strong PRIVATE_SERVER_KEY in .env}
|
2026-03-09 11:18:22 +01:00
|
|
|
PRIVATE_APP_HOST: ${PRIVATE_APP_HOST:-localhost}
|
|
|
|
|
PRIVATE_APP_SUBDOMAINS: ${PRIVATE_APP_SUBDOMAINS:-app}
|
2026-03-18 23:39:12 +01:00
|
|
|
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:-}
|
2026-03-09 11:18:22 +01:00
|
|
|
restart: unless-stopped
|
|
|
|
|
|
2026-06-23 10:17:09 +01:00
|
|
|
# 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
|
|
|
|
|
|
2026-03-09 11:18:22 +01:00
|
|
|
volumes:
|
|
|
|
|
postgres-data:
|
|
|
|
|
redis-data:
|
|
|
|
|
minio-data:
|