docs: selfhostin improvements
This commit is contained in:
+101
@@ -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
|
||||||
@@ -160,38 +160,11 @@ The repository also contains shared packages under `packages/` (for example `pac
|
|||||||
|
|
||||||
### Docker Compose (Full Stack)
|
### Docker Compose (Full Stack)
|
||||||
|
|
||||||
To run the Dockerized stack (including API + dashboard):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cp .env.example .env # copy env template, edit for your domain
|
||||||
./run-docker-full-stack.sh
|
./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
|
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.
|
||||||
./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`
|
|
||||||
|
|||||||
@@ -47,9 +47,6 @@
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
console.log('content', content);
|
console.log('content', content);
|
||||||
});
|
});
|
||||||
$effect(() => {
|
|
||||||
console.log('lessonApi.currentLocale', lessonApi.currentLocale);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if mode === MODES.edit}
|
{#if mode === MODES.edit}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ ClassroomIO supports Single Sign-On (SSO) via OpenID Connect (OIDC), allowing yo
|
|||||||
Set `LICENSE_KEY` in the API service environment.
|
Set `LICENSE_KEY` in the API service environment.
|
||||||
Local development: `apps/api/.env`.
|
Local development: `apps/api/.env`.
|
||||||
Docker Compose: repository root `.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.
|
If you do not use Enterprise-only features, leave `LICENSE_KEY` unset.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
|
|||||||
@@ -4,288 +4,19 @@ description: Guide to self-hosting ClassroomIO on your own servers.
|
|||||||
---
|
---
|
||||||
|
|
||||||
import { Callout } from 'fumadocs-ui/components/callout';
|
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
ClassroomIO is made up of the following applications:
|
## Docker (Recommended)
|
||||||
|
|
||||||
- [apps/api](https://github.com/classroomio/classroomio/tree/main/apps/api) - an Express server
|
### Prerequisites
|
||||||
- [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
|
|
||||||
|
|
||||||
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
|
### Quick Start
|
||||||
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.
|
|
||||||
|
|
||||||
<Callout title="Learn More">
|
|
||||||
Learn more about setting up a custom SMTP server
|
|
||||||
[here](https://nodemailer.com/smtp/)
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## 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
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout type="warn" title="API in Docker">
|
|
||||||
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`.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
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`) |
|
|
||||||
|
|
||||||
<Callout type="error" title="Change default credentials">
|
|
||||||
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`.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout type="warn" title="MinIO Web UI not loading?">
|
|
||||||
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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout title="Storage Optional">
|
|
||||||
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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
<Callout type="warning" title="Enterprise License Required">
|
|
||||||
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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
[](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"
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout title="Learn More">
|
|
||||||
Learn more about the environment variables
|
|
||||||
[here](https://github.com/classroomio/classroomio/blob/main/apps/api/.env.example)
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
#### 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"
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout title="Important">
|
|
||||||
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`.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout type="warn" title="CSP for Self-Hosted">
|
|
||||||
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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout title="Important">
|
|
||||||
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`).
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout title="Important">
|
|
||||||
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`).
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout title="Meta tags for self-hosted branding">
|
|
||||||
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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<Callout title="Learn More">
|
|
||||||
Learn more about the environment variables
|
|
||||||
[here](https://github.com/classroomio/classroomio/blob/main/apps/dashboard/.env.example)
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## 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`)
|
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
@@ -294,105 +25,214 @@ git clone https://github.com/classroomio/classroomio.git
|
|||||||
cd classroomio
|
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"
|
```zsh title="Terminal"
|
||||||
./run-docker-full-stack.sh
|
./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"
|
```zsh title="Terminal"
|
||||||
docker compose -p classroomio -f docker/docker-compose.yaml ps
|
curl -sS http://localhost:3081/ # API — returns JSON
|
||||||
curl -sS http://localhost:3081/
|
curl -I http://localhost:3082/ # Dashboard — returns 200
|
||||||
curl -I http://localhost:3082/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The services will be available at:
|
|
||||||
|
|
||||||
- Dashboard: http://localhost:3082
|
- Dashboard: http://localhost:3082
|
||||||
- API: http://localhost:3081
|
- 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) | — |
|
||||||
|
|
||||||
|
<Callout type="warn" title="CSP Domains">
|
||||||
|
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.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
<Callout type="error" title="Change default credentials">
|
||||||
|
Do not use `minioadmin` / `minioadmin` in production.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
#### 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"
|
```zsh title="Terminal"
|
||||||
docker compose -p classroomio -f docker/docker-compose.yaml up --build -d postgres redis db-init api
|
# Start full stack (builds images, starts all services including MinIO)
|
||||||
docker compose -p classroomio -f docker/docker-compose.yaml logs --tail=100 api
|
./run-docker-full-stack.sh
|
||||||
curl -sS http://localhost:3081/
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="warn" title="Volume prune deletes data">
|
||||||
|
Avoid `docker volume prune` after `docker compose ... down` — it can remove your database and storage volumes.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Then restart: `./run-docker-full-stack.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Railway
|
||||||
|
|
||||||
|
The quickest hosted deployment is the [Railway Template](https://railway.app/template/dl9RG7?referralCode=8OyIj5).
|
||||||
|
|
||||||
|
[](https://railway.app/template/dl9RG7?referralCode=8OyIj5)
|
||||||
|
|
||||||
|
### cio-api
|
||||||
|
|
||||||
```zsh title="env"
|
```zsh title="env"
|
||||||
POSTGRES_DB=classroomio
|
DATABASE_URL="${{db.DATABASE_URL}}"
|
||||||
POSTGRES_USER=postgres
|
REDIS_URL="${{Redis.REDIS_URL}}"
|
||||||
POSTGRES_PASSWORD=postgres
|
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=
|
# Email (configure one)
|
||||||
AUTH_BEARER_TOKEN=
|
SMTP_HOST=
|
||||||
PRIVATE_SERVER_KEY=
|
SMTP_PORT=
|
||||||
# Required only for Enterprise-only features (SSO, token-auth, no-tracking)
|
SMTP_USER=
|
||||||
LICENSE_KEY=
|
SMTP_PASSWORD=
|
||||||
|
SMTP_SENDER=
|
||||||
|
|
||||||
PUBLIC_SERVER_URL=
|
# Object storage — Cloudflare R2 (for hosted deployments)
|
||||||
PRIVATE_SERVER_URL=
|
CLOUDFLARE_ACCOUNT_ID=
|
||||||
TRUSTED_ORIGINS=
|
CLOUDFLARE_ACCESS_KEY=
|
||||||
AUTH_COOKIE_DOMAIN=
|
CLOUDFLARE_SECRET_ACCESS_KEY=
|
||||||
|
CLOUDFLARE_BUCKET_DOMAIN=
|
||||||
# MinIO (object storage, when using ./run-docker-full-stack.sh with MinIO)
|
CLOUDFLARE_IMAGE_BUCKET_DOMAIN=
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<Callout title="Important">
|
Optional: add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` for OAuth, `UNSPLASH_API_KEY` for image search, or `LICENSE_KEY` for enterprise features.
|
||||||
`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
|
### cio-dashboard
|
||||||
calls validates `PRIVATE_SERVER_KEY`. Set `LICENSE_KEY` only if you need Enterprise-only features; otherwise leave it empty.
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
<Callout title="PRIVATE_SERVER_KEY">
|
||||||
|
`PRIVATE_SERVER_KEY` must be the same value in both API and dashboard. It authenticates server-side dashboard requests to the API.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
### 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"
|
For hosted deployments, Cloudflare R2 handles object storage. See the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide.
|
||||||
df -h
|
|
||||||
docker system df
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Free Docker space** (safe — removes unused build cache and images)
|
### SSO (Enterprise)
|
||||||
|
|
||||||
```zsh title="Terminal"
|
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.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
<Callout type="warn" title="Volume prune can delete your data">
|
|
||||||
`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.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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:
|
|
||||||
@@ -102,38 +102,7 @@ docker pull classroomio/dashboard:latest
|
|||||||
|
|
||||||
### Deploy with Compose
|
### Deploy with Compose
|
||||||
|
|
||||||
Use the production compose file (`docker/docker-compose.prod.yaml`) with pinned image tags.
|
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`.
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
## Versioning Strategy
|
## Versioning Strategy
|
||||||
|
|
||||||
|
|||||||
+119
-198
@@ -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.
|
## Prerequisites
|
||||||
|
|
||||||
## 1. Prerequisites
|
|
||||||
|
|
||||||
- Docker Desktop installed and running
|
- Docker Desktop installed and running
|
||||||
- `git` installed
|
- `git` installed
|
||||||
- `curl` installed (recommended for health checks)
|
|
||||||
|
|
||||||
## 2. Clone the Repo
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --branch feat/course-v2 https://github.com/classroomio/classroomio.git
|
git clone https://github.com/classroomio/classroomio.git
|
||||||
cd classroomio
|
cd classroomio
|
||||||
```
|
cp .env.example .env
|
||||||
|
# Edit .env — set your domain, secrets, and ALLOWED_EXTERNAL_DOMAINS
|
||||||
## 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
|
|
||||||
./run-docker-full-stack.sh
|
./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)
|
It auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when they are missing.
|
||||||
- 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
|
|
||||||
|
|
||||||
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
|
```bash
|
||||||
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 ps
|
||||||
curl -sS http://localhost:3081/
|
curl -sS http://localhost:3081/ # API — returns JSON
|
||||||
curl -I http://localhost:3082/
|
curl -I http://localhost:3082/ # Dashboard — returns 200
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Access
|
## Seed Demo Data
|
||||||
|
|
||||||
- API: `http://localhost:3081`
|
|
||||||
- Dashboard: `http://localhost:3082`
|
|
||||||
|
|
||||||
## 7. Trigger Seed Data
|
|
||||||
|
|
||||||
To run DB setup and seed demo data in one command:
|
|
||||||
|
|
||||||
```bash
|
```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
|
All commands use this prefix — abbreviated as `dc` below:
|
||||||
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml run --rm db-init sh -c "pnpm --filter @cio/db seed"
|
|
||||||
|
```
|
||||||
|
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.
|
### Build Images Locally
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
```bash
|
```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 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:
|
# Both (e.g. shared packages/* changed)
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api dashboard
|
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api dashboard
|
||||||
```
|
|
||||||
|
|
||||||
Rebuild full stack:
|
# Full stack
|
||||||
|
|
||||||
```bash
|
|
||||||
./run-docker-full-stack.sh
|
./run-docker-full-stack.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
If you only changed environment variables (no code change), restart without rebuild:
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "No space left on device"
|
||||||
|
|
||||||
```bash
|
```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
|
```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
|
```bash
|
||||||
df -h
|
lsof -nP -iTCP:3081 -sTCP:LISTEN
|
||||||
docker system df
|
lsof -nP -iTCP:3082 -sTCP:LISTEN
|
||||||
```
|
```
|
||||||
|
|
||||||
**2. Free Docker space (safe — removes unused build cache and images)**
|
## Related
|
||||||
|
|
||||||
```bash
|
- [Self-hosting docs](https://classroomio.com/docs/quickstart/self-hosting) — published guide with Railway + Docker instructions
|
||||||
docker builder prune -f
|
- [`docker/docs/PUBLISHING_IMAGES.md`](PUBLISHING_IMAGES.md) — publishing Docker images to Docker Hub
|
||||||
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.
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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=<strong-random-secret> \
|
|
||||||
-e PRIVATE_SERVER_KEY=<dashboard-server-api-key> \
|
|
||||||
-e AUTH_BEARER_TOKEN=<optional-other-bearer-token> \
|
|
||||||
-e LICENSE_KEY=<your-issued-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=<dashboard-server-api-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 <container_name> env
|
|
||||||
|
|
||||||
# Sorted, one per line
|
|
||||||
docker exec <container_name> env | sort
|
|
||||||
|
|
||||||
# Inspect from host (env as configured at container create time)
|
|
||||||
docker inspect <container_name> --format '{{range .Config.Env}}{{println .}}{{end}}'
|
|
||||||
|
|
||||||
# Print a single variable
|
|
||||||
docker exec <container_name> 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
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user