Files
classroomio/docker/docs/SELF_HOST.md
T
Rotimi Best d5b07d356e feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)
New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
  transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
  run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
  classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows

Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
  services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
  context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
2026-06-07 18:43:05 +01:00

6.3 KiB

ClassroomIO Docker Self-Hosting

Prerequisites

  • Docker Desktop installed and running
  • git installed

Quick Start

git clone https://github.com/classroomio/classroomio.git
cd classroomio
cp .env.example .env
# Edit .env — set your dashboard domain and secrets
./run-docker-full-stack.sh

The script builds and starts: postgres, redis, minio (object storage), db-init (schema setup), api (localhost:3081), and dashboard (localhost:3082).

It auto-generates secure values for AUTH_BEARER_TOKEN and PRIVATE_SERVER_KEY when they are missing.

Environment Variables

All Docker services read from a single root .env file. See .env.example for the full annotated template with required/optional grouping.

Key points:

  • Required: BETTER_AUTH_SECRET, PUBLIC_IS_SELFHOSTED=true, DASHBOARD_ORIGIN.
  • API routing: Browser dashboard calls go to the dashboard origin and are proxied to PRIVATE_SERVER_URL (default: http://api:3081). You do not need API/dashboard cookie-domain matching for normal dashboard auth.
  • Direct API access (optional): Set PUBLIC_SERVER_URL and TRUSTED_ORIGINS only if browsers or third-party clients need to call the API origin directly.
  • CSP (runtime): ALLOWED_EXTERNAL_DOMAINS (overrides all) or per-directive: CSP_SCRIPT_SRC_DOMAINS, CSP_STYLE_SRC_DOMAINS, CSP_CONNECT_SRC_DOMAINS, CSP_FRAME_SRC_DOMAINS, CSP_FONT_SRC_DOMAINS, CSP_MEDIA_SRC_DOMAINS. These are read at container startup — no image rebuild needed. The API does not need to be added for normal dashboard calls.
  • Auth cookies: No cookie-domain env is needed. The dashboard proxy makes auth first-party and Better Auth sets host-only dashboard cookies.
  • Auto-generated: AUTH_BEARER_TOKEN, PRIVATE_SERVER_KEY (by ./run-docker-full-stack.sh).
  • Auto-configured: All MINIO_* / OBJECT_STORAGE_* vars (by the startup script).
  • Optional: Email (SMTP or Zoho), Google OAuth, Unsplash, LICENSE_KEY (enterprise).

PRIVATE_SERVER_KEY must be the same value in both API and dashboard — the script ensures this.

Verify

docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps
curl -sS http://localhost:3081/    # API — returns JSON
curl -I  http://localhost:3082/    # Dashboard — returns 200

Seed Demo Data

docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml \
  run --rm db-init sh -c "pnpm --filter @cio/db db:setup -- --seed"

Command Reference

All commands use this prefix — abbreviated as dc below:

dc = docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml
Task Command
Full stack (with MinIO) ./run-docker-full-stack.sh
Skip image rebuild ./run-docker-full-stack.sh --no-build
Exclude MinIO ./run-docker-full-stack.sh --no-minio
API-only smoke test dc up --build -d postgres redis db-init api
Service status dc ps
Stream logs dc logs -f api dashboard
Rebuild one service dc up -d --build api
Restart (env change, no rebuild) dc restart api dashboard
Stop dc down
Stop + delete volumes dc down -v

Build Images Locally

docker build -f docker/Dockerfile.api -t classroomio/api:latest .
docker build -f docker/Dockerfile.dashboard -t classroomio/dashboard:latest .

Inspect Container Env Vars

docker exec cio-api env | sort
docker exec cio-dashboard env | sort

Container names: cio-postgres, cio-redis, cio-api, cio-dashboard, cio-minio.

MinIO (Object Storage)

MinIO is included by default. The startup script auto-configures env vars, starts MinIO, and creates videos, documents, and media buckets.

  • API endpoint: http://localhost:9000
  • Web UI: http://localhost:9001 (default: minioadmin / minioadmin)

To start MinIO manually (e.g. after --no-minio):

docker compose -f docker/docker-compose.yaml --profile minio up -d

Security: Change MINIO_ROOT_USER / MINIO_ROOT_PASSWORD in .env for production. Use the same values for OBJECT_STORAGE_ACCESS_KEY_ID / OBJECT_STORAGE_SECRET_ACCESS_KEY.

MinIO Web UI Not Loading

If port 9001 does not respond, recreate the container:

docker rm -f cio-minio
docker compose -f docker/docker-compose.yaml --profile minio up -d

Data is preserved in the minio-data volume.

Rebuild After Code Changes

This setup builds images from source (no live hot reload). Code changes require a rebuild:

# One service
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api

# Both (e.g. shared packages/* changed)
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api dashboard

# Full stack
./run-docker-full-stack.sh

Troubleshooting

"No space left on device"

df -h                          # check host disk
docker system df               # check Docker usage
docker builder prune -f        # free build cache
docker image prune -a -f       # free unused images
docker container prune -f      # free stopped containers

Warning: docker volume prune removes volumes not used by any container. If run after docker compose ... down, it deletes postgres-data, redis-data, and minio-data. Use only the prunes above to free space safely.

Then restart: ./run-docker-full-stack.sh

"The database system is in recovery mode" (57P03)

Postgres has not finished starting. Wait 30-60 seconds and retry. The compose healthcheck requires a successful SELECT 1 before dependent services start.

MinIO "Storage reached its minimum free drive threshold"

Same root cause as disk full. Free space using the steps above and restart MinIO:

docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart minio

SMTP Errors in Logs

ECONNREFUSED 127.0.0.1:465 means SMTP is not configured. This does not block startup — configure SMTP or Zoho when you need email.

Port Conflicts

lsof -nP -iTCP:3081 -sTCP:LISTEN
lsof -nP -iTCP:3082 -sTCP:LISTEN