From a98d6a35fa81cfeef06855582592d7144a1e9cff Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Wed, 11 Mar 2026 10:53:03 +0100 Subject: [PATCH] docs: selfhostin improvements --- .env.example | 101 ++++ README.md | 33 +- .../course/components/lesson/note/note.svelte | 3 - apps/docs/content/docs/enterprise-sso.mdx | 1 - .../content/docs/quickstart/self-hosting.mdx | 532 ++++++------------ docker/.env.prod.example | 56 -- docker/docker-compose.prod.yaml | 136 ----- docker/docs/PUBLISHING_IMAGES.md | 33 +- docker/docs/SELF_HOST.md | 317 ++++------- docker/docs/USAGE.md | 247 -------- docker/docs/commands.md | 177 ------ 11 files changed, 410 insertions(+), 1226 deletions(-) create mode 100644 .env.example delete mode 100644 docker/.env.prod.example delete mode 100644 docker/docker-compose.prod.yaml delete mode 100644 docker/docs/USAGE.md delete mode 100644 docker/docs/commands.md diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0c785543 --- /dev/null +++ b/.env.example @@ -0,0 +1,101 @@ +# ClassroomIO Docker Self-Hosting Environment +# Copy this file to .env and fill in your values. +# For local development, the defaults work out of the box. +# For production, replace all placeholder values. + +# ────────────────────────────────────────────── +# Required +# ────────────────────────────────────────────── + +# Database (used by docker-compose to create the Postgres container) +POSTGRES_DB=classroomio +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres + +# Public URL where the API is reachable from browsers +# Local: http://localhost:3081 | Production: https://api.yourdomain.com +PUBLIC_SERVER_URL=http://localhost:3081 + +# Comma-separated origins allowed by CORS +# Local: http://localhost:3082 | Production: https://app.yourdomain.com +TRUSTED_ORIGINS=http://localhost:3082,http://localhost:5173 + +# Auth secret for session signing (use a long random string in production) +BETTER_AUTH_SECRET=replace-with-a-long-random-secret + +# API bearer token (leave blank — auto-generated by ./run-docker-full-stack.sh) +AUTH_BEARER_TOKEN= + +# Dashboard-to-API auth key (leave blank — auto-generated by ./run-docker-full-stack.sh) +# Must be the same value in both API and dashboard. +PRIVATE_SERVER_KEY= + +# Self-hosted mode flag +PUBLIC_IS_SELFHOSTED=true + +# Dashboard SSR calls to the API inside the Docker network +# Local: http://api:3081 | Production: http://api:3081 (always internal) +PRIVATE_SERVER_URL=http://api:3081 + +# Dashboard origin for SvelteKit CSRF protection +# Local: http://localhost:3082 | Production: https://app.yourdomain.com +DASHBOARD_ORIGIN=http://localhost:3082 + +# CSP: external domains allowed by Content-Security-Policy (required when self-hosted) +# Comma-separated. Include your CDN, API, video, and font domains. +ALLOWED_EXTERNAL_DOMAINS= + +# ────────────────────────────────────────────── +# Optional — Email (pick one approach, or skip) +# ────────────────────────────────────────────── + +# Option A: Zoho / ZeptoMail +ZOHO_TOKEN= + +# Option B: SMTP +SMTP_HOST= +SMTP_PORT= +SMTP_USER= +SMTP_PASSWORD= +SMTP_SENDER= + +# ────────────────────────────────────────────── +# Optional — Object Storage (auto-configured by ./run-docker-full-stack.sh) +# ────────────────────────────────────────────── + +# MinIO is included by default. The startup script sets these automatically. +# Change credentials in production. +MINIO_ROOT_USER=minioadmin +MINIO_ROOT_PASSWORD=minioadmin +OBJECT_STORAGE_ENDPOINT=http://minio:9000 +OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 +OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin +OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin +OBJECT_STORAGE_FORCE_PATH_STYLE=true +OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media + +# ────────────────────────────────────────────── +# Optional — Integrations +# ────────────────────────────────────────────── + +# Google OAuth +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# Cookie domain for cross-subdomain auth (e.g. .yourdomain.com) +AUTH_COOKIE_DOMAIN= + +# Dashboard domain routing +PRIVATE_APP_HOST=localhost +PRIVATE_APP_SUBDOMAINS=app + +# Unsplash image search +UNSPLASH_API_KEY= + +# Enterprise license (SSO, token-auth, no-tracking) +LICENSE_KEY= + +# Custom branding (meta tags) +# PUBLIC_APP_TITLE=My Learning Platform +# PUBLIC_APP_DESCRIPTION=A flexible LMS +# PUBLIC_OG_IMAGE_URL=https://app.yourdomain.com/og-image.png diff --git a/README.md b/README.md index d381455d..d37137df 100644 --- a/README.md +++ b/README.md @@ -160,38 +160,11 @@ The repository also contains shared packages under `packages/` (for example `pac ### Docker Compose (Full Stack) -To run the Dockerized stack (including API + dashboard): - ```bash +cp .env.example .env # copy env template, edit for your domain ./run-docker-full-stack.sh ``` -Start without rebuilding images: +The script reads root `.env` via `docker compose --env-file .env` and auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when missing. -```bash -./run-docker-full-stack.sh --no-build -``` - -The script always reads root `.env` via `docker compose --env-file .env`, and it auto-generates secure local values for `PRIVATE_SERVER_KEY` (and `AUTH_BEARER_TOKEN`) when they are missing/insecure. API key middleware validation for dashboard server-side calls uses `PRIVATE_SERVER_KEY`. - -For production deployments from published images, use `docker/docker-compose.prod.yaml` with `docker/.env.prod.example` as your template. - -Enterprise-only self-hosted features (SSO, token-auth, no-tracking) require a license key from us. Set `LICENSE_KEY` in: -- `apps/api/.env` for local non-Docker development -- root `.env` for Docker Compose self-hosting -- `docker/.env.prod` for published-image deployments - -If you do not use Enterprise-only features, you do not need `LICENSE_KEY`. - -URL routing for dashboard API calls: - -- Browser requests use `PUBLIC_SERVER_URL` (must be your public API domain in hosted environments, not `localhost`). -- Server-side dashboard requests (SSR/load functions) use `PRIVATE_SERVER_URL` when set, and fallback to `PUBLIC_SERVER_URL`. -- In Docker Compose, use `PRIVATE_SERVER_URL=http://api:3081` so dashboard SSR calls stay on the internal Docker network. -- Set `AUTH_COOKIE_DOMAIN` for Better Auth cookie domain control (`your-domain.com` for shared subdomains, or your exact API host). - -For Docker details and troubleshooting, see: - -- `docker/docs/USAGE.md` -- `docker/docs/commands.md` -- `docker/docs/PUBLISHING_IMAGES.md` +See [`.env.example`](.env.example) for the full list of environment variables with required/optional grouping, and [`docker/docs/SELF_HOST.md`](docker/docs/SELF_HOST.md) for the complete Docker self-hosting guide. diff --git a/apps/dashboard/src/lib/features/course/components/lesson/note/note.svelte b/apps/dashboard/src/lib/features/course/components/lesson/note/note.svelte index b6027f8c..c6763d52 100644 --- a/apps/dashboard/src/lib/features/course/components/lesson/note/note.svelte +++ b/apps/dashboard/src/lib/features/course/components/lesson/note/note.svelte @@ -47,9 +47,6 @@ $effect(() => { console.log('content', content); }); - $effect(() => { - console.log('lessonApi.currentLocale', lessonApi.currentLocale); - }); {#if mode === MODES.edit} diff --git a/apps/docs/content/docs/enterprise-sso.mdx b/apps/docs/content/docs/enterprise-sso.mdx index 5ac7ca79..cc8d3254 100644 --- a/apps/docs/content/docs/enterprise-sso.mdx +++ b/apps/docs/content/docs/enterprise-sso.mdx @@ -22,7 +22,6 @@ ClassroomIO supports Single Sign-On (SSO) via OpenID Connect (OIDC), allowing yo Set `LICENSE_KEY` in the API service environment. Local development: `apps/api/.env`. Docker Compose: repository root `.env`. - Published-image Docker deploys: `docker/.env.prod`. If you do not use Enterprise-only features, leave `LICENSE_KEY` unset. diff --git a/apps/docs/content/docs/quickstart/self-hosting.mdx b/apps/docs/content/docs/quickstart/self-hosting.mdx index 7b00c48a..11c066e6 100644 --- a/apps/docs/content/docs/quickstart/self-hosting.mdx +++ b/apps/docs/content/docs/quickstart/self-hosting.mdx @@ -4,288 +4,19 @@ description: Guide to self-hosting ClassroomIO on your own servers. --- import { Callout } from 'fumadocs-ui/components/callout'; -import { Step, Steps } from 'fumadocs-ui/components/steps'; -This guide will help you quickly self-host ClassroomIO. +This guide covers two deployment paths: **Docker** (recommended) and **Railway**. ![Selfhost ClassroomIO](/selfhost-classroomio.webp) -ClassroomIO is made up of the following applications: +## Docker (Recommended) -- [apps/api](https://github.com/classroomio/classroomio/tree/main/apps/api) - an Express server -- [apps/dashboard](https://github.com/classroomio/classroomio/tree/main/apps/dashboard) - the admin dashboard + LMS -- [packages/db](https://github.com/classroomio/classroomio/tree/main/packages/db) - shared database layer for PostgreSQL +### Prerequisites -The quickest and easiest way to deploy both the api and dashboard is by using our [Railway Template](https://railway.app/template/dl9RG7?referralCode=8OyIj5). However there are a couple of things that you'd need first before heading over to the template, which are: +- Docker Desktop installed and running +- `git` installed -1. SMTP values - For sending emails on the api -2. PostgreSQL + Redis - For the API and database layer -3. Object storage (MinIO for Docker self-hosting, or Cloudflare R2 for hosted) - Video, document, and image uploads - -## SMTP Setup - -You need a custom SMTP configuration for email sending. Most importantly you need these values: - -```zsh title="env" -SMTP_HOST= -SMTP_PORT= -SMTP_USER= -SMTP_PASSWORD= -SMTP_SENDER= -``` - -Make sure you have them handy because you'd need them while we go through this self-hosting guide. - - - Learn more about setting up a custom SMTP server - [here](https://nodemailer.com/smtp/) - - -## PostgreSQL + Redis - -ClassroomIO now runs directly on PostgreSQL and Redis (no Supabase dependency in Docker setup). - -For deployments, keep these connection variables ready: - -```zsh title="env" -DATABASE_URL= -PRIVATE_DATABASE_URL= -REDIS_URL= -``` - -## Object Storage (Videos, Documents, Images) - -ClassroomIO supports two object storage backends for video uploads, document uploads, and media (images/thumbnails): - -### Option A: MinIO (S3-compatible, default for Docker self-hosting) - -For Docker self-hosting, MinIO is included by default. Use MinIO or any S3-compatible storage for on-premise deployments: - -```zsh title="env" -OBJECT_STORAGE_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media -``` - -**Docker:** MinIO is included by default when you run `./run-docker-full-stack.sh`. The script auto-configures these. Add to your `.env`: - -```zsh title="env" -# MinIO container credentials (must match OBJECT_STORAGE_* below) -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin - -# When API runs in Docker: use minio (Docker service name), not localhost -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media -``` - - -When the API runs inside Docker, set `OBJECT_STORAGE_ENDPOINT=http://minio:9000`. From a container, `localhost` refers to the container itself; use the service name `minio` to reach MinIO on the Docker network. When the API runs locally (e.g. `pnpm api:dev`), use `OBJECT_STORAGE_ENDPOINT=http://localhost:9000`. - - -When you run `./run-docker-full-stack.sh`, MinIO starts automatically. To start MinIO manually (e.g. if you used `--no-minio`): - -```zsh title="Terminal" -docker compose -f docker/docker-compose.yaml --profile minio up -d -``` - -**Access URLs:** - -| Purpose | URL | -|---------|-----| -| API | http://localhost:9000 | -| Web UI | http://localhost:9001 (login: `minioadmin` / `minioadmin`) | - - -Do not use `minioadmin` / `minioadmin` in production. Set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` in `.env`, and use the same values for `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY`. - - - -MinIO uses a random port for the Web Console by default. If the Web UI at http://localhost:9001 does not load, remove the old container and recreate it: `docker rm -f cio-minio` then run the MinIO start command again. See [docker/docs/USAGE.md](https://github.com/classroomio/classroomio/blob/main/docker/docs/USAGE.md#minio-troubleshooting) for full troubleshooting. - - - - With Docker, MinIO is included by default. Use `./run-docker-full-stack.sh --no-minio` to exclude it if you don't need video/document uploads. - - -### Option B: Cloudflare R2 (hosted deployments) - -For hosted deployments (e.g. Railway), Cloudflare R2 can be used. Configure these env vars: - -```zsh title="env" -CLOUDFLARE_ACCOUNT_ID= -CLOUDFLARE_ACCESS_KEY= -CLOUDFLARE_SECRET_ACCESS_KEY= -CLOUDFLARE_BUCKET_DOMAIN= -CLOUDFLARE_IMAGE_BUCKET_DOMAIN= -``` - -See the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2 bucket and CORS configuration. - -## SSO Configuration - - - SSO is an Enterprise-only feature for self-hosted instances. - If you do not use Enterprise-only features, you do not need a license key. - If you need Enterprise features, request a key from us and set `LICENSE_KEY` in your API environment. - [Book a call](http://cal.com/classroomio/enterprise) to get your license. - - -For Enterprise SSO support, you need to configure the following: - -1. **License Key**: Set `LICENSE_KEY` in your API environment: - ```zsh - LICENSE_KEY="your-issued-license-key" - ``` - -2. **Trusted Origins**: Add your IdP domains to `TRUSTED_ORIGINS`: - ```zsh - TRUSTED_ORIGINS="https://app.classroomio.com,https://your-org.okta.com,https://accounts.google.com" - ``` - -3. **Callback URLs**: Ensure your `PUBLIC_SERVER_URL` is correctly set. SSO callbacks will be: - - `{PUBLIC_SERVER_URL}/api/auth/sso/callback/{providerId}` - -4. **License Check**: The system will verify your Enterprise license before allowing SSO configuration. Without a valid license, the SSO settings page will show an upgrade prompt. - -## Deploy on Railway - -Now you have everything you need and you are ready to deploy the api and dashboard. - -Go ahead and click this button to get started. - -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dl9RG7?referralCode=8OyIj5) - -### Environment Variables - -#### cio-api - -```zsh title="env" -# Required -DATABASE_URL="" -REDIS_URL="" -PUBLIC_SERVER_URL="" -TRUSTED_ORIGINS="" -BETTER_AUTH_SECRET="" -AUTH_BEARER_TOKEN="" -AUTH_COOKIE_DOMAIN="" -PRIVATE_SERVER_KEY="" -SMTP_HOST="" -SMTP_PORT="" -SMTP_USER="" -SMTP_SENDER="" -SMTP_PASSWORD="" - -# Optional - MinIO (S3-compatible, for Docker / on-premise self-hosting) -# When API runs in Docker: use minio service name. For local API: use http://localhost:9000 -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media - -# Optional - Cloudflare R2 (for hosted deployments) -CLOUDFLARE_BUCKET_ID="" -CLOUDFLARE_ACCESS_KEY="" -CLOUDFLARE_ACCOUNT_ID="" -CLOUDFLARE_BUCKET_DOMAIN="" -CLOUDFLARE_SECRET_ACCESS_KEY="" -SENTRY_DNS="" - -# Enterprise (Optional) -# Required only when using Enterprise-only features (SSO, token-auth, no-tracking) -LICENSE_KEY="" - -# SSO (Optional) -# For enterprise SSO with SAML (future support) -# SSO_ENCRYPTION_KEY="your-32-character-secret-key" -``` - - - Learn more about the environment variables - [here](https://github.com/classroomio/classroomio/blob/main/apps/api/.env.example) - - -#### cio-dashboard - -```zsh title="env" -# Required -PUBLIC_SERVER_URL="" -PRIVATE_SERVER_KEY="" -PUBLIC_IS_SELFHOSTED="true" - -# Required when self-hosting: CSP external domains (no defaults in self-hosted mode) -# Comma-separated list of domains allowed in Content-Security-Policy. -# Example: ALLOWED_EXTERNAL_DOMAINS=assets.cdn.clsrio.com,cdnjs.cloudflare.com,cdn.plyr.io,https://*.classroomio.com,https://api.classroomio.com,https://pgrest.classroomio.com,wss://*.classroomio.com -ALLOWED_EXTERNAL_DOMAINS="" - -# Optional - per-directive override (script, style, connect, frame, font) -# CSP_SCRIPT_SRC_DOMAINS="" -# CSP_CONNECT_SRC_DOMAINS="" - -# Optional -PRIVATE_SERVER_URL="" -OPENAI_API_KEY="" -UNSPLASH_API_KEY="" -PRIVATE_APP_HOST="" -PRIVATE_APP_SUBDOMAINS="" - -# Optional - meta tags for custom branding (title, description, OG image) -# PUBLIC_APP_TITLE="My Learning Platform" -# PUBLIC_APP_DESCRIPTION="A flexible LMS for your organization" -# PUBLIC_OG_IMAGE_URL="https://app.yourdomain.com/og-image.png" -``` - - - Set `PRIVATE_SERVER_KEY` in both API and dashboard environments for server-to-server API key authentication. `AUTH_BEARER_TOKEN` is a separate token and is not used by `apiKeyMiddleware`. - - - - When `PUBLIC_IS_SELFHOSTED="true"`, the dashboard uses **only** environment variables for CSP external domains—no defaults. You must set `ALLOWED_EXTERNAL_DOMAINS` with a comma-separated list of domains your instance needs (CDN, analytics, video providers, etc.). This supports strict security policies, data residency, and CISO allowlists. - - - - Dashboard URL routing is environment-aware: - browser requests use `PUBLIC_SERVER_URL`, while server-side dashboard requests use - `PRIVATE_SERVER_URL` (fallback: `PUBLIC_SERVER_URL`). In hosted deployments, set - `PUBLIC_SERVER_URL` to your public API domain (not `localhost`). - - - - Set `AUTH_COOKIE_DOMAIN` to `.your-domain.com` for cross-subdomain cookies. If you need host-only cookies, set it - to your exact API host (for example `api.your-domain.com`). - - - - When self-hosting, page title, description, and Open Graph image default to your organization's branding (name, avatar, landing page banner). Set `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, and `PUBLIC_OG_IMAGE_URL` to override with custom values. If unset, a bundled fallback image is used so the dashboard never depends on external CDNs. - - - - Learn more about the environment variables - [here](https://github.com/classroomio/classroomio/blob/main/apps/dashboard/.env.example) - - -## Using Docker - -We provide `docker/docker-compose.yaml` for local self-hosting. - -The stack includes: - -- `postgres` (database) -- `redis` (cache/rate-limit store) -- `db-init` (one-off DB setup) -- `api` (backend on `3081`) -- `dashboard` (frontend on `3082`) +### Quick Start 1. Clone the repository: @@ -294,105 +25,214 @@ git clone https://github.com/classroomio/classroomio.git cd classroomio ``` -2. Start the full stack (includes MinIO for object storage by default): +2. Copy the env template and edit it: + +```zsh title="Terminal" +cp .env.example .env +``` + +Open `.env` and set `PUBLIC_SERVER_URL`, `TRUSTED_ORIGINS`, `BETTER_AUTH_SECRET`, and `ALLOWED_EXTERNAL_DOMAINS` for your domain. For local development the defaults work as-is. + +3. Start the full stack: ```zsh title="Terminal" ./run-docker-full-stack.sh ``` -3. Verify services: +The script builds and starts Postgres, Redis, MinIO (object storage), the API, and the dashboard. It auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when they are missing. + +4. Verify: ```zsh title="Terminal" -docker compose -p classroomio -f docker/docker-compose.yaml ps -curl -sS http://localhost:3081/ -curl -I http://localhost:3082/ +curl -sS http://localhost:3081/ # API — returns JSON +curl -I http://localhost:3082/ # Dashboard — returns 200 ``` -The services will be available at: - - Dashboard: http://localhost:3082 - API: http://localhost:3081 +- MinIO Console: http://localhost:9001 (default login: `minioadmin` / `minioadmin`) -### API-Only Smoke Test +### One `.env`, One Command -If you want to validate API startup first: +Docker self-hosting uses a single root `.env` file. Every `docker compose` command reads it via `--env-file .env` — you never need separate env files per service. See [`.env.example`](https://github.com/classroomio/classroomio/blob/main/.env.example) for the full template. + +### Environment Variables + +#### Required + +| Variable | Description | Local Default | +|----------|-------------|---------------| +| `POSTGRES_DB` | Database name | `classroomio` | +| `POSTGRES_USER` | Database user | `postgres` | +| `POSTGRES_PASSWORD` | Database password | `postgres` | +| `PUBLIC_SERVER_URL` | Public API URL (browser requests) | `http://localhost:3081` | +| `TRUSTED_ORIGINS` | Comma-separated CORS origins | `http://localhost:3082` | +| `BETTER_AUTH_SECRET` | Auth session signing secret | — | +| `AUTH_BEARER_TOKEN` | API bearer token | Auto-generated | +| `PRIVATE_SERVER_KEY` | Dashboard-to-API auth key (same in both) | Auto-generated | +| `PUBLIC_IS_SELFHOSTED` | Self-hosted mode flag | `true` | +| `PRIVATE_SERVER_URL` | Internal API URL for SSR (Docker network) | `http://api:3081` | +| `DASHBOARD_ORIGIN` | Dashboard URL for CSRF protection | `http://localhost:3082` | +| `ALLOWED_EXTERNAL_DOMAINS` | CSP allowed domains (comma-separated) | — | + + + When `PUBLIC_IS_SELFHOSTED=true`, the dashboard has **no default** CSP domains. You must set `ALLOWED_EXTERNAL_DOMAINS` with your CDN, API, video, and font domains. + + +#### Optional — Email + +Configure either SMTP or Zoho. Without email config, the app works but cannot send emails. + +| Variable | Description | +|----------|-------------| +| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_SENDER` | SMTP server | +| `ZOHO_TOKEN` | Zoho/ZeptoMail token (alternative to SMTP) | + +#### Optional — Object Storage + +MinIO is included by default. The startup script auto-configures these — you only need to change them if using custom credentials or an external S3-compatible service. + +| Variable | Default | +|----------|---------| +| `MINIO_ROOT_USER` | `minioadmin` | +| `MINIO_ROOT_PASSWORD` | `minioadmin` | +| `OBJECT_STORAGE_ENDPOINT` | `http://minio:9000` | +| `OBJECT_STORAGE_PUBLIC_ENDPOINT` | `http://localhost:9000` | +| `OBJECT_STORAGE_ACCESS_KEY_ID` | `minioadmin` | +| `OBJECT_STORAGE_SECRET_ACCESS_KEY` | `minioadmin` | +| `OBJECT_STORAGE_FORCE_PATH_STYLE` | `true` | +| `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL` | `http://localhost:9000/media` | + +Use `./run-docker-full-stack.sh --no-minio` to exclude MinIO if you do not need uploads. + + + Do not use `minioadmin` / `minioadmin` in production. + + +#### Optional — Integrations + +| Variable | Description | +|----------|-------------| +| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Google OAuth | +| `AUTH_COOKIE_DOMAIN` | Cross-subdomain cookies (e.g. `.yourdomain.com`) | +| `PRIVATE_APP_HOST`, `PRIVATE_APP_SUBDOMAINS` | Multi-subdomain routing | +| `UNSPLASH_API_KEY` | Unsplash image search | +| `LICENSE_KEY` | Enterprise features (SSO, token-auth, no-tracking) | +| `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, `PUBLIC_OG_IMAGE_URL` | Custom branding | + +### Common Commands ```zsh title="Terminal" -docker compose -p classroomio -f docker/docker-compose.yaml up --build -d postgres redis db-init api -docker compose -p classroomio -f docker/docker-compose.yaml logs --tail=100 api -curl -sS http://localhost:3081/ +# Start full stack (builds images, starts all services including MinIO) +./run-docker-full-stack.sh + +# Start without rebuilding images +./run-docker-full-stack.sh --no-build + +# Start without MinIO (no file uploads) +./run-docker-full-stack.sh --no-minio + +# Service status +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps + +# Stream logs +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs -f api dashboard + +# Rebuild one service +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api + +# Restart after env change (no rebuild needed) +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart api dashboard + +# Run DB seed +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" + +# Stop +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down + +# Stop and delete all data (database, redis, minio volumes) +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down -v ``` -### Environment Variables for Docker +See [docker/docs/SELF_HOST.md](https://github.com/classroomio/classroomio/blob/main/docker/docs/SELF_HOST.md) for the full Docker reference including troubleshooting. -Local defaults are provided in compose, but set these for real deployments: +### Troubleshooting: "No space left on device" + +```zsh title="Terminal" +docker system df # check usage +docker builder prune -f # free build cache +docker image prune -a -f # free unused images +docker container prune -f # free stopped containers +``` + + + Avoid `docker volume prune` after `docker compose ... down` — it can remove your database and storage volumes. + + +Then restart: `./run-docker-full-stack.sh` + +--- + +## Railway + +The quickest hosted deployment is the [Railway Template](https://railway.app/template/dl9RG7?referralCode=8OyIj5). + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dl9RG7?referralCode=8OyIj5) + +### cio-api ```zsh title="env" -POSTGRES_DB=classroomio -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres +DATABASE_URL="${{db.DATABASE_URL}}" +REDIS_URL="${{Redis.REDIS_URL}}" +PUBLIC_SERVER_URL="https://api.yourdomain.com" +TRUSTED_ORIGINS="https://api.yourdomain.com,https://app.yourdomain.com" +BETTER_AUTH_SECRET="random-secure-key" +AUTH_BEARER_TOKEN="some-key" +AUTH_COOKIE_DOMAIN="yourdomain.com" +PRIVATE_SERVER_KEY="same-key-for-api-and-dashboard" -BETTER_AUTH_SECRET= -AUTH_BEARER_TOKEN= -PRIVATE_SERVER_KEY= -# Required only for Enterprise-only features (SSO, token-auth, no-tracking) -LICENSE_KEY= +# Email (configure one) +SMTP_HOST= +SMTP_PORT= +SMTP_USER= +SMTP_PASSWORD= +SMTP_SENDER= -PUBLIC_SERVER_URL= -PRIVATE_SERVER_URL= -TRUSTED_ORIGINS= -AUTH_COOKIE_DOMAIN= - -# MinIO (object storage, when using ./run-docker-full-stack.sh with MinIO) -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media +# Object storage — Cloudflare R2 (for hosted deployments) +CLOUDFLARE_ACCOUNT_ID= +CLOUDFLARE_ACCESS_KEY= +CLOUDFLARE_SECRET_ACCESS_KEY= +CLOUDFLARE_BUCKET_DOMAIN= +CLOUDFLARE_IMAGE_BUCKET_DOMAIN= ``` - - `run-docker-full-stack.sh` auto-generates secure values for `AUTH_BEARER_TOKEN` and - `PRIVATE_SERVER_KEY` when missing/insecure. API key middleware for dashboard server-side - calls validates `PRIVATE_SERVER_KEY`. Set `LICENSE_KEY` only if you need Enterprise-only features; otherwise leave it empty. +Optional: add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` for OAuth, `UNSPLASH_API_KEY` for image search, or `LICENSE_KEY` for enterprise features. + +### cio-dashboard + +```zsh title="env" +ALLOWED_EXTERNAL_DOMAINS="https://*.yourdomain.com,https://api.yourdomain.com,https://fonts.googleapis.com" +PRIVATE_APP_HOST="https://app.yourdomain.com" +PRIVATE_SERVER_KEY="same-key-for-api-and-dashboard" +PUBLIC_IS_SELFHOSTED="true" +PRIVATE_SERVER_URL="http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8080" +PUBLIC_SERVER_URL="https://api.yourdomain.com" +``` + +Optional: add `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, `PUBLIC_OG_IMAGE_URL` for custom branding. + + + `PRIVATE_SERVER_KEY` must be the same value in both API and dashboard. It authenticates server-side dashboard requests to the API. -### If PostgreSQL or Docker fails with "No space left on device" +### SMTP Setup -The error `could not write lock file "postmaster.pid": No space left on device` means the disk (or Docker storage) is full. Free space, then restart the stack. +You need SMTP for email sending. See [Nodemailer SMTP docs](https://nodemailer.com/smtp/) for provider setup. -**1. Check disk usage** +### Cloudflare R2 -```zsh title="Terminal" -df -h -docker system df -``` +For hosted deployments, Cloudflare R2 handles object storage. See the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide. -**2. Free Docker space** (safe — removes unused build cache and images) +### SSO (Enterprise) -```zsh title="Terminal" -docker builder prune -f -docker image prune -a -f -docker container prune -f -``` - -**3. If still low on space, remove unused volumes** (only if you do not need data in other projects’ volumes) - -```zsh title="Terminal" -docker volume prune -f -``` - - - `docker volume prune` removes volumes not used by any container. If you run it after `docker compose ... down`, it can remove Classroom.io’s `postgres-data`, `redis-data`, and `minio-data`, and you will lose database and object storage data. To free space without touching Classroom.io data, use only the prunes in step 2, or remove specific unused volumes by name. - - -**4. Restart the stack** - -```zsh title="Terminal" -./run-docker-full-stack.sh -``` - -If the host disk is still full after pruning, free space elsewhere (e.g. large files, old logs, other apps) or increase disk capacity. See [docker/docs/SELF_HOST.md](https://github.com/classroomio/classroomio/blob/main/docker/docs/SELF_HOST.md) for the full self-host runbook. +SSO requires a license key. Set `LICENSE_KEY` in your API environment and add your IdP domains to `TRUSTED_ORIGINS`. [Book a call](http://cal.com/classroomio/enterprise) to get a license. diff --git a/docker/.env.prod.example b/docker/.env.prod.example deleted file mode 100644 index 18773bda..00000000 --- a/docker/.env.prod.example +++ /dev/null @@ -1,56 +0,0 @@ -# Published image references (pin to explicit versions, avoid latest in production) -CIO_API_IMAGE=classroomio/api:v0.1.0 -CIO_DASHBOARD_IMAGE=classroomio/dashboard:v0.1.0 - -# Core services -POSTGRES_DB=classroomio -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres - -# API runtime -PUBLIC_SERVER_URL=https://api.domain.com -TRUSTED_ORIGINS=https://app.domain.com -# Optional: cookie domain for Better Auth cross-subdomain cookies. -# Use either .domain.com (shared across subdomains) or your API host. -AUTH_COOKIE_DOMAIN= -BETTER_AUTH_SECRET=replace-with-a-long-random-secret -AUTH_BEARER_TOKEN=replace-with-a-long-random-token - -# Dashboard runtime -DASHBOARD_ORIGIN=https://app.domain.com -# Browser requests use this URL. Use your public API domain, not localhost. -PUBLIC_SERVER_URL=https://api.domain.com -# Optional internal override for SSR calls in Docker network -PRIVATE_SERVER_URL=http://api:3081 -# Used by API key middleware for dashboard server-side API calls. -PRIVATE_SERVER_KEY=replace-with-a-long-random-token -PUBLIC_IS_SELFHOSTED=true -PRIVATE_APP_HOST=domain.com -PRIVATE_APP_SUBDOMAINS=app,launchweek - -# License (optional - for enterprise features: SSO, token-auth, no-tracking) -# Leave unset if you do not use Enterprise-only features. -# LICENSE_KEY=your-license-key - -# CSP: External domains (required when self-hosted—no defaults) -# Comma-separated list for Content-Security-Policy -ALLOWED_EXTERNAL_DOMAINS=https://*.classroomio.com,https://api.classroomio.com,https://pgrest.classroomio.com,wss://*.classroomio.com,https://assets.cdn.clsrio.com,https://cdn.plyr.io - -# Email (configure either Zoho token flow in app code or SMTP values) -ZOHO_TOKEN= -SMTP_HOST= -SMTP_PORT= -SMTP_USER= -SMTP_PASSWORD= -SMTP_SENDER= - -# Object storage - MinIO (S3-compatible, default for Docker self-hosting) -# When API runs in Docker: use minio service name. For local API: use http://localhost:9000 -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media diff --git a/docker/docker-compose.prod.yaml b/docker/docker-compose.prod.yaml deleted file mode 100644 index 9b55902b..00000000 --- a/docker/docker-compose.prod.yaml +++ /dev/null @@ -1,136 +0,0 @@ -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: - test: - [ - "CMD-SHELL", - "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-classroomio}", - ] - interval: 5s - timeout: 5s - retries: 10 - start_period: 5s - - 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 - - db-init: - image: ${CIO_API_IMAGE:-classroomio/api:latest} - pull_policy: always - container_name: cio-db-init - depends_on: - postgres: - condition: service_healthy - environment: - DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio} - PRIVATE_DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio} - command: - - sh - - -c - - pnpm --filter @cio/db db:setup - restart: "no" - - api: - image: ${CIO_API_IMAGE:-classroomio/api:latest} - pull_policy: always - container_name: cio-api - ports: - - "3081:3081" - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - db-init: - condition: service_completed_successfully - environment: - NODE_ENV: production - PORT: 3081 - PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:?PUBLIC_SERVER_URL is required} - TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:?TRUSTED_ORIGINS is required} - AUTH_COOKIE_DOMAIN: ${AUTH_COOKIE_DOMAIN:-} - DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-classroomio} - PRIVATE_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:-} - GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} - ZOHO_TOKEN: ${ZOHO_TOKEN:-} - SMTP_HOST: ${SMTP_HOST:-} - SMTP_USER: ${SMTP_USER:-} - SMTP_PASSWORD: ${SMTP_PASSWORD:-} - SMTP_PORT: ${SMTP_PORT:-} - SMTP_SENDER: ${SMTP_SENDER:-} - CLOUDFLARE_BUCKET_DOMAIN: ${CLOUDFLARE_BUCKET_DOMAIN:-} - CLOUDFLARE_ACCESS_KEY: ${CLOUDFLARE_ACCESS_KEY:-} - CLOUDFLARE_SECRET_ACCESS_KEY: ${CLOUDFLARE_SECRET_ACCESS_KEY:-} - CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID:-} - CLOUDFLARE_IMAGE_BUCKET_DOMAIN: ${CLOUDFLARE_IMAGE_BUCKET_DOMAIN:-} - CLOUDFLARE_RENDERING_API_KEY: ${CLOUDFLARE_RENDERING_API_KEY:-} - 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:-} - OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL: ${OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL:-} - AUTH_BEARER_TOKEN: ${AUTH_BEARER_TOKEN:?AUTH_BEARER_TOKEN is required} - PRIVATE_SERVER_KEY: ${PRIVATE_SERVER_KEY:?PRIVATE_SERVER_KEY is required} - LICENSE_KEY: ${LICENSE_KEY:-} - UNSPLASH_API_KEY: ${UNSPLASH_API_KEY:-} - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET is required} - restart: unless-stopped - - dashboard: - image: ${CIO_DASHBOARD_IMAGE:-classroomio/dashboard:latest} - build: - context: .. - dockerfile: docker/Dockerfile.dashboard - args: - PUBLIC_IS_SELFHOSTED: ${PUBLIC_IS_SELFHOSTED:-true} - PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:-} - 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:-} - pull_policy: always - container_name: cio-dashboard - ports: - - "3082:3082" - depends_on: - api: - condition: service_started - environment: - NODE_ENV: production - PORT: 3082 - ORIGIN: ${DASHBOARD_ORIGIN:?DASHBOARD_ORIGIN is required} - PUBLIC_IS_SELFHOSTED: ${PUBLIC_IS_SELFHOSTED:-true} - PUBLIC_SERVER_URL: ${PUBLIC_SERVER_URL:?PUBLIC_SERVER_URL is required} - PRIVATE_SERVER_URL: ${PRIVATE_SERVER_URL:-http://api:3081} - PRIVATE_SERVER_KEY: ${PRIVATE_SERVER_KEY:?PRIVATE_SERVER_KEY is required} - PRIVATE_APP_HOST: ${PRIVATE_APP_HOST:-localhost} - PRIVATE_APP_SUBDOMAINS: ${PRIVATE_APP_SUBDOMAINS:-app} - AUTH_BEARER_TOKEN: ${AUTH_BEARER_TOKEN:-} - restart: unless-stopped - -volumes: - postgres-data: - redis-data: diff --git a/docker/docs/PUBLISHING_IMAGES.md b/docker/docs/PUBLISHING_IMAGES.md index fa32aca4..523c892c 100644 --- a/docker/docs/PUBLISHING_IMAGES.md +++ b/docker/docs/PUBLISHING_IMAGES.md @@ -102,38 +102,7 @@ docker pull classroomio/dashboard:latest ### Deploy with Compose -Use the production compose file (`docker/docker-compose.prod.yaml`) with pinned image tags. - -1. Copy the production env template: - -```bash -cp docker/.env.prod.example docker/.env.prod -``` - -2. Set pinned images in `docker/.env.prod`: - -```bash -CIO_API_IMAGE=classroomio/api:v0.1.0 -CIO_DASHBOARD_IMAGE=classroomio/dashboard:v0.1.0 -``` - -3. Deploy: - -```bash -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml pull -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml up -d -``` - -The `db-init` service also uses `CIO_API_IMAGE` so schema setup always matches the API build you deploy. - -Important URL behavior for dashboard API calls: - -- Browser requests use `PUBLIC_SERVER_URL` (set this to your public API domain, not `localhost`). -- Server-side dashboard requests use `PRIVATE_SERVER_URL` when set, with fallback to `PUBLIC_SERVER_URL`. -- In Docker network deployments, set `PRIVATE_SERVER_URL=http://api:3081`. -- Set `AUTH_COOKIE_DOMAIN` to `your-domain.com` (shared subdomain cookies) or your exact API host if you need explicit Better Auth cookie domain control. - -For local source builds, keep using `docker/docker-compose.yaml`. +For self-hosting with Docker Compose, see [SELF_HOST.md](SELF_HOST.md). The setup uses a single root `.env` file and `docker/docker-compose.yaml`. ## Versioning Strategy diff --git a/docker/docs/SELF_HOST.md b/docker/docs/SELF_HOST.md index 6728f2da..9ec54831 100644 --- a/docker/docs/SELF_HOST.md +++ b/docker/docs/SELF_HOST.md @@ -1,249 +1,170 @@ -# ClassroomIO Docker Selfhosting Setup +# ClassroomIO Docker Self-Hosting -This guide is for someone setting up ClassroomIO with Docker for the first time. - -## 1. Prerequisites +## Prerequisites - Docker Desktop installed and running - `git` installed -- `curl` installed (recommended for health checks) -## 2. Clone the Repo +## Quick Start ```bash -git clone --branch feat/course-v2 https://github.com/classroomio/classroomio.git +git clone https://github.com/classroomio/classroomio.git cd classroomio -``` - -## 3. Create Root `.env` for Docker Compose - -Create `./.env` in the repository root: - -```bash -cat > .env <<'EOF' -# Database -POSTGRES_DB=classroomio -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres - -# API auth/secrets -# Leave blank to auto-generate on first ./run-docker-full-stack.sh -AUTH_BEARER_TOKEN= -BETTER_AUTH_SECRET=replace-with-a-long-random-secret - -# Dashboard server-to-server auth -# Leave blank to auto-generate on first ./run-docker-full-stack.sh -PRIVATE_SERVER_KEY= - -# Enterprise features (optional: SSO, token-auth, no-tracking) -# Request a key from ClassroomIO. Leave empty if you do not use Enterprise-only features. -LICENSE_KEY= - -# API and dashboard URLs -PUBLIC_SERVER_URL=http://localhost:3081 - -# Dashboard server-side calls (inside Docker network) -PRIVATE_SERVER_URL=http://api:3081 -TRUSTED_ORIGINS=http://localhost:3082,http://localhost:5173 -AUTH_COOKIE_DOMAIN= -PUBLIC_IS_SELFHOSTED=true - -# Dashboard domain config -PRIVATE_APP_HOST=domain.com -PRIVATE_APP_SUBDOMAINS=app,launchweek - -# CSP: External domains (required when PUBLIC_IS_SELFHOSTED=true; no defaults) -# Passed as build args to the dashboard. Rebuild dashboard after changing: --build dashboard -# Comma-separated list for Content-Security-Policy. Include your CDN, API, and video domains. -# Example (adjust to your instance): -ALLOWED_EXTERNAL_DOMAINS=https://*.classroomio.com,https://api.classroomio.com,https://pgrest.classroomio.com,wss://*.classroomio.com,https://assets.cdn.clsrio.com,https://cdn.plyr.io - -# Email delivery (choose one approach) -# Option A: Zoho/ZeptoMail token -ZOHO_TOKEN= - -# Option B: SMTP -SMTP_HOST= -SMTP_PORT= -SMTP_USER= -SMTP_PASSWORD= -SMTP_SENDER= - -# Object storage - MinIO (included by default; script auto-sets these if missing) -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media -EOF -``` - -Important: - -- `./run-docker-full-stack.sh` auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when missing/insecure. -- `./run-docker-full-stack.sh` always uses root `.env` (`docker compose --env-file .env ...`) for compose build/runtime variables. -- API key middleware for dashboard/server-to-server calls validates `PRIVATE_SERVER_KEY`. -- `AUTH_BEARER_TOKEN` is separate and is not used by `apiKeyMiddleware`. -- Enterprise-only features (SSO, token-auth, no-tracking) require `LICENSE_KEY`. If you do not need those features, leave `LICENSE_KEY` empty. -- Dashboard API URL behavior is split by environment: browser requests use `PUBLIC_SERVER_URL`, and server-side dashboard requests use `PRIVATE_SERVER_URL` (fallback: `PUBLIC_SERVER_URL`). -- In hosted deployments, `PUBLIC_SERVER_URL` must be your public API domain (not `localhost`), while Docker internal SSR calls should use `PRIVATE_SERVER_URL=http://api:3081`. -- `AUTH_COOKIE_DOMAIN` is optional. Leave it empty for Better Auth defaults, or set it to `.your-domain.com` (shared cookies across subdomains) or your API host domain when you need explicit cookie domain control. -- Do not use invalid domains (for example `b.com` or `.com` when your backend is `api.b.com`); use `.b.com` or the exact backend host. -- For email sending, configure either `ZOHO_TOKEN` or a full SMTP config (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_SENDER`). -- If neither Zoho token nor SMTP is configured, emails will not send. -- Keep real secrets out of version control. -- When `PUBLIC_IS_SELFHOSTED=true`, the dashboard CSP uses **only** env vars—no defaults. Set `ALLOWED_EXTERNAL_DOMAINS` with all external domains your instance needs (CDNs, video providers, analytics, etc.). -- **Object storage:** MinIO is included by default. The script auto-sets MinIO env vars if missing. Use `--no-minio` to exclude object storage (video/document/media uploads will not work). - -## 4. Start Full Docker Stack - -From repo root: - -```bash +cp .env.example .env +# Edit .env — set your domain, secrets, and ALLOWED_EXTERNAL_DOMAINS ./run-docker-full-stack.sh - -# Skip image rebuild and only start containers -./run-docker-full-stack.sh --no-build - -# Exclude MinIO (video/document/media uploads will not work) -./run-docker-full-stack.sh --no-minio ``` -This script: +The script builds and starts: **postgres**, **redis**, **minio** (object storage), **db-init** (schema setup), **api** (`localhost:3081`), and **dashboard** (`localhost:3082`). -- builds and starts `postgres`, `redis`, `db-init`, `api`, `dashboard`, and **MinIO** (object storage) -- auto-configures MinIO env vars (if missing), runs `minio-init` (creates videos, documents, media buckets) -- prints compose status -- checks API (`3081`) and dashboard (`3082`) endpoints +It auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when they are missing. -MinIO Web UI: `http://localhost:9001` (default: `minioadmin` / `minioadmin`). +## Environment Variables -## 5. Verify Manually (Optional) +All Docker services read from a single root `.env` file. See [`.env.example`](../../.env.example) for the full annotated template with required/optional grouping. + +Key points: + +- **Required:** `PUBLIC_SERVER_URL`, `TRUSTED_ORIGINS`, `BETTER_AUTH_SECRET`, `PUBLIC_IS_SELFHOSTED=true`, `ALLOWED_EXTERNAL_DOMAINS`, `DASHBOARD_ORIGIN`. +- **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 ```bash docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps -curl -sS http://localhost:3081/ -curl -I http://localhost:3082/ +curl -sS http://localhost:3081/ # API — returns JSON +curl -I http://localhost:3082/ # Dashboard — returns 200 ``` -## 6. Access - -- API: `http://localhost:3081` -- Dashboard: `http://localhost:3082` - -## 7. Trigger Seed Data - -To run DB setup and seed demo data in one command: +## Seed Demo Data ```bash -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" +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" ``` -Seed only (skip DB setup): +## Command Reference -```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml run --rm db-init sh -c "pnpm --filter @cio/db seed" +All commands use this prefix — abbreviated as `dc` below: + +``` +dc = docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ``` -Use seed-only only after setup has already run at least once (`db:setup`, which now creates roles and syncs schema). +| 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` | -`--rm` removes the one-off `db-init` container after the command finishes so you do not accumulate stopped seed containers. - -## 8. Restart After Source Code Changes - -Because this Docker setup builds images from source (no live bind-mount hot reload), code changes require a rebuild. - -Rebuild and restart only one service: +### Build Images Locally ```bash +docker build -f docker/Dockerfile.api -t classroomio/api:latest . +docker build -f docker/Dockerfile.dashboard -t classroomio/dashboard:latest . +``` + +### Inspect Container Env Vars + +```bash +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`): + +```bash +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: + +```bash +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: + +```bash +# One service docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build dashboard -``` -If you changed shared code used by both (`packages/*`), rebuild both: - -```bash +# Both (e.g. shared packages/* changed) docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api dashboard -``` -Rebuild full stack: - -```bash +# Full stack ./run-docker-full-stack.sh ``` -If you only changed environment variables (no code change), restart without rebuild: +## Troubleshooting + +### "No space left on device" ```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart api dashboard +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 ``` -## 9. Stop +> **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: ```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down +docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart minio ``` -## 10. If PostgreSQL or Docker Fails with "No space left on device" +### SMTP Errors in Logs -The error `could not write lock file "postmaster.pid": No space left on device` means the disk (or Docker storage) is full. Fix it by freeing space, then restart Postgres. +`ECONNREFUSED 127.0.0.1:465` means SMTP is not configured. This does not block startup — configure SMTP or Zoho when you need email. -**1. Check disk usage** +### Port Conflicts ```bash -df -h -docker system df +lsof -nP -iTCP:3081 -sTCP:LISTEN +lsof -nP -iTCP:3082 -sTCP:LISTEN ``` -**2. Free Docker space (safe — removes unused build cache and images)** +## Related -```bash -docker builder prune -f -docker image prune -a -f -docker container prune -f -``` - -**3. If still low on space, remove unused volumes** (only if you do not need data in other projects’ volumes) - -```bash -docker volume prune -f -``` - -Warning: `docker volume prune` removes volumes not used by any container. If you run it after `docker compose ... down`, it can remove `postgres-data`, `redis-data`, and `minio-data`, and you will lose database and object storage data. To free space without touching Classroom.io data, avoid `down` and use only the prunes in step 2, or remove specific unused volumes by name. - -**4. Restart the stack** - -```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d -``` - -If the host disk is still full after pruning, free space elsewhere (e.g. large files, old logs, other apps) or increase disk capacity. - -## 11. "The database system is in recovery mode" (57P03) - -This happens when the API (or another service) hits Postgres before it has finished starting or replaying WAL after a crash/restart. Our Postgres healthcheck now requires a successful `SELECT 1` (not just `pg_isready`) so dependent services only start once the DB accepts queries. - -If you still see it (e.g. old compose file or a very slow disk): - -1. Wait 30–60 seconds and retry the request. -2. Ensure you’re using the latest `docker/docker-compose.yaml` (healthcheck runs `pg_isready` and `psql ... SELECT 1`). -3. Restart the stack so Postgres gets a clean start and the API waits for the updated healthcheck: `./run-docker-full-stack.sh`. - -## 12. MinIO: "Storage reached its minimum free drive threshold" - -MinIO’s data scanner reports this when the disk (or the volume backing `minio-data`) is below MinIO’s minimum free space. Same root cause as [§10](#10-if-postgresql-or-docker-fails-with-no-space-left-on-device): the host or Docker storage is too full. - -**What to do:** - -1. Free space using the same steps as in **§10** (e.g. `docker system df`, `docker builder prune -f`, `docker image prune -a -f`, `docker container prune -f`). Avoid `docker volume prune` if you need to keep existing data. -2. After freeing space, restart MinIO (or the full stack) so the scanner sees the new free space: - ```bash - docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart minio - ``` - Or restart everything: `./run-docker-full-stack.sh`. -3. If the disk is still near full, remove unneeded objects from MinIO buckets (via Console or `mc`) or add/attach more storage. - -MinIO does not expose an env var to lower or disable this threshold; freeing space or adding capacity is the intended fix. +- [Self-hosting docs](https://classroomio.com/docs/quickstart/self-hosting) — published guide with Railway + Docker instructions +- [`docker/docs/PUBLISHING_IMAGES.md`](PUBLISHING_IMAGES.md) — publishing Docker images to Docker Hub diff --git a/docker/docs/USAGE.md b/docker/docs/USAGE.md deleted file mode 100644 index a2e3977e..00000000 --- a/docker/docs/USAGE.md +++ /dev/null @@ -1,247 +0,0 @@ -# Docker Usage Guide - -This directory contains Docker files for local self-hosting and image publishing. - -## Stack Overview - -`docker/docker-compose.yaml` starts these services: - -- `postgres`: primary database used by `@cio/db` and `@cio/api` -- `redis`: cache/rate-limit store used by `@cio/api` -- `db-init`: one-off setup job (`pnpm --filter @cio/db db:setup`, which creates required roles and syncs schema) -- `api`: backend service on `http://localhost:3081` -- `dashboard`: frontend service on `http://localhost:3082` - -For published-image deployments, use `docker/docker-compose.prod.yaml` (same services, but `api`, `dashboard`, and `db-init` run from published images instead of local builds). - -Notes: - -- Postgres and Redis are internal-only in compose (not exposed on host ports by default). -- `db-init` is expected to exit with code `0` after setup. -- If you run seed-only commands, make sure setup has already run once so schema exists. - -## Quick Start - -Run from the repository root: - -```bash -# Full stack with MinIO (object storage) - default -./run-docker-full-stack.sh - -# Skip image rebuild and only start containers -./run-docker-full-stack.sh --no-build - -# Exclude MinIO (video/document/media uploads will not work) -./run-docker-full-stack.sh --no-minio -``` - -Verify: - -```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps -curl -sS http://localhost:3081/ -curl -I http://localhost:3082/ -``` - -Expected: - -- API returns welcome JSON -- Dashboard returns `HTTP/1.1 200 OK` - -## API-Only Smoke Test - -If you only want to validate API startup (recommended first check): - -```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up --build -d postgres redis db-init api -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs --tail=100 api -curl -sS http://localhost:3081/ -``` - -## Production (Published Images) - -Create a production env file from the template: - -```bash -cp docker/.env.prod.example docker/.env.prod -``` - -Set pinned image tags in `docker/.env.prod`: - -```bash -CIO_API_IMAGE=classroomio/api:v0.1.0 -CIO_DASHBOARD_IMAGE=classroomio/dashboard:v0.1.0 -# Optional: Enterprise-only features (SSO, token-auth, no-tracking) -# LICENSE_KEY=your-issued-license-key -``` - -Then deploy: - -```bash -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml pull -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml up -d -``` - -## Environment Variables - -Create a root `.env` file for compose. For local Docker, you can leave the token values blank and the startup script will generate secure values and keep them synchronized: - -```bash -POSTGRES_DB=classroomio -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres - -BETTER_AUTH_SECRET=replace-with-a-strong-secret -AUTH_BEARER_TOKEN= -PRIVATE_SERVER_KEY= -# Optional: required only for Enterprise-only features (SSO, token-auth, no-tracking) -LICENSE_KEY= - -PUBLIC_SERVER_URL=https://api.your-domain.com -PRIVATE_SERVER_URL=http://api:3081 -TRUSTED_ORIGINS=https://app.your-domain.com -AUTH_COOKIE_DOMAIN= -``` - -Optional integrations (if used): - -```bash -SMTP_HOST= -SMTP_PORT= -SMTP_USER= -SMTP_PASSWORD= -SMTP_SENDER= - -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= - -UNSPLASH_API_KEY= -``` - -Object storage (MinIO included by default; script auto-sets these): - -```bash -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media -``` - -Important: - -- `./run-docker-full-stack.sh` auto-generates secure `AUTH_BEARER_TOKEN`/`PRIVATE_SERVER_KEY` values when missing or insecure placeholders are used. -- `./run-docker-full-stack.sh` always uses root `.env` (`docker compose --env-file .env ...`) so compose build/runtime variables come from repository-root `.env`. -- API key middleware for dashboard/server-to-server calls validates `PRIVATE_SERVER_KEY`. -- `AUTH_BEARER_TOKEN` is a separate token and is not used by `apiKeyMiddleware`. -- Enterprise-only features (SSO, token-auth, no-tracking) require `LICENSE_KEY`. If you do not use those features, leave it unset. -- Dashboard URL selection is environment-aware: browser calls use `PUBLIC_SERVER_URL`, while SSR/server-side calls use `PRIVATE_SERVER_URL` (fallback: `PUBLIC_SERVER_URL`). -- In Docker, set `PRIVATE_SERVER_URL=http://api:3081` for internal service-to-service traffic, and keep `PUBLIC_SERVER_URL` as your public API URL (do not use `localhost` in hosted deployments). -- `AUTH_COOKIE_DOMAIN` is required for setting cookies. Set it to `.your-domain.com` for cross-subdomain cookies, or to your exact API host when you need host-scoped cookies. Leave empty to use Better Auth defaults. - -## Common Commands - -```bash -# Start / rebuild -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up --build -d - -# Stream logs -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs -f api dashboard - -# Stop -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down - -# Stop and remove volumes (deletes local DB/cache data) -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down -v -``` - -### API or dashboard fails to start - -```bash -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs --tail=200 api dashboard db-init postgres redis -``` - -### SMTP errors in API logs - -If SMTP is not configured, logs may include `ECONNREFUSED 127.0.0.1:465`. This does not block API startup. - -### Port conflicts - -```bash -lsof -nP -iTCP:3081 -sTCP:LISTEN -lsof -nP -iTCP:3082 -sTCP:LISTEN -``` - -## MinIO (S3-compatible storage) - -MinIO is **included by default** when you run `./run-docker-full-stack.sh`. The script auto-configures env vars and starts MinIO + minio-init (creates videos, documents, media buckets). - -To start MinIO manually (e.g. if you used `--no-minio` previously): - -```bash -docker compose -f docker/docker-compose.yaml --profile minio up -d -``` - -### Access URLs - -| Purpose | URL | Notes | -|---------|-----|-------| -| API | `http://localhost:9000` | Used by the ClassroomIO API for S3-compatible storage | -| Web UI | `http://localhost:9001` | Login to browse buckets, manage objects (default: `minioadmin` / `minioadmin`) | - -> **Security:** Change the default credentials (`minioadmin` / `minioadmin`) in production. Set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` in `.env`, and use the same values for `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY`. - -### Environment variables - -Set in `.env`: - -```bash -# MinIO container credentials (must match OBJECT_STORAGE_* below) -MINIO_ROOT_USER=minioadmin -MINIO_ROOT_PASSWORD=minioadmin - -# When API runs in Docker: use minio (Docker service name) -OBJECT_STORAGE_ENDPOINT=http://minio:9000 -OBJECT_STORAGE_PUBLIC_ENDPOINT=http://localhost:9000 -OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin -OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin -OBJECT_STORAGE_FORCE_PATH_STYLE=true -OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=http://localhost:9000/media -``` - -> **API in Docker:** Use `OBJECT_STORAGE_ENDPOINT=http://minio:9000` so the API container can reach MinIO on the Docker network. Use `OBJECT_STORAGE_ENDPOINT=http://localhost:9000` when the API runs locally (e.g. `pnpm api:dev`). - -`MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` configure the MinIO container. Set them to the same values as `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY` when using the built-in MinIO service. - -### MinIO troubleshooting - -#### Web UI not loading - -MinIO assigns a **random port** for the Web Console by default. If the Web UI at `http://localhost:9001` does not load, your container may have been created before we pinned the console port. - -**Fix:** Remove the old container and recreate it: - -```bash -docker rm -f cio-minio -docker compose -f docker/docker-compose.yaml --profile minio up -d -``` - -Our `docker-compose.yaml` uses `--console-address ":9001"` so the Web UI consistently listens on port 9001. Your data is stored in the `minio-data` volume and is preserved across container recreation. - -#### Container name conflict - -If you see `The container name "/cio-minio" is already in use`, remove the existing container first: - -```bash -docker rm -f cio-minio -docker compose -f docker/docker-compose.yaml --profile minio up -d -``` - -## Related Docs - -- `docker/docs/commands.md` for quick command snippets -- `docker/docs/PUBLISHING_IMAGES.md` for Docker Hub publishing diff --git a/docker/docs/commands.md b/docker/docs/commands.md deleted file mode 100644 index bdea1293..00000000 --- a/docker/docs/commands.md +++ /dev/null @@ -1,177 +0,0 @@ -# Quick Docker Commands Reference - -## Compose Stack - -```bash -# Full stack with MinIO (default; generates/syncs secure auth tokens) -./run-docker-full-stack.sh - -# Full stack without rebuilding images -./run-docker-full-stack.sh --no-build - -# Exclude MinIO (object storage) -./run-docker-full-stack.sh --no-minio - -# Full stack (postgres, redis, db-init, api, dashboard, minio) -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml --profile minio up --build -d - -# API-only smoke test path (plus required dependencies) -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up --build -d postgres redis db-init api - -# Service status -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps - -# Logs -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs -f api -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml logs -f dashboard - -# Stop services -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down - -# Stop + remove volumes (deletes local postgres/redis data) -docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml down -v -``` - -## MinIO (S3-compatible storage) - -MinIO is **included by default** with `./run-docker-full-stack.sh`. To start MinIO manually: - -```bash -docker compose -f docker/docker-compose.yaml --profile minio up -d - -# If Web UI doesn't load or container name conflict: -docker rm -f cio-minio -docker compose -f docker/docker-compose.yaml --profile minio up -d -``` - -**When API runs in Docker:** Use `OBJECT_STORAGE_ENDPOINT=http://minio:9000` (Docker service name). The script auto-sets this when MinIO is included. - -**Security:** Change the default `minioadmin` / `minioadmin` credentials in production. Set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` in `.env` (and matching `OBJECT_STORAGE_*` vars). - -See `docker/docs/USAGE.md` for MinIO env vars and troubleshooting. - -## Compose Stack (Published Images / Production) - -```bash -# 1) Copy and edit production env values (including pinned image tags) -cp docker/.env.prod.example docker/.env.prod - -# 2) Pull pinned images -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml pull - -# 3) Start stack from published images -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml up -d - -# 4) Inspect status and logs -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml ps -docker compose -p classroomio --env-file docker/.env.prod -f docker/docker-compose.prod.yaml logs -f api dashboard -``` - -## Health Checks - -```bash -# API should return welcome JSON -curl -sS http://localhost:3081/ - -# Dashboard should return 200 -curl -I http://localhost:3082/ -``` - -## Build Images Locally - -```bash -docker build -f docker/Dockerfile.api -t classroomio/api:latest . -docker build -f docker/Dockerfile.dashboard -t classroomio/dashboard:latest . -``` - -## Push to Docker Hub - -```bash -docker push classroomio/api:latest -docker push classroomio/dashboard:latest -``` - -## All-in-One Publish Script - -```bash -# Default username=classroomio version=latest -./docker/docker-push.sh - -# Custom username -DOCKERHUB_USERNAME=your-username ./docker/docker-push.sh - -# Versioned tag -VERSION=v1.0.0 ./docker/docker-push.sh -``` - -## Pull Published Images - -```bash -docker pull classroomio/api:latest -docker pull classroomio/dashboard:latest -``` - -## Run Published Images (Manual) - -Use compose for local development. If you run manually, include required runtime env. -`LICENSE_KEY` is only needed if you use Enterprise-only features. - -```bash -# API -docker run -d --name cio-api -p 3081:3081 \ - -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/classroomio \ - -e PRIVATE_DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/classroomio \ - -e REDIS_URL=redis://host.docker.internal:6379 \ - -e PUBLIC_SERVER_URL=https://api.your-domain.com \ - -e TRUSTED_ORIGINS=https://app.your-domain.com \ - -e AUTH_COOKIE_DOMAIN=.your-domain.com \ - -e BETTER_AUTH_SECRET= \ - -e PRIVATE_SERVER_KEY= \ - -e AUTH_BEARER_TOKEN= \ - -e LICENSE_KEY= \ - classroomio/api:latest - -# Dashboard -docker run -d --name cio-dashboard -p 3082:3082 \ - -e PUBLIC_SERVER_URL=http://localhost:3081 \ - -e PRIVATE_SERVER_KEY= \ - -e PUBLIC_IS_SELFHOSTED=true \ - classroomio/dashboard:latest -``` - -## Inspect environment variables in a container - -```bash -# List all env vars in a running container (use actual container name) -docker exec env - -# Sorted, one per line -docker exec env | sort - -# Inspect from host (env as configured at container create time) -docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' - -# Print a single variable -docker exec printenv DATABASE_URL -``` - -**Container names** (from `docker compose ... ps`): `cio-postgres`, `cio-redis`, `cio-api`, `cio-dashboard`; with MinIO: `cio-minio`, `cio-minio-init`. - -Example: - -```bash -docker exec cio-api env | sort -``` - -## Useful Commands - -```bash -# List local images -docker images | grep classroomio - -# Remove local images -docker rmi classroomio/api:latest classroomio/dashboard:latest - -# Inspect image -docker inspect classroomio/api:latest -```