refactor: selfhosting docs (#798)f
* Remove the run-docker-full-stack.sh script to streamline the deployment process and eliminate unnecessary complexity. * fix: update documentation to clarify script behavior for building and pulling images * update docs * fix: update script URL to use classroomio.com for downloads * fix: humanize selfhosting docs and minor amendment * fix: attend to comment * fix: humanize docs * update docs * fix: adjusted the Restarting ClassroomIO... function * fix: update commands and documentation for environment variable handling and upgrades * chore: added space for rebuild
This commit is contained in:
@@ -168,8 +168,14 @@ export default defineConfig({
|
||||
root: '/self-hosted',
|
||||
items: [
|
||||
'/self-hosted',
|
||||
{
|
||||
label: 'Platforms',
|
||||
'/self-hosted/101',
|
||||
'/self-hosted/architecture',
|
||||
{
|
||||
label: 'Install',
|
||||
display: 'group',
|
||||
// Pinned open — it's what most visitors came for. The other groups
|
||||
// start collapsed (and auto-expand when they contain the current page).
|
||||
collapsed: false,
|
||||
items: [
|
||||
'/self-hosted/docker',
|
||||
'/self-hosted/digitalocean',
|
||||
@@ -179,7 +185,9 @@ export default defineConfig({
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Configuration',
|
||||
label: 'Configure',
|
||||
display: 'group',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'/self-hosted/configuration',
|
||||
'/self-hosted/configuration/secrets',
|
||||
@@ -187,10 +195,21 @@ export default defineConfig({
|
||||
'/self-hosted/configuration/storage',
|
||||
'/self-hosted/configuration/csp',
|
||||
'/self-hosted/configuration/integrations',
|
||||
'/self-hosted/configuration/sso',
|
||||
'/self-hosted/configuration/versions'
|
||||
'/self-hosted/enterprise'
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Manage',
|
||||
display: 'group',
|
||||
collapsed: true,
|
||||
items: ['/self-hosted/configuration/versions', '/self-hosted/backups']
|
||||
},
|
||||
{
|
||||
label: 'Troubleshoot',
|
||||
display: 'group',
|
||||
collapsed: true,
|
||||
items: ['/self-hosted/troubleshooting']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -19,7 +19,7 @@ Set `LICENSE_KEY` in the API service environment.
|
||||
Local development: `apps/api/.env`.
|
||||
Docker Compose: repository root `.env`.
|
||||
If you do not use Enterprise-only features, leave `LICENSE_KEY` unset.
|
||||
See [Self Hosting → SSO & licensing](/self-hosted/configuration/sso) for per-platform placement.
|
||||
See [Self Hosting → Enterprise & licensing](/self-hosted/enterprise) for per-platform placement.
|
||||
:::
|
||||
|
||||
## Supported Providers
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Self-hosting 101
|
||||
description: What you get when you self-host ClassroomIO, how the stack fits together, and how self-hosted differs from the cloud product.
|
||||
---
|
||||
|
||||
ClassroomIO is an open-source learning platform you can run entirely on your own infrastructure.
|
||||
This page explains what you're actually deploying before you pick an
|
||||
[install path](/self-hosted#which-should-i-choose).
|
||||
|
||||
## What you get
|
||||
|
||||
The full product: the same code that powers the cloud version. Self-hosted, you run:
|
||||
|
||||
- The **dashboard** your team and students use (courses, exercises, communities, certificates).
|
||||
- The **API** behind it.
|
||||
- A background **jobs worker** for the heavy lifting: video processing, transcription, AI course
|
||||
generation, and email sending.
|
||||
- The infrastructure they need: **Postgres** (database), **Redis** (queues and caching), and
|
||||
**MinIO** (S3-compatible object storage for uploads, swappable for AWS S3 or Cloudflare R2).
|
||||
|
||||
That's the ~7 containers the Docker stack starts (MinIO ships with a small one-shot init container
|
||||
that creates its buckets). They're deliberately separate services: the parts that spike CPU and RAM
|
||||
(video transcodes) are isolated from the parts that serve your users. How they talk to each other
|
||||
is on the [Architecture](/self-hosted/architecture) page; what hardware they need is in the
|
||||
[server requirements](/self-hosted#server-requirements).
|
||||
|
||||
## Self-hosted vs cloud
|
||||
|
||||
| | Cloud (app.classroomio.com) | Self-hosted |
|
||||
|---|---|---|
|
||||
| Infrastructure | Managed for you | Yours — your server, your domain |
|
||||
| Your data | Hosted by ClassroomIO | Stays on your infrastructure |
|
||||
| Updates | Automatic | You choose when ([pin a version](/self-hosted/configuration/versions)) |
|
||||
| Organizations | Multiple per account, plan-based feature tiers | Single organization with all core features |
|
||||
| Enterprise features (SSO, token auth, no-tracking) | On the Enterprise plan | Via a [license key](/self-hosted/enterprise) |
|
||||
|
||||
Self-hosted mode is controlled by one flag, `PUBLIC_IS_SELFHOSTED=true`, which is already set in
|
||||
the shipped Docker configuration. It locks the instance to a single organization with the full core
|
||||
feature set, and points invite links and emails at your own domain.
|
||||
|
||||
**About telemetry:** self-hosted mode disables error reporting (Sentry) and the in-app feedback
|
||||
widget out of the box. Product analytics (PostHog, Umami) are only disabled by the `no-tracking`
|
||||
enterprise license feature. See [Enterprise & licensing](/self-hosted/enterprise).
|
||||
|
||||
## How licensing works
|
||||
|
||||
ClassroomIO is **open core** under the [AGPL-3.0 license](https://github.com/classroomio/classroomio/blob/main/LICENSE):
|
||||
|
||||
- **Everything an LMS needs is free:** courses, students, uploads, video, AI features,
|
||||
communities, certificates. No seat limits, no artificial caps. You never need a license key to
|
||||
run a complete platform.
|
||||
- **A small set of enterprise features** (SSO, token auth, and no-tracking mode) are gated behind
|
||||
a paid `LICENSE_KEY`. They exist so organizations with compliance requirements can fund the
|
||||
project. Details on the [Enterprise & licensing](/self-hosted/enterprise) page.
|
||||
|
||||
There is exactly one codebase and one set of Docker images. A license key unlocks features in the
|
||||
same build you already run, so you never migrate editions.
|
||||
|
||||
## Where to next
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Pick an install path" href="/self-hosted" icon="server">
|
||||
Docker, DigitalOcean, Railway, Coolify, or Dokploy.
|
||||
</Card>
|
||||
<Card title="Architecture" href="/self-hosted/architecture" icon="network">
|
||||
The services, how they connect, and why the jobs worker needs RAM.
|
||||
</Card>
|
||||
<Card title="Configuration" href="/self-hosted/configuration" icon="settings">
|
||||
Every environment variable the stack reads.
|
||||
</Card>
|
||||
<Card title="Enterprise & licensing" href="/self-hosted/enterprise" icon="id-card">
|
||||
SSO, token auth, no-tracking, and how license validation works.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: The services in a self-hosted ClassroomIO stack, how they authenticate to each other, and why the jobs worker drives your RAM requirement.
|
||||
---
|
||||
|
||||
Every install path (Docker, DigitalOcean, Railway, Coolify, Dokploy) runs the same topology, so
|
||||
this page applies no matter which one you picked.
|
||||
|
||||
## The services
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B[Browser] -->|HTTPS| D[dashboard :3082]
|
||||
D -->|Bearer PRIVATE_SERVER_KEY| A[api :3081]
|
||||
A --> P[(postgres)]
|
||||
A --> R[(redis)]
|
||||
A --> M[(minio / S3)]
|
||||
J[jobs worker] --> R
|
||||
J --> P
|
||||
J --> M
|
||||
```
|
||||
|
||||
| Service | Image | Port | Role |
|
||||
|---------|-------|------|------|
|
||||
| `dashboard` | `classroomio/dashboard` | 3082 | The SvelteKit web app. Server-side renders pages and proxies browser API calls, so user traffic is same-origin. |
|
||||
| `api` | `classroomio/api` | 3081 | The backend. Runs **database migrations on boot**, then serves the API. |
|
||||
| `jobs` | `classroomio/jobs` | — | Background worker (BullMQ). Drains the Redis queues: video processing, transcription, AI course generation, emails, notifications, maintenance. Ships with ffmpeg. |
|
||||
| `postgres` | `postgres:16-alpine` | internal | The database. All state lives here (volume `postgres-data`). |
|
||||
| `redis` | `redis:7-alpine` | internal | Job queues and caching, persisted with AOF (volume `redis-data`). |
|
||||
| `minio` | `minio/minio` | 9000/9001 | S3-compatible object storage for uploads (volume `minio-data`). Swappable for AWS S3 / Cloudflare R2. |
|
||||
| `minio-init` | `minio/mc` | — | One-shot: creates the `videos`, `documents`, and `media` buckets, makes `media` publicly downloadable, then exits. |
|
||||
|
||||
The app services are pinned by one variable, `CIO_VERSION`. See
|
||||
[Versions & upgrades](/self-hosted/configuration/versions).
|
||||
|
||||
## Startup order
|
||||
|
||||
The compose file encodes the dependency chain with healthchecks:
|
||||
|
||||
1. **postgres** and **redis** start first. Postgres only reports healthy once it answers a real
|
||||
query (`SELECT 1`), not just `pg_isready`, because Postgres can accept connections while still
|
||||
in crash recovery, which would surface as
|
||||
[57P03 sign-in errors](/self-hosted/troubleshooting#sign-in-fails-with-57p03-right-after-startup).
|
||||
2. **api** starts, runs any pending database migrations, then listens. A failed migration exits the
|
||||
container instead of serving a half-migrated database.
|
||||
3. **dashboard** starts only after the API is healthy.
|
||||
|
||||
This is why a first boot takes a minute or two: the API is migrating before anything responds.
|
||||
|
||||
## The api ↔ dashboard handshake
|
||||
|
||||
The dashboard's server side calls the API over the internal container network
|
||||
(`PRIVATE_SERVER_URL`, default `http://api:3081`) and authenticates every request with:
|
||||
|
||||
```
|
||||
Authorization: Bearer <PRIVATE_SERVER_KEY>
|
||||
```
|
||||
|
||||
The API rejects requests where the key is missing or wrong (401), and refuses to operate without
|
||||
one configured (500). Two consequences for operators:
|
||||
|
||||
- **The value must be identical in the api and dashboard services.** The compose file passes the
|
||||
same `.env` value to both, and `./classroomio.sh` generates a strong one automatically. On PaaS
|
||||
platforms where you set env per service, a mismatch is the classic cause of a dashboard that
|
||||
renders but can't load any data.
|
||||
- The compose file **fails fast** if `PRIVATE_SERVER_KEY` (or `BETTER_AUTH_SECRET`) is empty.
|
||||
See [Environment & secrets](/self-hosted/configuration/secrets).
|
||||
|
||||
## The jobs worker and your RAM
|
||||
|
||||
The jobs worker is why the [server requirements](/self-hosted#server-requirements) recommend 8 GB.
|
||||
|
||||
It processes queues for media (probe → thumbnail → audio extraction), transcription (Whisper),
|
||||
emails, notifications, AI course generation, and periodic maintenance (including reaping stuck
|
||||
jobs). Video work runs **ffmpeg as a subprocess**, streaming files via disk rather than holding
|
||||
whole videos in memory, but transcodes still spike RAM well above the stack's idle usage. A server
|
||||
that looks comfortable at idle can OOM-kill Postgres the first time someone uploads a lecture
|
||||
video; see [Troubleshooting → OOM kills](/self-hosted/troubleshooting#containers-are-oom-killed).
|
||||
|
||||
The worker is required, not optional: without it (or without Redis), uploads queue forever at
|
||||
"processing" and most emails never send
|
||||
([details](/self-hosted/troubleshooting#uploads-stuck-at-processing)). It also scales
|
||||
independently: the image runs all workers in one process by default, but you can override the
|
||||
container command to run, say, media processing on separate hardware from email sending.
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Backups
|
||||
description: Back up and restore a self-hosted ClassroomIO — Postgres dumps and object-storage archives, automated or manual.
|
||||
---
|
||||
|
||||
Two things hold all of your data: the **Postgres database** (courses, users, organizations) and
|
||||
**object storage** (uploaded videos, documents, media). Back up both.
|
||||
|
||||
## With the script
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh backup
|
||||
```
|
||||
|
||||
This writes two timestamped files into `./backups/`:
|
||||
|
||||
- `classroomio-db-<timestamp>.sql.gz`: a full `pg_dump` of the database.
|
||||
- `classroomio-minio-<timestamp>.tar.gz`: an archive of the bundled MinIO volume.
|
||||
|
||||
If you use external S3/R2 instead of the bundled MinIO, the second step is skipped. Use your
|
||||
provider's backup or replication tools for the buckets.
|
||||
|
||||
`./classroomio.sh upgrade` runs this backup automatically before pulling new images, so every
|
||||
upgrade has a restore point.
|
||||
|
||||
:::warning[Copy backups off the server]
|
||||
A dead disk takes `./backups/` with it. Sync the files to another machine, an object-storage
|
||||
bucket, or your provider's snapshot system on a schedule, e.g. a nightly cron:
|
||||
|
||||
```zsh title="crontab"
|
||||
0 3 * * * cd /path/to/classroomio && ./classroomio.sh backup && rsync -a backups/ user@other-host:classroomio-backups/
|
||||
```
|
||||
:::
|
||||
|
||||
## Manual commands
|
||||
|
||||
The script runs the equivalent of:
|
||||
|
||||
```zsh title="Terminal"
|
||||
# Postgres (adjust user/db if you changed POSTGRES_USER / POSTGRES_DB)
|
||||
docker compose --env-file .env -p classroomio -f docker-compose.images.yaml \
|
||||
exec -T postgres pg_dump -U postgres classroomio | gzip > backups/db.sql.gz
|
||||
|
||||
# MinIO volume
|
||||
docker run --rm -v classroomio_minio-data:/data:ro -v "$PWD/backups":/backup \
|
||||
alpine tar czf /backup/minio.tar.gz -C /data .
|
||||
```
|
||||
|
||||
## Restoring
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### Stop the app services
|
||||
|
||||
Keep Postgres running, stop everything that writes to it:
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose --env-file .env -p classroomio -f docker-compose.images.yaml stop api dashboard jobs
|
||||
```
|
||||
</Step>
|
||||
<Step>
|
||||
### Restore the database
|
||||
|
||||
```zsh title="Terminal"
|
||||
gunzip -c backups/classroomio-db-<timestamp>.sql.gz | \
|
||||
docker compose --env-file .env -p classroomio -f docker-compose.images.yaml \
|
||||
exec -T postgres psql -U postgres classroomio
|
||||
```
|
||||
|
||||
Restoring into a non-empty database can conflict with existing rows. For a clean restore, drop and
|
||||
recreate the database first (`dropdb`/`createdb` inside the postgres container), then run the
|
||||
command above.
|
||||
</Step>
|
||||
<Step>
|
||||
### Restore object storage
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker run --rm -v classroomio_minio-data:/data -v "$PWD/backups":/backup \
|
||||
alpine sh -c "cd /data && tar xzf /backup/classroomio-minio-<timestamp>.tar.gz"
|
||||
```
|
||||
</Step>
|
||||
<Step>
|
||||
### Start everything again
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh start
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
:::danger[The fastest way to lose data is a cleanup command]
|
||||
`docker compose down -v` and `docker volume prune` **delete** the `postgres-data`, `redis-data`,
|
||||
and `minio-data` volumes. Never run them on a production host. See
|
||||
[Troubleshooting → disk space](/self-hosted/troubleshooting#no-space-left-on-device) for safe
|
||||
alternatives.
|
||||
:::
|
||||
@@ -4,8 +4,8 @@ description: Allow the external CDN, font, media, and frame domains your self-ho
|
||||
---
|
||||
|
||||
When `PUBLIC_IS_SELFHOSTED=true`, the dashboard ships a strict Content Security Policy that allows only
|
||||
`'self'`. If you load assets from external domains — a CDN, custom fonts, embedded video, an external
|
||||
media/storage host — you must add those domains, or the browser blocks them.
|
||||
`'self'`. If you load assets from external domains (a CDN, custom fonts, embedded video, an external
|
||||
media/storage host), you must add those domains, or the browser blocks them.
|
||||
|
||||
## Variables
|
||||
|
||||
@@ -29,13 +29,13 @@ ALLOWED_EXTERNAL_DOMAINS=https://fonts.googleapis.com,https://storage.yourdomain
|
||||
CSP_MEDIA_SRC_DOMAINS=https://storage.yourdomain.com
|
||||
```
|
||||
|
||||
:::note[Runtime — no rebuild needed]
|
||||
CSP domain variables are read at **container startup**. Change them and restart the container — you do
|
||||
:::note[Runtime: no rebuild needed]
|
||||
CSP domain variables are read at **container startup**. Change them and restart the container. You do
|
||||
not need to rebuild any image.
|
||||
:::
|
||||
|
||||
:::warning[You don't need to add the API]
|
||||
Normal dashboard traffic is proxied to the API **same-origin**, so the API host does not need to be in
|
||||
the CSP. Add external hosts only — your CDN, video provider, font host, and object-storage/media domain
|
||||
the CSP. Add external hosts only: your CDN, video provider, font host, and object-storage/media domain
|
||||
(the most common one, so browsers can load uploaded media).
|
||||
:::
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Email (SMTP)
|
||||
description: Configure SMTP so ClassroomIO can send signup verification, password reset, and invite emails.
|
||||
---
|
||||
|
||||
ClassroomIO sends transactional email — **signup verification, password resets, and invite emails** —
|
||||
ClassroomIO sends transactional email (**signup verification, password resets, and invite emails**)
|
||||
over SMTP. Without it the app still runs, but it can't send any of those messages, so have mail
|
||||
credentials ready before you deploy.
|
||||
|
||||
@@ -33,6 +33,6 @@ whole compose stack. The jobs worker won't send invites or notifications if SMTP
|
||||
|
||||
## Providers
|
||||
|
||||
Any standard SMTP provider works — including transactional services such as **Zoho / ZeptoMail**,
|
||||
Any standard SMTP provider works, including transactional services such as **Zoho / ZeptoMail**,
|
||||
Postmark, Resend (SMTP), Amazon SES, or Mailgun. See the
|
||||
[Nodemailer SMTP docs](https://nodemailer.com/smtp/) for provider-specific setup and port/TLS options.
|
||||
|
||||
@@ -3,20 +3,21 @@ title: Configuration
|
||||
description: Shared configuration reference for self-hosting ClassroomIO — secrets, email, storage, CSP, integrations, licensing, and versions.
|
||||
---
|
||||
|
||||
However you deploy ClassroomIO — [Docker](/self-hosted/docker), [Railway](/self-hosted/railway), or
|
||||
[Coolify](/self-hosted/coolify) — the stack reads the **same set of environment variables**. This
|
||||
section is the single source of truth for what each group of variables does. The provider guides show
|
||||
you *where* to set them for that platform; these pages explain *what* to set and *why*.
|
||||
However you deploy ClassroomIO ([Docker](/self-hosted/docker), [DigitalOcean](/self-hosted/digitalocean),
|
||||
[Railway](/self-hosted/railway), [Coolify](/self-hosted/coolify), or [Dokploy](/self-hosted/dokploy)),
|
||||
the stack reads the **same set of environment variables**. This section is the single source of truth
|
||||
for what each group of variables does. The provider guides show you *where* to set them for that
|
||||
platform; these pages explain *what* to set and *why*.
|
||||
|
||||
## How to read these pages
|
||||
|
||||
ClassroomIO is configured entirely through environment variables. On Docker they live in one root
|
||||
`.env` file that every `docker compose` command reads via `--env-file .env`; on Railway and Coolify you
|
||||
set the same variables on each service.
|
||||
`.env` file that every `docker compose` command reads via `--env-file .env`; on PaaS platforms
|
||||
(Railway, Coolify, Dokploy) you set the same variables on each service.
|
||||
|
||||
:::note[Value = required, blank = optional]
|
||||
Throughout these pages and the provider guides, a variable shown **with a value** is one you should
|
||||
set for a working deployment. A variable left **blank after `=`** is optional — fill it in only if you
|
||||
set for a working deployment. A variable left **blank after `=`** is optional: fill it in only if you
|
||||
use that feature. Leaving an optional variable blank never causes a build or boot error.
|
||||
:::
|
||||
|
||||
@@ -24,7 +25,7 @@ use that feature. Leaving an optional variable blank never causes a build or boo
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Environment & secrets" href="/self-hosted/configuration/secrets" icon="key-round">
|
||||
Generate `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` — the stack won't boot without them.
|
||||
Generate `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY`: the stack won't boot without them.
|
||||
</Card>
|
||||
<Card title="Email (SMTP)" href="/self-hosted/configuration/email" icon="mail">
|
||||
SMTP for signup verification, password resets, and invite emails.
|
||||
@@ -38,7 +39,7 @@ use that feature. Leaving an optional variable blank never causes a build or boo
|
||||
<Card title="Integrations" href="/self-hosted/configuration/integrations" icon="plug">
|
||||
Google OAuth, Unsplash, OpenAI transcription and AI course generation.
|
||||
</Card>
|
||||
<Card title="SSO & licensing" href="/self-hosted/configuration/sso" icon="id-card">
|
||||
<Card title="Enterprise & licensing" href="/self-hosted/enterprise" icon="id-card">
|
||||
Set `LICENSE_KEY` to unlock Enterprise features such as SSO.
|
||||
</Card>
|
||||
<Card title="Versions & upgrades" href="/self-hosted/configuration/versions" icon="git-branch">
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Integrations
|
||||
description: Optional integrations for self-hosted ClassroomIO — Google OAuth, Unsplash, OpenAI, and branding.
|
||||
---
|
||||
|
||||
These integrations are all **optional** — ClassroomIO runs without any of them. Add a variable only if
|
||||
These integrations are all **optional**: ClassroomIO runs without any of them. Add a variable only if
|
||||
you use that feature; leaving it blank never causes a build or boot error.
|
||||
|
||||
## Google OAuth
|
||||
@@ -43,6 +43,6 @@ Enables the in-app Unsplash image search.
|
||||
| `PRIVATE_APP_SUBDOMAINS` | Subdomains served by the dashboard |
|
||||
|
||||
:::note[Enterprise features]
|
||||
`LICENSE_KEY` unlocks Enterprise-only integrations such as SSO — see
|
||||
[SSO & licensing](/self-hosted/configuration/sso).
|
||||
`LICENSE_KEY` unlocks Enterprise-only integrations such as SSO. See
|
||||
[Enterprise & licensing](/self-hosted/enterprise).
|
||||
:::
|
||||
|
||||
@@ -16,23 +16,23 @@ openssl rand -hex 32 # PRIVATE_SERVER_KEY (must be identical in the api and
|
||||
| Variable | What it's for | Where it goes |
|
||||
|----------|---------------|---------------|
|
||||
| `BETTER_AUTH_SECRET` | Signs authentication sessions. | The **api** service. |
|
||||
| `PRIVATE_SERVER_KEY` | Authenticates server-side dashboard → API requests. | **Both** the api and dashboard — the value must be **identical** in each. |
|
||||
| `PRIVATE_SERVER_KEY` | Authenticates server-side dashboard → API requests. | **Both** the api and dashboard; the value must be **identical** in each. |
|
||||
|
||||
:::danger[The stack won't start without them]
|
||||
The compose file **fails fast** if `BETTER_AUTH_SECRET` or `PRIVATE_SERVER_KEY` is empty — the
|
||||
The compose file **fails fast** if `BETTER_AUTH_SECRET` or `PRIVATE_SERVER_KEY` is empty: the
|
||||
containers refuse to boot rather than run with a predictable, guessable session-signing secret. Set
|
||||
both to a strong random value (e.g. `openssl rand -hex 32`).
|
||||
:::
|
||||
|
||||
:::note[Who generates them]
|
||||
Only the source-build path's `run-docker-full-stack.sh` auto-generates these when missing. The
|
||||
pre-built image path, Railway, and Coolify do **not** run that script — you generate the secrets
|
||||
yourself and paste them in.
|
||||
On Docker, `./classroomio.sh` auto-generates both secrets when they're missing (on install and
|
||||
start, for the pull and build paths alike). Railway, Coolify, and Dokploy do **not** run that
|
||||
script. There you generate the secrets yourself and paste them in.
|
||||
:::
|
||||
|
||||
## Database credentials
|
||||
|
||||
Postgres runs inside the stack (or as a managed plugin on Railway). Set a strong password — the
|
||||
Postgres runs inside the stack (or as a managed plugin on Railway). Set a strong password. The
|
||||
defaults are for local use only.
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -41,6 +41,26 @@ defaults are for local use only.
|
||||
| `POSTGRES_USER` | Database user | `postgres` |
|
||||
| `POSTGRES_PASSWORD` | Database password — set a **strong** value in production | `postgres` |
|
||||
|
||||
### Rotating `POSTGRES_PASSWORD` after install
|
||||
|
||||
:::danger[Editing `.env` alone doesn't rotate it]
|
||||
The official Postgres image only reads `POSTGRES_PASSWORD` when it initializes a **fresh,
|
||||
empty** data directory. Once `./classroomio.sh install` has run once, the data volume
|
||||
already exists — changing `POSTGRES_PASSWORD` in `.env` afterward and restarting does
|
||||
**not** change the database's actual password. It only changes the password `api`/`jobs`
|
||||
*attempt* to connect with, so a mismatch breaks every DB connection.
|
||||
|
||||
To actually rotate it after install:
|
||||
|
||||
1. Set the new password inside Postgres itself:
|
||||
```zsh title="Terminal"
|
||||
docker compose --env-file .env -p classroomio -f docker-compose.images.yaml \
|
||||
exec postgres psql -U postgres -c "ALTER ROLE postgres WITH PASSWORD '<new-password>';"
|
||||
```
|
||||
2. Update `POSTGRES_PASSWORD` in `.env` to the same value.
|
||||
3. Run `./classroomio.sh restart` to recreate `api`/`jobs`/`postgres` with the matching value.
|
||||
:::
|
||||
|
||||
## Mode & routing basics
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -50,7 +70,7 @@ defaults are for local use only.
|
||||
| `DASHBOARD_ORIGIN` | Public dashboard origin, used as the auth base URL and in invite/email links | `http://localhost:3082` |
|
||||
|
||||
:::warning[Use an `https://` origin in production]
|
||||
`DASHBOARD_ORIGIN` (and `ORIGIN` on the dashboard) must be the real `https://` URL your users visit —
|
||||
`DASHBOARD_ORIGIN` (and `ORIGIN` on the dashboard) must be the real `https://` URL your users visit:
|
||||
auth session cookies are `Secure`, so login silently fails over plain HTTP on any public domain. See
|
||||
[Self Hosting → HTTPS is required in production](/self-hosted#https-is-required-in-production).
|
||||
:::
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: SSO & licensing
|
||||
description: Set LICENSE_KEY to unlock Enterprise features such as SSO on your self-hosted ClassroomIO.
|
||||
---
|
||||
|
||||
Enterprise-only features — **SSO**, **token auth**, and no-tracking mode — require an Enterprise
|
||||
**license key**. If you don't use any Enterprise feature, you don't need a key; leave `LICENSE_KEY`
|
||||
unset and everything else works.
|
||||
|
||||
## Setting `LICENSE_KEY`
|
||||
|
||||
`LICENSE_KEY` is read by the **api** service. Set it wherever that service takes its environment:
|
||||
|
||||
| Deployment | Where to set it |
|
||||
|------------|-----------------|
|
||||
| Local development | `apps/api/.env` |
|
||||
| Docker Compose | repository root `.env` |
|
||||
| Railway | the `cio-api` service environment |
|
||||
| Coolify | the resource's environment variables |
|
||||
|
||||
```zsh title=".env"
|
||||
LICENSE_KEY=<your enterprise license key>
|
||||
```
|
||||
|
||||
## Getting a license
|
||||
|
||||
[Book a call](http://cal.com/classroomio/enterprise) to get an Enterprise license key.
|
||||
|
||||
## Feature docs
|
||||
|
||||
Once the key is set, configure the features themselves:
|
||||
|
||||
- [Enterprise SSO](/enterprise-sso) — Single Sign-On with OIDC (Okta, Azure AD, and more).
|
||||
- [Token Auth](/token-auth) — issue signed tokens to log users in from your own app.
|
||||
@@ -7,7 +7,7 @@ ClassroomIO stores uploaded files and videos in **object storage**. You have two
|
||||
**bundled MinIO** alongside the stack, or point ClassroomIO at an **external S3-compatible** service
|
||||
(AWS S3, Cloudflare R2, etc.). Both speak the S3 API and use the same `OBJECT_STORAGE_*` variables.
|
||||
|
||||
## Option 1 — Bundled MinIO (default)
|
||||
## Option 1: Bundled MinIO (default)
|
||||
|
||||
The Docker and Coolify compose files include **MinIO** under the `minio` profile, and the startup
|
||||
script auto-configures it. You only change these for custom credentials.
|
||||
@@ -23,17 +23,26 @@ script auto-configures it. You only change these for custom credentials.
|
||||
| `OBJECT_STORAGE_FORCE_PATH_STYLE` | `true` (required for MinIO) |
|
||||
| `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL` | `http://localhost:9000/media` |
|
||||
|
||||
:::warning[MinIO is your durability boundary]
|
||||
Single-node MinIO has no redundancy — its volume is the only thing protecting your uploads. Give it
|
||||
**persistent storage** and **back it up**. If you'd rather not operate storage yourself, use external
|
||||
**Cloudflare R2 / AWS S3** instead (R2 has no egress fees) — see below.
|
||||
:::note[Changing your domain later]
|
||||
If you change `DASHBOARD_ORIGIN` after Install, `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL` is
|
||||
re-derived from it automatically the next time you run `./classroomio.sh restart` (or
|
||||
`start`/`upgrade`). If you've bundled MinIO but manually pinned
|
||||
`OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL` to a custom media CDN, note that a domain change will
|
||||
overwrite it back to `<DASHBOARD_ORIGIN>/media` — re-set it afterward if you need the custom
|
||||
value.
|
||||
:::
|
||||
|
||||
## Option 2 — External S3 / Cloudflare R2
|
||||
:::warning[MinIO is your durability boundary]
|
||||
Single-node MinIO has no redundancy: its volume is the only thing protecting your uploads. Give it
|
||||
**persistent storage** and **back it up**. If you'd rather not operate storage yourself, use external
|
||||
**Cloudflare R2 / AWS S3** instead (R2 has no egress fees); see below.
|
||||
:::
|
||||
|
||||
## Option 2: External S3 / Cloudflare R2
|
||||
|
||||
The `OBJECT_STORAGE_*` variables speak the S3 API, so they work with **any S3-compatible provider**,
|
||||
including Cloudflare R2 and AWS S3. Point them at the provider and start **without** the bundled MinIO
|
||||
(`./run-docker-full-stack.sh --no-minio`, or leave the `minio` profile off).
|
||||
(`./classroomio.sh start --no-minio`, or leave the `minio` profile off).
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
@@ -48,7 +57,7 @@ including Cloudflare R2 and AWS S3. Point them at the provider and start **witho
|
||||
:::note[Legacy `CLOUDFLARE_*` variables]
|
||||
ClassroomIO also accepts a legacy Cloudflare R2 config (`CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_ACCESS_KEY`,
|
||||
`CLOUDFLARE_SECRET_ACCESS_KEY`, …). It is a **fallback** used only when the `OBJECT_STORAGE_*` variables
|
||||
are absent — prefer `OBJECT_STORAGE_*` for new deployments (it covers R2 too). For step-by-step R2 bucket
|
||||
are absent; prefer `OBJECT_STORAGE_*` for new deployments (it covers R2 too). For step-by-step R2 bucket
|
||||
and CORS setup, see the [Cloudflare Setup](/guides/contributing/cloudflare-setup) guide.
|
||||
:::
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ ClassroomIO publishes versioned images. `CIO_VERSION` controls which tag you run
|
||||
|
||||
- **Pin an exact release** (e.g. `1.0.0`) in production, so a merge to `main` never restarts you onto
|
||||
changed images unattended.
|
||||
- `latest` is the freshest `main` build — it moves on **every merge** (rolling; may include unreleased
|
||||
- `latest` is the freshest `main` build: it moves on **every merge** (rolling; may include unreleased
|
||||
code). The version tags (`1.0.0`, `1.0`, `1`) are the immutable, pinned releases.
|
||||
|
||||
```zsh title=".env"
|
||||
@@ -25,7 +25,13 @@ Before deploying, confirm the tag you set actually exists at
|
||||
|
||||
### Pre-built images (Docker)
|
||||
|
||||
Bump `CIO_VERSION` in `.env`, then re-pull and recreate:
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh upgrade
|
||||
```
|
||||
|
||||
The script [backs up](/self-hosted/backups) Postgres and MinIO first, lets you pin a new
|
||||
`CIO_VERSION`, then re-pulls and recreates. Running compose by hand is equivalent (minus the
|
||||
backup): bump `CIO_VERSION` in `.env`, then:
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose -f docker-compose.images.yaml --env-file .env --profile minio pull
|
||||
@@ -34,12 +40,15 @@ docker compose -f docker-compose.images.yaml --env-file .env --profile minio up
|
||||
|
||||
### Build from source (Docker)
|
||||
|
||||
`git pull`, then re-run `./run-docker-full-stack.sh --build` to rebuild.
|
||||
`git pull`, then re-run `./classroomio.sh upgrade --build` to rebuild. Like the pre-built path,
|
||||
this backs up Postgres and MinIO first, then rebuilds and restarts. (`start --build` also
|
||||
rebuilds, but skips the backup step — use `upgrade --build` when upgrading an existing
|
||||
deployment with real data.)
|
||||
|
||||
### Railway / Coolify
|
||||
### Railway / Coolify / Dokploy / DigitalOcean
|
||||
|
||||
Set `CIO_VERSION` to the newer release and redeploy. Because you pin a version, a new release never
|
||||
restarts you onto changed images unattended.
|
||||
Set `CIO_VERSION` to the newer release and redeploy (on a DigitalOcean-style VPS: `dc pull && dc up -d`).
|
||||
Because you pin a version, a new release never restarts you onto changed images unattended.
|
||||
|
||||
## Rolling back
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Coolify
|
||||
description: Deploy ClassroomIO on Coolify using the pull-based Docker Compose stack, with a video walkthrough.
|
||||
---
|
||||
|
||||
[Coolify](https://coolify.io) is a self-hosted PaaS (an open-source Heroku/Netlify alternative) you run on your own VPS. It runs ClassroomIO from the published `classroomio/*` images — your Coolify host **pulls** them and never builds anything.
|
||||
[Coolify](https://coolify.io) is a self-hosted PaaS (an open-source Heroku/Netlify alternative) you run on your own VPS. It runs ClassroomIO from the published `classroomio/*` images: your Coolify host **pulls** them and never builds anything.
|
||||
|
||||
## Watch the walkthrough
|
||||
|
||||
@@ -40,7 +40,7 @@ This uses a **Docker Compose resource**: you paste the compose file and set ever
|
||||
<Step>
|
||||
### Create a project and add a Docker Compose resource
|
||||
|
||||
Log in to Coolify and **create a project**. When you add a resource, Coolify asks what type it is — GitHub, a Docker image, a service, or a **Docker Compose** file. Choose **Docker Compose**, then **paste the contents of [`docker-compose.images.yaml`](https://github.com/classroomio/classroomio/blob/main/docker-compose.images.yaml)** into the editor.
|
||||
Log in to Coolify and **create a project**. When you add a resource, Coolify asks what type it is: GitHub, a Docker image, a service, or a **Docker Compose** file. Choose **Docker Compose**, then **paste the contents of [`docker-compose.images.yaml`](https://github.com/classroomio/classroomio/blob/main/docker-compose.images.yaml)** into the editor.
|
||||
|
||||

|
||||
|
||||
@@ -74,13 +74,13 @@ SMTP_SENDER=ClassroomIO <[email protected]>
|
||||
# Object storage + domains: see the full reference below
|
||||
```
|
||||
|
||||
👉 The essentials above get you started, but the complete list of what to set — object storage, domains, and every optional variable — is in the [**Environment variable reference**](#environment-variable-reference) below. Copy that full block before you deploy.
|
||||
The essentials above get you started, but the complete list of what to set (object storage, domains, and every optional variable) is in the [**Environment variable reference**](#environment-variable-reference) below. Copy that full block before you deploy.
|
||||
|
||||
:::note[How to read the reference: value = required, blank = optional]
|
||||
There's no way to guess which variables Coolify needs, so the [Environment variable
|
||||
reference](#environment-variable-reference) spells it out. The rule is simple: **every variable
|
||||
shown with a value must be set** for a smooth deployment (secrets, database, domains, object
|
||||
storage, and SMTP are all needed). **Every variable left blank after `=` is optional** — fill it in
|
||||
storage, and SMTP are all needed). **Every variable left blank after `=` is optional**: fill it in
|
||||
only if you use that integration. Leaving an optional one blank never causes a build or boot error.
|
||||
:::
|
||||
|
||||
@@ -93,7 +93,7 @@ only if you use that integration. Leaving an optional one blank never causes a b
|
||||
`CIO_VERSION` chooses the tag pulled for `classroomio/{api,dashboard,jobs}`. **Confirm the tag you use actually exists** at [hub.docker.com/r/classroomio](https://hub.docker.com/r/classroomio/) before deploying.
|
||||
|
||||
:::note[Pin a released version]
|
||||
Set `CIO_VERSION=1.0.0` to pin an exact release. `latest` is the freshest `main` build — it moves
|
||||
Set `CIO_VERSION=1.0.0` to pin an exact release. `latest` is the freshest `main` build: it moves
|
||||
on every merge (rolling; may include unreleased code), while the version tags (`1.0.0`, `1.0`, `1`)
|
||||
are immutable, pinned releases. Pinning an exact release makes upgrades deliberate and deploys
|
||||
reproducible.
|
||||
@@ -107,7 +107,7 @@ reproducible.
|
||||
|
||||
Open the **dashboard** service → **Settings** and set its domain, including the internal port, e.g. `https://dash.yourdomain.com:3082`. Coolify maps that FQDN to the container's port `3082` and issues the certificate through its proxy.
|
||||
|
||||
The dashboard proxies API calls server-side, so you normally only expose the dashboard publicly — the `api` service stays internal.
|
||||
The dashboard proxies API calls server-side, so you normally only expose the dashboard publicly. The `api` service stays internal.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -115,7 +115,7 @@ The dashboard proxies API calls server-side, so you normally only expose the das
|
||||
<Step>
|
||||
### Deploy
|
||||
|
||||
Click **Deploy**. Coolify pulls `classroomio/{api,dashboard,jobs}:${CIO_VERSION}`, starts Postgres + Redis, the API container runs database setup on first boot, then the dashboard and the **jobs worker** come up. Watch the **logs** — they tell you when every service is up.
|
||||
Click **Deploy**. Coolify pulls `classroomio/{api,dashboard,jobs}:${CIO_VERSION}`, starts Postgres + Redis, the API container runs database setup on first boot, then the dashboard and the **jobs worker** come up. Watch the **logs**: they tell you when every service is up.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -123,7 +123,7 @@ Click **Deploy**. Coolify pulls `classroomio/{api,dashboard,jobs}:${CIO_VERSION}
|
||||
<Step>
|
||||
### Open the app and sign up
|
||||
|
||||
Once the logs show the stack is healthy, open your dashboard URL (`https://dash.yourdomain.com`) in a new browser tab and **sign up**. The **first** account becomes the organization **admin**. Every signup or sign-in after that joins as a student — unless you invite them with a specific role.
|
||||
Once the logs show the stack is healthy, open your dashboard URL (`https://dash.yourdomain.com`) in a new browser tab and **sign up**. The **first** account becomes the organization **admin**. Every signup or sign-in after that joins as a student, unless you invite them with a specific role.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -132,11 +132,11 @@ Once the logs show the stack is healthy, open your dashboard URL (`https://dash.
|
||||
|
||||
## Environment variable reference
|
||||
|
||||
You set these on the resource's Environment Variables page (Step 3). This is the complete picture — copy the block below, then replace the placeholder values with your own.
|
||||
You set these on the resource's Environment Variables page (Step 3). This is the complete picture: copy the block below, then replace the placeholder values with your own.
|
||||
|
||||
:::note[Value = required, blank = optional]
|
||||
**Every variable shown with a value below is required** for a working deployment. **Every variable
|
||||
left blank after `=` is optional** — fill it in only if you use that feature. A blank optional
|
||||
left blank after `=` is optional**: fill it in only if you use that feature. A blank optional
|
||||
variable never causes a build or boot error, so don't put junk into fields you aren't using.
|
||||
:::
|
||||
|
||||
@@ -184,7 +184,7 @@ SMTP_PASSWORD=<smtp password>
|
||||
SMTP_SENDER=ClassroomIO <[email protected]>
|
||||
|
||||
# ── Optional — leave blank unless you need them ──
|
||||
CIO_VERSION=1.0.0 # pinned release; blank = latest (rolling main build)
|
||||
CIO_VERSION= # pin a release, e.g. 1.0.0; blank = latest (rolling main build)
|
||||
GOOGLE_CLIENT_ID= # Google OAuth sign-in
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
OPENAI_API_KEY= # transcription + AI course generation
|
||||
@@ -201,7 +201,7 @@ OBJECT_STORAGE_REGION= # only for some external S3 providers
|
||||
SKIP_DB_SETUP= # blank = run DB setup on first boot (leave blank)
|
||||
```
|
||||
|
||||
### Required — set all of these
|
||||
### Required: set all of these
|
||||
|
||||
| Variable | What it's for |
|
||||
|----------|---------------|
|
||||
@@ -229,10 +229,10 @@ SKIP_DB_SETUP= # blank = run DB setup on first boot (leave blank)
|
||||
Of the above, `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` are the two the compose file
|
||||
**fails fast** on: leave either empty and the containers refuse to boot (so ClassroomIO never runs
|
||||
with a predictable session-signing secret). The rest won't abort the boot, but the deployment
|
||||
won't work correctly without them — set them all.
|
||||
won't work correctly without them. Set them all.
|
||||
:::
|
||||
|
||||
### Optional — leave blank unless you need them
|
||||
### Optional: leave blank unless you need them
|
||||
|
||||
| Variable | Enables |
|
||||
|----------|---------|
|
||||
@@ -255,7 +255,7 @@ Enable the **bundled MinIO** with `COMPOSE_PROFILES=minio` and Coolify runs it a
|
||||
(`OBJECT_STORAGE_PUBLIC_ENDPOINT` / `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL`) so browsers can load media.
|
||||
|
||||
:::warning[MinIO is your durability boundary]
|
||||
Single-node MinIO has no redundancy — its volume is the only thing protecting your uploads, so give it
|
||||
Single-node MinIO has no redundancy: its volume is the only thing protecting your uploads, so give it
|
||||
persistent storage in Coolify and back it up.
|
||||
:::
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: DigitalOcean
|
||||
description: Deploy ClassroomIO to a DigitalOcean droplet with Docker Compose and Caddy, including automatic HTTPS.
|
||||
---
|
||||
|
||||
Run ClassroomIO on a plain **DigitalOcean droplet** — no PaaS in between. You get the
|
||||
Run ClassroomIO on a plain **DigitalOcean droplet**, no PaaS in between. You get the
|
||||
[pull-based Docker Compose stack](/self-hosted/docker) plus a [Caddy](https://caddyserver.com)
|
||||
reverse proxy that obtains and renews HTTPS certificates automatically.
|
||||
|
||||
@@ -12,7 +12,7 @@ AWS Lightsail**, or any Ubuntu 24.04 server you can SSH into.
|
||||
|
||||
:::note[How this differs from the PaaS guides]
|
||||
[Railway](/self-hosted/railway), [Coolify](/self-hosted/coolify) and [Dokploy](/self-hosted/dokploy)
|
||||
terminate HTTPS for you. On a raw VPS you provide that yourself — this page covers the reverse proxy,
|
||||
terminate HTTPS for you. On a raw VPS you provide that yourself: this page covers the reverse proxy,
|
||||
the certificates, and closing your internal ports off from the internet.
|
||||
:::
|
||||
|
||||
@@ -35,12 +35,12 @@ Prefer to follow along? This video walks through the whole deployment end to end
|
||||
| A | `app` | your droplet's IP |
|
||||
| A | `s3` | your droplet's IP |
|
||||
|
||||
- An **SMTP provider** for signup verification, password resets, and invites — see
|
||||
- An **SMTP provider** for signup verification, password resets, and invites. See
|
||||
[Configuration → Email (SMTP)](/self-hosted/configuration/email).
|
||||
|
||||
:::note[No domain? You can still get real HTTPS]
|
||||
Use [sslip.io](https://sslip.io), a public wildcard-DNS service where `<anything>.<ip>.sslip.io`
|
||||
resolves to that IP — so no records are needed. In Step 1 set
|
||||
resolves to that IP, so no records are needed. In Step 1 set
|
||||
`export DOMAIN=<your-droplet-ip>.sslip.io` instead of your own domain, and **every other command on
|
||||
this page is identical**. Read the [caveat](#a-note-on-sslipio) before using it for anything real.
|
||||
:::
|
||||
@@ -152,7 +152,7 @@ sudo usermod -aG docker cio
|
||||
```
|
||||
|
||||
:::warning[Use the literal username, not `$USER`]
|
||||
`$USER` expands to whoever runs the shell — run it from a root session and it grants **root** rather
|
||||
`$USER` expands to whoever runs the shell. Run it from a root session and it grants **root** rather
|
||||
than your user, and every later Docker command fails with `permission denied` on
|
||||
`/var/run/docker.sock`. Check with `getent group docker`.
|
||||
:::
|
||||
@@ -168,7 +168,7 @@ id | grep -o docker # -> docker
|
||||
docker compose version # must be v2.24.0 or newer
|
||||
```
|
||||
|
||||
Compose **v2.24.0+** is required — Step 6 uses the `!override` YAML tag. Older versions
|
||||
Compose **v2.24.0+** is required: Step 6 uses the `!override` YAML tag. Older versions
|
||||
*concatenate* port lists instead of replacing them, which would leave your internal services exposed
|
||||
to the internet.
|
||||
</Step>
|
||||
@@ -181,8 +181,8 @@ mkdir -p ~/classroomio && cd ~/classroomio
|
||||
curl -O https://raw.githubusercontent.com/classroomio/classroomio/main/docker-compose.images.yaml
|
||||
```
|
||||
|
||||
This is the same [pull-based stack](/self-hosted/docker#run-from-pre-built-images-recommended) used
|
||||
by the other guides — it pulls the published `classroomio/*` images and builds nothing.
|
||||
This is the same [pull-based stack](/self-hosted/docker#quick-start) used
|
||||
by the other guides. It pulls the published `classroomio/*` images and builds nothing.
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
@@ -242,11 +242,11 @@ Then fill in your SMTP credentials with `nano .env`.
|
||||
|
||||
Three of these break deployments more often than all the others combined:
|
||||
|
||||
- **`OBJECT_STORAGE_PUBLIC_ENDPOINT`** — where the browser fetches video from. Left at the template
|
||||
- **`OBJECT_STORAGE_PUBLIC_ENDPOINT`**: where the browser fetches video from. Left at the template
|
||||
default of `localhost`, every video URL points at the *visitor's own machine*.
|
||||
- **`DASHBOARD_ORIGIN`** — must match the browser URL exactly, `https://` included, no trailing
|
||||
- **`DASHBOARD_ORIGIN`**: must match the browser URL exactly, `https://` included, no trailing
|
||||
slash. A mismatch breaks login with CSRF errors.
|
||||
- **`CSP_MEDIA_SRC_DOMAINS` / `CSP_CONNECT_SRC_DOMAINS`** — because storage is on a second hostname,
|
||||
- **`CSP_MEDIA_SRC_DOMAINS` / `CSP_CONNECT_SRC_DOMAINS`**: because storage is on a second hostname,
|
||||
the browser blocks its media unless these allow it. Symptom: the page looks fine, images and video
|
||||
silently don't load, and the only clue is in the browser console.
|
||||
|
||||
@@ -261,7 +261,7 @@ Every remaining variable is documented in [Configuration](/self-hosted/configura
|
||||
<Step>
|
||||
### Add Caddy and close the internal ports
|
||||
|
||||
Right now each part of ClassroomIO runs on its own port number — the dashboard on `3082`, file
|
||||
Right now each part of ClassroomIO runs on its own port number: the dashboard on `3082`, file
|
||||
storage on `9000`. That leaves two problems: nobody wants to visit `yoursite.com:3082`, and neither
|
||||
port is encrypted, so there would be no padlock in the browser.
|
||||
|
||||
@@ -328,14 +328,14 @@ EOF
|
||||
```
|
||||
|
||||
Those eight lines are the entire HTTPS setup. The first time someone visits your site, Caddy asks
|
||||
Let's Encrypt for a free certificate, installs it, and from then on renews it before it expires —
|
||||
you never touch it again.
|
||||
Let's Encrypt for a free certificate, installs it, and from then on renews it before it expires.
|
||||
You never touch it again.
|
||||
|
||||
You could use nginx instead, but you'd also be installing certbot, setting up a renewal timer, and
|
||||
hand-writing the forwarding rules that Caddy applies by default.
|
||||
|
||||
:::warning[Your firewall does not cover Docker]
|
||||
You set up a firewall back in Step 3. It does **not** apply to Docker containers — Docker writes its
|
||||
You set up a firewall back in Step 3. It does **not** apply to Docker containers: Docker writes its
|
||||
own networking rules underneath it, so a container that publishes a port stays reachable from the
|
||||
internet even when `ufw` says that port is blocked.
|
||||
|
||||
@@ -367,7 +367,7 @@ dc pull
|
||||
|
||||
:::warning[This downloads ~12 GB and takes 10–20 minutes]
|
||||
The images are large (`api` 3.7 GB, `dashboard` 3.9 GB, `jobs` 4.2 GB). On a 2-vCPU droplet the
|
||||
**progress bars sit still for minutes at a time — this is normal, it is not frozen.** Don't cancel it.
|
||||
**progress bars sit still for minutes at a time. This is normal, it is not frozen.** Don't cancel it.
|
||||
:::
|
||||
</Step>
|
||||
|
||||
@@ -378,7 +378,7 @@ The images are large (`api` 3.7 GB, `dashboard` 3.9 GB, `jobs` 4.2 GB). On a 2-v
|
||||
dc up -d
|
||||
```
|
||||
|
||||
Services start in dependency order — Postgres and Redis must report healthy before the API starts,
|
||||
Services start in dependency order: Postgres and Redis must report healthy before the API starts,
|
||||
because the API runs database migrations on boot. A full start takes about **40 seconds**.
|
||||
|
||||
```zsh title="Terminal"
|
||||
@@ -396,7 +396,7 @@ cio-postgres Up About a minute (healthy)
|
||||
cio-redis Up About a minute (healthy)
|
||||
```
|
||||
|
||||
`cio-minio-init` is absent on purpose — it creates the storage buckets and exits. Confirm it worked:
|
||||
`cio-minio-init` is absent on purpose: it creates the storage buckets and exits. Confirm it worked:
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker logs cio-minio-init | tail -4
|
||||
@@ -425,19 +425,19 @@ for p in 3081 3082 9000 9001; do
|
||||
done
|
||||
```
|
||||
|
||||
All four must report `no response`. If any is reachable, your Compose version ignored `!override` —
|
||||
check that `docker compose version` is v2.24.0 or newer.
|
||||
All four must report `no response`. If any is reachable, your Compose version ignored `!override`.
|
||||
Check that `docker compose version` is v2.24.0 or newer.
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Sign up
|
||||
|
||||
Open `https://app.<your-domain>` and create the first account — it becomes the organization
|
||||
Open `https://app.<your-domain>` and create the first account: it becomes the organization
|
||||
**admin**. Every signup after that joins as a student unless you invite them with a specific role.
|
||||
|
||||
Then verify uploads end to end: create a course, upload an image, and upload a short video. Watch the
|
||||
worker process it with `dc logs -f jobs`. In your browser's DevTools the media URLs must point at
|
||||
`https://s3.<your-domain>/...` — if you see `localhost:9000`, `OBJECT_STORAGE_PUBLIC_ENDPOINT` is
|
||||
`https://s3.<your-domain>/...`. If you see `localhost:9000`, `OBJECT_STORAGE_PUBLIC_ENDPOINT` is
|
||||
wrong.
|
||||
</Step>
|
||||
|
||||
@@ -453,7 +453,7 @@ dc down # stop, keeping all data
|
||||
```
|
||||
|
||||
:::danger[Never use `dc down -v`]
|
||||
The `-v` flag deletes the `postgres-data`, `redis-data` and `minio-data` volumes — your database and
|
||||
The `-v` flag deletes the `postgres-data`, `redis-data` and `minio-data` volumes: your database and
|
||||
every upload. Plain `dc down` is always safe.
|
||||
:::
|
||||
|
||||
@@ -498,7 +498,7 @@ own. **For production, use your own domain.**
|
||||
```
|
||||
|
||||
3. Change the two site addresses in `~/classroomio/Caddyfile`.
|
||||
4. Apply, then clear cached media URLs — presigned URLs are cached in Redis for 50 minutes, so
|
||||
4. Apply, then clear cached media URLs: presigned URLs are cached in Redis for 50 minutes, so
|
||||
without this the old hostname keeps being served:
|
||||
|
||||
```zsh title="Terminal"
|
||||
@@ -506,10 +506,14 @@ own. **For production, use your own domain.**
|
||||
docker exec cio-redis sh -c "redis-cli --scan --pattern 'presign:download:*' | xargs -r redis-cli del"
|
||||
```
|
||||
|
||||
Existing uploads keep working — media URLs are generated per request from the new endpoint.
|
||||
Existing uploads keep working. Media URLs are generated per request from the new endpoint.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
These are the droplet-specific issues. For general stack problems (sign-in errors right after
|
||||
startup, stuck uploads, OOM kills, disk space), see the shared
|
||||
[Troubleshooting](/self-hosted/troubleshooting) page.
|
||||
|
||||
| Symptom | Cause |
|
||||
| ------- | ----- |
|
||||
| `permission denied` on `/var/run/docker.sock` | Your user isn't in the `docker` group, or you haven't logged out and back in since adding it |
|
||||
@@ -549,8 +553,8 @@ This deployment reads the same variables as every other self-hosted install:
|
||||
|
||||
## Upgrades
|
||||
|
||||
Check which versions exist, then pin one — never guess a tag, as an unpublished one fails the pull
|
||||
with `manifest unknown`:
|
||||
Check which versions exist, then pin one: never guess a tag, since an unpublished one fails the pull
|
||||
with `manifest unknown`.
|
||||
|
||||
```zsh title="Terminal"
|
||||
curl -s "https://hub.docker.com/v2/repositories/classroomio/dashboard/tags?page_size=20&ordering=last_updated" \
|
||||
@@ -561,5 +565,5 @@ dc pull && dc up -d
|
||||
```
|
||||
|
||||
Migrations run automatically on API startup. Roll back by setting `CIO_VERSION` to the previous
|
||||
release and repeating — which is why you pin an exact version rather than `latest`. See
|
||||
release and repeating, which is why you pin an exact version rather than `latest`. See
|
||||
[Configuration → Versions & upgrades](/self-hosted/configuration/versions).
|
||||
|
||||
@@ -1,37 +1,129 @@
|
||||
---
|
||||
title: Docker
|
||||
description: Self-host ClassroomIO with Docker — pull pre-built images (recommended) or build from source.
|
||||
description: Self-host ClassroomIO with Docker — one script installs, starts, upgrades, and backs up the whole stack.
|
||||
---
|
||||
|
||||
Docker is one way to self-host ClassroomIO. There are two paths:
|
||||
|
||||
- **Run from pre-built images (recommended)** — pull the published `classroomio/{api,dashboard,jobs}` images. You only need one compose file and a `.env`; no repository clone and no heavy local build.
|
||||
- **Build from source** — clone the repo and build the images yourself (needs ~8 GB RAM for the dashboard build).
|
||||
Run ClassroomIO anywhere Docker runs. One script manages the entire lifecycle: you copy it,
|
||||
run it, and pick a number. New to self-hosting? Start with
|
||||
[Self-hosting 101](/self-hosted/101) to see what you're deploying.
|
||||
|
||||

|
||||
|
||||
## Run from Pre-built Images (recommended)
|
||||
## Prerequisites
|
||||
|
||||
### Prerequisites
|
||||
- Docker installed and running (Compose v2 or the legacy `docker-compose` both work).
|
||||
- A server that meets the [requirements](/self-hosted#server-requirements) (8 GB RAM recommended).
|
||||
|
||||
- Docker installed and running.
|
||||
## Quick start
|
||||
|
||||
### Steps
|
||||
|
||||
1. Download the pull-based compose file and an env template:
|
||||
<Steps>
|
||||
<Step>
|
||||
### Get the script
|
||||
|
||||
```zsh title="Terminal"
|
||||
curl -O https://raw.githubusercontent.com/classroomio/classroomio/main/docker-compose.images.yaml
|
||||
curl -o .env https://raw.githubusercontent.com/classroomio/classroomio/main/.env.example
|
||||
mkdir classroomio && cd classroomio
|
||||
curl -fsSLO https://classroomio.com/classroomio.sh
|
||||
chmod +x classroomio.sh
|
||||
```
|
||||
</Step>
|
||||
<Step>
|
||||
### Run it and pick Install
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh
|
||||
```
|
||||
|
||||
2. Edit `.env`. At minimum set `CIO_VERSION` (pin a release), `DASHBOARD_ORIGIN`, the auth secrets, and SMTP:
|
||||
Choose **1) Install**. The script downloads the compose file, creates `.env` from the template,
|
||||
generates the auth secrets and MinIO credentials for you, then pulls the images and starts the
|
||||
stack. Re-running Install is safe: it never overwrites an existing `.env` or compose file.
|
||||
</Step>
|
||||
<Step>
|
||||
### Edit the required values
|
||||
|
||||
Everything else is auto-generated or defaulted. For a real deployment, set only:
|
||||
|
||||
```zsh title=".env"
|
||||
CIO_VERSION=1.0.0 # pin a release for production
|
||||
DASHBOARD_ORIGIN=https://app.yourdomain.com # your public https:// URL
|
||||
POSTGRES_PASSWORD=<strong password> # required in production
|
||||
SMTP_HOST= # email — signup, resets, invites
|
||||
SMTP_PORT=
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_SENDER=
|
||||
```
|
||||
|
||||
Then apply: `./classroomio.sh restart`
|
||||
|
||||
:::warning[Changing `POSTGRES_PASSWORD` after Install does nothing on its own]
|
||||
Postgres only applies `POSTGRES_PASSWORD` the first time it initializes its data volume. If
|
||||
you're editing `.env` after Install has already run, see [rotating it after
|
||||
install](/self-hosted/configuration/secrets#rotating-postgres_password-after-install) —
|
||||
just restarting/recreating containers will break the DB connection instead of fixing it.
|
||||
:::
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The dashboard is at http://localhost:3082, the API at http://localhost:3081, and the MinIO console
|
||||
at http://localhost:9001 (credentials are in your `.env`).
|
||||
|
||||
:::warning[Pin `CIO_VERSION` in production]
|
||||
The default tags track moving builds: `latest` changes on every merge to `main`. Pin an exact
|
||||
release (e.g. `CIO_VERSION=1.4.2`) so an upstream merge can never change your deployment
|
||||
unannounced. The script warns you about this too. See
|
||||
[Versions & upgrades](/self-hosted/configuration/versions).
|
||||
:::
|
||||
|
||||
:::note[Serving on a real domain?]
|
||||
Use an `https://` origin: session cookies are `Secure`, so
|
||||
[login silently fails over plain HTTP](/self-hosted#https-is-required-in-production).
|
||||
:::
|
||||
|
||||
## The menu
|
||||
|
||||
| Option | What it does |
|
||||
|--------|--------------|
|
||||
| 1 Install | First-time setup: fetch files, create `.env`, generate secrets, pull and start. |
|
||||
| 2 Start | Start the stack (also fills in any missing secrets first). |
|
||||
| 3 Stop | Stop containers. Data volumes are kept. |
|
||||
| 4 Restart | Restart containers, e.g. after editing `.env`. |
|
||||
| 5 Upgrade | **Backs up first**, then pulls newer images and restarts. |
|
||||
| 6 View logs | Follow logs for all services or one (`./classroomio.sh logs api`). |
|
||||
| 7 Backup | Dump Postgres and archive the MinIO volume into `./backups/`. See [Backups](/self-hosted/backups). |
|
||||
|
||||
Every option is also a subcommand (`./classroomio.sh upgrade`), so you can script or cron it.
|
||||
|
||||
<Expandable title="Build from source instead of pulling images">
|
||||
|
||||
To run local code changes, clone the repo and use the same script with `--build`
|
||||
(needs `git` and ~8 GB RAM for the dashboard build):
|
||||
|
||||
```zsh title="Terminal"
|
||||
git clone https://github.com/classroomio/classroomio.git
|
||||
cd classroomio
|
||||
cp .env.example .env
|
||||
./classroomio.sh start --build
|
||||
```
|
||||
|
||||
Without `--build` the script pulls the published `classroomio/*` images at your `CIO_VERSION`,
|
||||
which is the faster default. `--no-minio` excludes the bundled MinIO if you use external
|
||||
[S3/R2 storage](/self-hosted/configuration/storage).
|
||||
|
||||
</Expandable>
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Docker self-hosting uses a single root `.env` file: every compose command reads it via
|
||||
`--env-file .env`, and the compose file itself tracks `main` (it's just the stack topology;
|
||||
`CIO_VERSION` pins the app images).
|
||||
|
||||
<Expandable title="Common .env values">
|
||||
|
||||
```zsh title=".env"
|
||||
CIO_VERSION=1.0.0 # pin a release (not "latest") so upgrades are deliberate
|
||||
DASHBOARD_ORIGIN=https://app.yourdomain.com
|
||||
BETTER_AUTH_SECRET=<openssl rand -hex 32>
|
||||
PRIVATE_SERVER_KEY=<openssl rand -hex 32> # must be identical in the api and dashboard
|
||||
BETTER_AUTH_SECRET= # auto-generated by the script if left blank
|
||||
PRIVATE_SERVER_KEY= # auto-generated; must be identical in api and dashboard
|
||||
POSTGRES_PASSWORD=<strong password>
|
||||
# Email (required for signup/reset/invites)
|
||||
SMTP_HOST=
|
||||
@@ -41,78 +133,13 @@ SMTP_PASSWORD=
|
||||
SMTP_SENDER=
|
||||
```
|
||||
|
||||
:::danger[Set strong secrets — the stack won't start without them]
|
||||
This path does **not** run `run-docker-full-stack.sh`, so secrets are not auto-generated. You must set
|
||||
`BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` yourself, or the compose file fails fast. See
|
||||
[Configuration → Environment & secrets](/self-hosted/configuration/secrets).
|
||||
:::
|
||||
See [`.env.example`](https://github.com/classroomio/classroomio/blob/main/.env.example) for the
|
||||
full annotated template.
|
||||
|
||||
3. Pull and start the stack (MinIO is included via the `minio` profile):
|
||||
</Expandable>
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose -f docker-compose.images.yaml --env-file .env --profile minio pull
|
||||
docker compose -f docker-compose.images.yaml --env-file .env --profile minio up -d
|
||||
```
|
||||
|
||||
4. Verify:
|
||||
|
||||
```zsh title="Terminal"
|
||||
curl -sS http://localhost:3081/ # API — returns JSON
|
||||
curl -I http://localhost:3082/ # Dashboard — returns 200
|
||||
```
|
||||
|
||||
- Dashboard: http://localhost:3082
|
||||
- API: http://localhost:3081
|
||||
- MinIO Console: http://localhost:9001 (log in with `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` from your `.env`)
|
||||
|
||||
## Build from Source
|
||||
|
||||
Use this if you want to build the images locally (e.g. to run code changes).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker Desktop installed and running
|
||||
- `git` installed
|
||||
- ~8 GB RAM available for the dashboard build
|
||||
|
||||
### Steps
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```zsh title="Terminal"
|
||||
git clone https://github.com/classroomio/classroomio.git
|
||||
cd classroomio
|
||||
```
|
||||
|
||||
2. Copy the env template and edit it:
|
||||
|
||||
```zsh title="Terminal"
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open `.env` and set `DASHBOARD_ORIGIN` for your domain. For local development the defaults work as-is — the startup script generates `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` when they are missing, and randomizes the MinIO credentials.
|
||||
|
||||
3. Build and start the full stack:
|
||||
|
||||
```zsh title="Terminal"
|
||||
./run-docker-full-stack.sh --build
|
||||
```
|
||||
|
||||
The script builds and starts Postgres, Redis, MinIO (object storage), the API, the dashboard, and the background **jobs** worker.
|
||||
|
||||
:::note[Default is pull, not build]
|
||||
Run without `--build` and the script **pulls** the published `classroomio/*` images at the
|
||||
`CIO_VERSION` in your `.env` instead of building locally — that's the faster default path.
|
||||
:::
|
||||
|
||||
## One `.env`, One Command
|
||||
|
||||
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.
|
||||
|
||||
## Configuration
|
||||
|
||||
Every variable the stack reads is documented once in the shared
|
||||
[Configuration](/self-hosted/configuration) section — set the ones you need in your root `.env`:
|
||||
Every variable the stack reads is documented in the shared
|
||||
[Configuration](/self-hosted/configuration) section:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Environment & secrets" href="/self-hosted/configuration/secrets" icon="key-round">
|
||||
@@ -130,51 +157,15 @@ Every variable the stack reads is documented once in the shared
|
||||
<Card title="Integrations" href="/self-hosted/configuration/integrations" icon="plug">
|
||||
Google OAuth, Unsplash, OpenAI, branding.
|
||||
</Card>
|
||||
<Card title="SSO & licensing" href="/self-hosted/configuration/sso" icon="id-card">
|
||||
`LICENSE_KEY` for Enterprise features.
|
||||
<Card title="Enterprise & licensing" href="/self-hosted/enterprise" icon="id-card">
|
||||
`LICENSE_KEY` for SSO, token auth, and no-tracking.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Upgrading
|
||||
|
||||
ClassroomIO publishes versioned images and `CIO_VERSION` controls which tag you run. To upgrade the
|
||||
pre-built images path, bump `CIO_VERSION` and re-pull:
|
||||
Run `./classroomio.sh upgrade`: it backs up Postgres and MinIO first, then pulls the images at
|
||||
your (possibly newly pinned) `CIO_VERSION` and restarts. Rollback = set `CIO_VERSION` back and
|
||||
upgrade again. Details: [Versions & upgrades](/self-hosted/configuration/versions).
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker compose -f docker-compose.images.yaml --env-file .env --profile minio pull
|
||||
docker compose -f docker-compose.images.yaml --env-file .env --profile minio up -d
|
||||
```
|
||||
|
||||
For the build-from-source path, `git pull` and re-run `./run-docker-full-stack.sh --build`. See
|
||||
[Configuration → Versions & upgrades](/self-hosted/configuration/versions) for pinning, rollback, and
|
||||
the pre-upgrade backup steps.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```zsh title="Terminal"
|
||||
# Start the full stack (pulls pre-built images by default)
|
||||
./run-docker-full-stack.sh
|
||||
|
||||
# Build from source instead of pulling
|
||||
./run-docker-full-stack.sh --build
|
||||
|
||||
# Start without MinIO (requires an external S3-compatible store)
|
||||
./run-docker-full-stack.sh --no-minio
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
:::warning[Volume prune deletes data]
|
||||
Avoid `docker volume prune` after `docker compose ... down` — it can remove your database and storage volumes.
|
||||
:::
|
||||
|
||||
Then restart: `./run-docker-full-stack.sh`
|
||||
Something not working? See [Troubleshooting](/self-hosted/troubleshooting).
|
||||
|
||||
@@ -3,10 +3,10 @@ title: Dokploy
|
||||
description: Deploy ClassroomIO on Dokploy using the pull-based Docker Compose stack.
|
||||
---
|
||||
|
||||
[Dokploy](https://dokploy.com) is an open-source, self-hosted PaaS — a Heroku/Vercel alternative you run
|
||||
on your own VPS. It deploys ClassroomIO from the published `classroomio/*` images using the **same
|
||||
[Dokploy](https://dokploy.com) is an open-source, self-hosted PaaS (a Heroku/Vercel alternative you run
|
||||
on your own VPS). It deploys ClassroomIO from the published `classroomio/*` images using the **same
|
||||
pull-based Docker Compose file** as the [Docker](/self-hosted/docker) and [Coolify](/self-hosted/coolify)
|
||||
guides — Dokploy **pulls** the images and never builds anything.
|
||||
guides. Dokploy **pulls** the images and never builds anything.
|
||||
|
||||
## Watch the walkthrough
|
||||
|
||||
@@ -17,18 +17,18 @@ steps below mirror it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A running **Dokploy instance** with a **server connected** to it. Dokploy runs on any VPS —
|
||||
- A running **Dokploy instance** with a **server connected** to it. Dokploy runs on any VPS:
|
||||
DigitalOcean, Hetzner, AWS (Lightsail/EC2), or another provider. See Dokploy's
|
||||
[installation guide](https://docs.dokploy.com/docs/core/installation) to set this up.
|
||||
- A server sized for ClassroomIO. Dokploy's own baseline stack needs ~2 GB RAM, and ClassroomIO's
|
||||
seven services need **8 GB for reliable operation** — provision for both. See
|
||||
seven services need **8 GB for reliable operation**, so provision for both. See
|
||||
[Server requirements](/self-hosted#server-requirements).
|
||||
- A **domain** with a DNS **A record pointing at your server's public IP**. Dokploy's built-in Traefik
|
||||
proxy terminates HTTPS and issues the certificate.
|
||||
- An **SMTP provider** for signup verification, password reset, and invite emails — see
|
||||
- An **SMTP provider** for signup verification, password reset, and invite emails. See
|
||||
[Configuration → Email (SMTP)](/self-hosted/configuration/email).
|
||||
- *Optional:* an **OpenAI API key** (transcription + AI course generation) and Google OAuth / Unsplash
|
||||
keys — see [Configuration → Integrations](/self-hosted/configuration/integrations).
|
||||
keys. See [Configuration → Integrations](/self-hosted/configuration/integrations).
|
||||
|
||||
:::note[Two secrets to generate first]
|
||||
Dokploy does **not** run our secret-generation script, so generate `BETTER_AUTH_SECRET` and
|
||||
@@ -64,7 +64,7 @@ In the Dokploy dashboard, open **Projects** and click **Create Project**. Give i
|
||||
### Add a Compose service
|
||||
|
||||
Inside the project, click **Create Service** and choose **Compose** from the service types. When
|
||||
prompted, **select the server** you connected to Dokploy — this is the VPS ClassroomIO runs on.
|
||||
prompted, **select the server** you connected to Dokploy. This is the VPS ClassroomIO runs on.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -72,10 +72,10 @@ prompted, **select the server** you connected to Dokploy — this is the VPS Cla
|
||||
<Step>
|
||||
### Choose the Raw (manual) provider and paste the compose file
|
||||
|
||||
Dokploy asks where the compose file comes from — GitHub, GitLab, Bitbucket, or **Raw**. Choose **Raw**
|
||||
Dokploy asks where the compose file comes from: GitHub, GitLab, Bitbucket, or **Raw**. Choose **Raw**
|
||||
(the manual option) and paste the contents of
|
||||
[`docker-compose.images.yaml`](https://github.com/classroomio/classroomio/blob/main/docker-compose.images.yaml)
|
||||
— the same pull-based stack used for the [Docker](/self-hosted/docker) and [Coolify](/self-hosted/coolify)
|
||||
[`docker-compose.images.yaml`](https://github.com/classroomio/classroomio/blob/main/docker-compose.images.yaml),
|
||||
the same pull-based stack used for the [Docker](/self-hosted/docker) and [Coolify](/self-hosted/coolify)
|
||||
deployments.
|
||||
|
||||

|
||||
@@ -101,7 +101,7 @@ SMTP_PASSWORD=<smtp password>
|
||||
SMTP_SENDER=ClassroomIO <[email protected]>
|
||||
```
|
||||
|
||||
The full variable reference — object storage, CSP, integrations, licensing, versions — lives in the
|
||||
The full variable reference (object storage, CSP, integrations, licensing, versions) lives in the
|
||||
shared [Configuration](/self-hosted/configuration) section. Copy what you need before deploying.
|
||||
|
||||

|
||||
@@ -111,7 +111,7 @@ shared [Configuration](/self-hosted/configuration) section. Copy what you need b
|
||||
### Point your domain at the server
|
||||
|
||||
Before the domain will work, add a DNS **A record** for it (e.g. `dash.yourdomain.com`) pointing at
|
||||
your server's public IP. Do this for the dashboard domain — and any other domain you plan to use (such
|
||||
your server's public IP. Do this for the dashboard domain, and any other domain you plan to use (such
|
||||
as storage). DNS can take a few minutes to propagate.
|
||||
|
||||
</Step>
|
||||
@@ -121,7 +121,7 @@ as storage). DNS can take a few minutes to propagate.
|
||||
|
||||
Click **Deploy**. Dokploy pulls `classroomio/{api,dashboard,jobs}` plus Postgres, Redis, and MinIO,
|
||||
then starts the stack. The API runs database setup on first boot. Watch the **logs** until every
|
||||
service is up — this is your first deploy.
|
||||
service is up. This is your first deploy.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -131,12 +131,12 @@ service is up — this is your first deploy.
|
||||
|
||||
Open the service's **Domains** section and click **Add Domain**. In the modal that appears:
|
||||
|
||||
- **Service:** select **dashboard** — the service this domain routes to.
|
||||
- **Service:** select **dashboard**, the service this domain routes to.
|
||||
- **Host:** your dashboard domain, e.g. `dash.yourdomain.com`.
|
||||
- **Container Port:** `3082` — the port the dashboard listens on.
|
||||
- **Container Port:** `3082`, the port the dashboard listens on.
|
||||
- **HTTPS:** turn it on, then choose **Let's Encrypt** as the certificate provider.
|
||||
|
||||
Create the domain. Dokploy provisions the certificate through Traefik — it usually only takes a moment.
|
||||
Create the domain. Dokploy provisions the certificate through Traefik, which usually only takes a moment.
|
||||
|
||||

|
||||
</Step>
|
||||
@@ -174,7 +174,7 @@ shared [Configuration](/self-hosted/configuration) section:
|
||||
<Card title="Integrations" href="/self-hosted/configuration/integrations" icon="plug">
|
||||
Google OAuth, Unsplash, OpenAI.
|
||||
</Card>
|
||||
<Card title="SSO & licensing" href="/self-hosted/configuration/sso" icon="id-card">
|
||||
<Card title="Enterprise & licensing" href="/self-hosted/enterprise" icon="id-card">
|
||||
`LICENSE_KEY` for Enterprise features.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Enterprise & licensing
|
||||
description: What a ClassroomIO enterprise license unlocks on self-hosted — SSO, token auth, no-tracking — and how LICENSE_KEY validation works.
|
||||
---
|
||||
|
||||
ClassroomIO is [open core](/self-hosted/101#how-licensing-works): the complete LMS is free to
|
||||
self-host, and a small set of enterprise features is unlocked by a paid license key. No key means
|
||||
everything else still works, so leave `LICENSE_KEY` unset until you need one of these.
|
||||
|
||||
## What a license unlocks
|
||||
|
||||
| Feature | What it does |
|
||||
|---------|--------------|
|
||||
| **SSO** | Single Sign-On with OIDC (Okta, Azure AD, and more). Setup: [Enterprise SSO](/enterprise-sso). |
|
||||
| **Token auth** | Issue signed tokens to log users in from your own app. Setup: [Token Auth](/token-auth). |
|
||||
| **No-tracking** | Disables product analytics in the dashboard (PostHog, Umami) and third-party embeds. Without this feature, self-hosted mode already disables error reporting and the feedback widget, but not product analytics. |
|
||||
|
||||
Without a valid license, the SSO and token-auth APIs return `403 FEATURE_REQUIRES_LICENSE` and
|
||||
their settings are hidden in the dashboard UI.
|
||||
|
||||
## Setting `LICENSE_KEY`
|
||||
|
||||
`LICENSE_KEY` is read by the **api** service. Set it wherever that service takes its environment:
|
||||
|
||||
| Deployment | Where to set it |
|
||||
|------------|-----------------|
|
||||
| Docker | repository root `.env` |
|
||||
| Railway | the `cio-api` service environment |
|
||||
| Coolify / Dokploy | the resource's environment variables |
|
||||
| Local development | `apps/api/.env` |
|
||||
|
||||
```zsh title=".env"
|
||||
LICENSE_KEY=<your enterprise license key>
|
||||
```
|
||||
|
||||
Restart the stack with `./classroomio.sh restart` after setting it (this recreates the
|
||||
`api` container so it picks up the new value — `docker compose restart api` on its own
|
||||
does **not**, since it doesn't recreate the container).
|
||||
|
||||
## How validation works
|
||||
|
||||
The api service validates the key against ClassroomIO's license server
|
||||
(`enterprise-api.classroomio.dev`) and caches the result in memory for **one hour**.
|
||||
|
||||
What that means operationally:
|
||||
|
||||
- **The api container needs outbound HTTPS** to that host. In an air-gapped or egress-restricted
|
||||
network, licensed features can't activate. Contact ClassroomIO about your environment before
|
||||
buying.
|
||||
- If a check fails (network blip, license server unreachable), licensed features switch off and
|
||||
the failure is also cached, so it can take **up to an hour** (or an api restart) after
|
||||
connectivity returns before they come back.
|
||||
- Everything outside the licensed feature list is unaffected by license-server reachability.
|
||||
|
||||
## Getting a license
|
||||
|
||||
[Book a call](https://cal.com/classroomio/enterprise) to get an enterprise license key.
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
title: Self Hosting
|
||||
description: Run ClassroomIO on your own infrastructure — with Docker, DigitalOcean, Railway, Coolify, or Dokploy.
|
||||
sidebar:
|
||||
label: Overview
|
||||
---
|
||||
|
||||
ClassroomIO is open source, and you can run the whole platform on your own infrastructure. Every
|
||||
option below runs the same stack — the API, the dashboard, and the background jobs worker, backed by
|
||||
option below runs the same stack: the API, the dashboard, and the background jobs worker, backed by
|
||||
Postgres and Redis.
|
||||
|
||||
Pick the path that matches where you want it to run.
|
||||
@@ -14,7 +16,7 @@ Pick the path that matches where you want it to run.
|
||||
Run anywhere Docker runs. Pull the pre-built images, or build from source.
|
||||
</Card>
|
||||
<Card title="DigitalOcean" href="/self-hosted/digitalocean" icon="droplet">
|
||||
A plain VPS, end to end — Docker Compose plus Caddy for automatic HTTPS.
|
||||
A plain VPS, end to end: Docker Compose plus Caddy for automatic HTTPS.
|
||||
</Card>
|
||||
<Card title="Railway" href="/self-hosted/railway" icon="train-front">
|
||||
Deploy to Railway from a template. The quickest managed option.
|
||||
@@ -49,16 +51,18 @@ pick, have mail credentials ready before you start.
|
||||
- Object storage (**MinIO** or S3-compatible) if you want file and video uploads.
|
||||
- *Optional:* an **OpenAI API key** for transcription and AI course generation.
|
||||
|
||||
For the full environment variable reference — secrets, email, storage, CSP, integrations, licensing,
|
||||
and versions — see the [Configuration](/self-hosted/configuration) section, which documents every
|
||||
variable the stack reads.
|
||||
The [Configuration](/self-hosted/configuration) section has the full environment variable reference
|
||||
(secrets, email, storage, CSP, integrations, licensing, versions) and documents every variable the
|
||||
stack reads.
|
||||
|
||||
## Server requirements
|
||||
|
||||
ClassroomIO is not a single container — it runs **seven services**: Postgres, Redis, MinIO (object
|
||||
storage), the API, the dashboard, and a background **jobs worker** that handles video processing,
|
||||
transcription, and AI course generation. The jobs worker spikes RAM whenever it runs, so a server's
|
||||
idle memory use understates what you actually need.
|
||||
ClassroomIO is not a single container. It runs **seven services**: Postgres, Redis, MinIO (object
|
||||
storage), the API, the dashboard, a background **jobs worker** that handles video processing,
|
||||
transcription, and AI course generation, and **`minio-init`**, a one-shot container that creates
|
||||
the MinIO buckets on first start and then exits, so it won't show up in `docker ps` afterward. The
|
||||
jobs worker spikes RAM whenever it runs, so a server's idle memory use understates what you
|
||||
actually need.
|
||||
|
||||
| Resource | Minimum | Recommended |
|
||||
| -------- | ---------------- | ------------- |
|
||||
@@ -67,31 +71,30 @@ idle memory use understates what you actually need.
|
||||
| Disk | 20 GB + uploads | 40 GB+ SSD |
|
||||
|
||||
:::warning[Don't under-provision RAM]
|
||||
A **1 GB server cannot run ClassroomIO at all** — it exhausts memory on the base stack before the
|
||||
app is even up. **2 GB is only enough for a PaaS control plane** (Coolify, Dokploy) with little left
|
||||
over for the app. Target **8 GB for reliable operation**, especially once the jobs worker starts
|
||||
processing video or AI generation.
|
||||
A **1 GB** server can't run ClassroomIO at all: it exhausts memory on the base stack before the app
|
||||
is even up. **2 GB** is only enough for a PaaS control plane (Coolify, Dokploy), with little left
|
||||
over for the app itself. Aim for **8 GB** for reliable operation, especially once the jobs worker
|
||||
starts processing video or AI generation.
|
||||
:::
|
||||
|
||||
:::note[Running a PaaS on the same box?]
|
||||
Coolify and Dokploy need roughly **2 GB for themselves** on top of ClassroomIO's requirement. If
|
||||
they share the server, size it for both — e.g. 8 GB for ClassroomIO plus the PaaS overhead.
|
||||
Coolify and Dokploy need roughly **2 GB** for themselves on top of ClassroomIO's requirement. If
|
||||
they share the server, size it for both, e.g. 8 GB for ClassroomIO plus the PaaS overhead.
|
||||
:::
|
||||
|
||||
Building the images **from source** is a separate, heavier job that needs ~8 GB RAM at build time —
|
||||
see the [Docker guide](/self-hosted/docker#build-from-source). Pulling the pre-built images (the
|
||||
default) avoids that.
|
||||
Building the images **from source** is a separate, heavier job that needs ~8 GB RAM at build time.
|
||||
See the [Docker guide](/self-hosted/docker). Pulling the pre-built images (the default) avoids that.
|
||||
|
||||
## HTTPS is required in production
|
||||
|
||||
:::warning[Serve over HTTPS on any real domain]
|
||||
ClassroomIO's authentication session cookies are marked `Secure`, so browsers refuse to store or
|
||||
send them over plain HTTP — **login silently fails**. Plain HTTP works only for local evaluation on
|
||||
send them over plain HTTP. **Login silently fails**. Plain HTTP works only for local evaluation on
|
||||
`localhost`. Any internet-facing deployment **must** be served over HTTPS.
|
||||
:::
|
||||
|
||||
Terminate TLS at a reverse proxy in front of the stack:
|
||||
|
||||
- **Railway** and **Coolify** issue the certificate and terminate HTTPS for you — nothing extra to do.
|
||||
- **Railway** and **Coolify** issue the certificate and terminate HTTPS for you: nothing extra to do.
|
||||
- **Raw Docker** on your own server: put **nginx**, **Caddy**, or **Traefik** in front, and set
|
||||
`DASHBOARD_ORIGIN` / `ORIGIN` to the `https://` URL your users visit.
|
||||
|
||||
@@ -15,7 +15,7 @@ Prefer to follow along? This video walks through the whole deployment end to end
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A **Railway account** (a paid plan is recommended — the stack runs several always-on services).
|
||||
- A **Railway account** (a paid plan is recommended, since the stack runs several always-on services).
|
||||
- An **SMTP provider** (or Zoho/ZeptoMail) for signup verification, password reset, and invite emails. See [SMTP Setup](#smtp-setup).
|
||||
- *Optional:* a **custom domain** for the dashboard and storage, an **OpenAI API key** (transcription + AI course generation), and Google OAuth / Unsplash keys.
|
||||
|
||||
@@ -32,7 +32,7 @@ See [Configuration → Environment & secrets](/self-hosted/configuration/secrets
|
||||
|
||||
## Step-by-step walkthrough
|
||||
|
||||
The one-click template provisions **all of the services** for you — Postgres, Redis, `cio-api`, `cio-dashboard`, `cio-jobs`, and `cio-minio` (object storage). The flow is: **configure a few values → deploy → connect the dashboard domain → sign up**.
|
||||
The one-click template provisions **all of the services** for you: Postgres, Redis, `cio-api`, `cio-dashboard`, `cio-jobs`, and `cio-minio` (object storage). The flow is: **configure a few values → deploy → connect the dashboard domain → sign up**.
|
||||
|
||||
|
||||
<Steps>
|
||||
@@ -49,10 +49,10 @@ Click the **Deploy on Railway** button above. Railway opens the template page wh
|
||||
<Step>
|
||||
### Review the preconfigured services
|
||||
|
||||
The template comes with every service preconfigured — most need nothing from you:
|
||||
The template comes with every service preconfigured: most need nothing from you:
|
||||
|
||||
- **Redis** and **Postgres** — no configuration required.
|
||||
- **cio-dashboard** — no required configuration (optionally set branding such as the public OG image; not required).
|
||||
- **Redis** and **Postgres**: no configuration required.
|
||||
- **cio-dashboard**: no required configuration (optionally set branding such as the public OG image; not required).
|
||||
|
||||
The next three steps cover the services that **do** need input: `cio-jobs`, `cio-minio`, and `cio-api`.
|
||||
|
||||
@@ -78,7 +78,7 @@ The next three steps cover the services that **do** need input: `cio-jobs`, `cio
|
||||
|
||||
Set the storage credentials on `cio-minio`:
|
||||
|
||||
- `MINIO_SERVER_URL` — optional but recommended (fixes MinIO console redirects behind the proxy); the template presets it to the generated `cio-minio` domain, so you normally just confirm it
|
||||
- `MINIO_SERVER_URL`: optional but recommended (fixes MinIO console redirects behind the proxy); the template presets it to the generated `cio-minio` domain, so you normally just confirm it
|
||||
- `MINIO_ROOT_USER`
|
||||
- `MINIO_ROOT_PASSWORD`
|
||||
|
||||
@@ -97,16 +97,16 @@ On `cio-api`, set the SMTP host and any additional values you want. See the [Env
|
||||
<Step>
|
||||
### Deploy
|
||||
|
||||
Click **Deploy**. Railway begins building and bringing the services online — Redis, Postgres, and the rest come up first.
|
||||
Click **Deploy**. Railway begins building and bringing the services online: Redis, Postgres, and the rest come up first.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### First boot may crash — this is expected
|
||||
### First boot may crash: this is expected
|
||||
|
||||
:::warning[Only if you use the Railway-generated subdomain]
|
||||
On the first deploy, `cio-api` can crash. This is expected **only when you rely on the Railway-generated subdomain**: the API needs the dashboard's public domain (Better Auth's base URL), which doesn't exist yet. If you're using your **own custom subdomain**, this crash doesn't happen — skip ahead to signing up.
|
||||
On the first deploy, `cio-api` can crash. This is expected **only when you rely on the Railway-generated subdomain**: the API needs the dashboard's public domain (Better Auth's base URL), which doesn't exist yet. If you're using your **own custom subdomain**, this crash doesn't happen. Skip ahead to signing up.
|
||||
:::
|
||||
|
||||

|
||||
@@ -141,7 +141,7 @@ Open the generated dashboard URL in a **new browser tab** and **sign up** to cre
|
||||
|
||||
## Environment variable reference
|
||||
|
||||
You set these values **while configuring each service on the template page** (the configure steps above) — you don't enter them again after deploying. Use this section as the reference for what each service's variables should be.
|
||||
You set these values **while configuring each service on the template page** (the configure steps above); you don't enter them again after deploying. Use this section as the reference for what each service's variables should be.
|
||||
|
||||
### cio-api
|
||||
|
||||
@@ -198,7 +198,7 @@ Optional: add `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, `PUBLIC_OG_IMAGE_URL
|
||||
|
||||
### cio-jobs (background worker)
|
||||
|
||||
The jobs worker has **no public domain** — it drains the Redis queue (video processing, transcription, AI course generation, and emails). It shares the same Postgres, Redis, storage, email, and OpenAI settings as the API:
|
||||
The jobs worker has **no public domain**: it drains the Redis queue (video processing, transcription, AI course generation, and emails). It shares the same Postgres, Redis, storage, email, and OpenAI settings as the API:
|
||||
|
||||
```zsh title="env"
|
||||
DATABASE_URL="${{db.DATABASE_URL}}"
|
||||
@@ -225,7 +225,7 @@ OPENAI_API_KEY=
|
||||
|
||||
### cio-minio (object storage)
|
||||
|
||||
`cio-minio` ships **in the template** — its `/data` volume is already attached and the `videos`,
|
||||
`cio-minio` ships **in the template**: its `/data` volume is already attached and the `videos`,
|
||||
`documents`, and `media` buckets are already seeded. You only set its credentials during
|
||||
configuration:
|
||||
|
||||
@@ -239,11 +239,11 @@ MINIO_SERVER_URL=https://${{cio-minio.RAILWAY_PUBLIC_DOMAIN}}
|
||||
|
||||
The api and jobs services reach MinIO over **two** endpoints: the private `OBJECT_STORAGE_ENDPOINT`
|
||||
(server uploads/reads) and the public `OBJECT_STORAGE_PUBLIC_ENDPOINT` + `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL`
|
||||
(browser-facing presigned downloads and public images) — already wired in the env blocks above.
|
||||
(browser-facing presigned downloads and public images), already wired in the env blocks above.
|
||||
`OBJECT_STORAGE_FORCE_PATH_STYLE=true` is required for MinIO.
|
||||
|
||||
:::warning[MinIO is your durability boundary]
|
||||
Single-node MinIO has no redundancy — the Railway Volume is the only thing protecting your uploads, so
|
||||
Single-node MinIO has no redundancy: the Railway Volume is the only thing protecting your uploads, so
|
||||
back it up. See [Configuration → Object storage & uploads](/self-hosted/configuration/storage) for
|
||||
external Cloudflare R2 / AWS S3 alternatives.
|
||||
:::
|
||||
@@ -254,7 +254,7 @@ See [Configuration → Email (SMTP)](/self-hosted/configuration/email) for the S
|
||||
|
||||
### SSO (Enterprise)
|
||||
|
||||
SSO requires a `LICENSE_KEY` on the `cio-api` service. See [Configuration → SSO & licensing](/self-hosted/configuration/sso).
|
||||
SSO requires a `LICENSE_KEY` on the `cio-api` service. See [Enterprise & licensing](/self-hosted/enterprise).
|
||||
|
||||
## Upgrades
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Diagnose common self-hosted ClassroomIO failures — sign-in errors, stuck uploads, out-of-memory kills, and disk issues.
|
||||
---
|
||||
|
||||
Start here when something misbehaves. Every entry below is a failure mode we've seen in real
|
||||
deployments, with its cause and fix.
|
||||
|
||||
First, always check what's actually running:
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh logs # follow all service logs
|
||||
docker ps # anything restarting or exited?
|
||||
```
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| Sign-in fails right after startup with a database error (`57P03`) | Postgres is still starting | Wait 30–60 s and retry, [details](#sign-in-fails-with-57p03-right-after-startup) |
|
||||
| Login silently fails on your domain (no error, just back to the login page) | Serving over plain HTTP — session cookies are `Secure` | Serve over HTTPS — [details](/self-hosted#https-is-required-in-production) |
|
||||
| Login loops or CSRF errors | `DASHBOARD_ORIGIN` doesn't match the URL users actually visit | Set it to the exact `https://` origin, restart |
|
||||
| Uploads stuck at "processing" forever, no error | The `jobs` worker isn't running, or Redis is unreachable | [details](#uploads-stuck-at-processing) |
|
||||
| Containers randomly killed, stack unstable | Out of memory — a transcode can exhaust a 4 GB server | [details](#containers-are-oom-killed) |
|
||||
| `No space left on device`, MinIO "minimum free drive threshold" | Disk full (images, build cache, uploads) | [details](#no-space-left-on-device) |
|
||||
| API container exits immediately at boot | Database migration failed | [details](#api-container-exits-at-boot) |
|
||||
| No signup / invite / reset emails, logs show `ECONNREFUSED` | SMTP not configured or wrong host/port | Check [Email settings](/self-hosted/configuration/email) |
|
||||
| Videos never get captions; no transcription | `OPENAI_API_KEY` not set | [details](#transcription-never-happens) |
|
||||
| Crash loops with `read ECONNABORTED` during managed-Redis maintenance | A bug fixed upstream | Upgrade — [details](#crash-loops-during-redis-maintenance) |
|
||||
|
||||
## Sign-in fails with 57P03 right after startup
|
||||
|
||||
**Symptom:** immediately after starting the stack, sign-in returns a database error with Postgres
|
||||
code `57P03` ("the database system is in recovery mode" / "is starting up").
|
||||
|
||||
**Cause:** Postgres reports itself ready (`pg_isready` passes) before it has finished crash
|
||||
recovery, so the API can connect but queries fail.
|
||||
|
||||
**Fix:** wait 30–60 seconds and retry. Recovery completes on its own. Current compose files
|
||||
already guard against this: the Postgres healthcheck requires a successful `SELECT 1`, not just
|
||||
`pg_isready`, so dependent services wait until queries actually work. If you see this repeatedly,
|
||||
your compose file predates that healthcheck. Refresh it:
|
||||
|
||||
```zsh title="Terminal"
|
||||
curl -fsSLO https://raw.githubusercontent.com/classroomio/classroomio/main/docker-compose.images.yaml
|
||||
./classroomio.sh restart
|
||||
```
|
||||
|
||||
## Uploads stuck at "processing"
|
||||
|
||||
Video processing, transcription, AI course generation, and most emails don't run in the API. They
|
||||
are queued in Redis and executed by the separate **jobs** worker. If that worker is down (or the API
|
||||
has no `REDIS_URL`), jobs are created but nothing ever picks them up: uploads sit at "processing"
|
||||
with no error anywhere in the UI.
|
||||
|
||||
```zsh title="Terminal"
|
||||
docker ps | grep jobs # is cio-jobs up?
|
||||
./classroomio.sh logs jobs # any errors?
|
||||
```
|
||||
|
||||
The bundled compose file always starts the worker; on PaaS platforms make sure the jobs service
|
||||
wasn't scaled to zero or crashed on boot.
|
||||
|
||||
## Containers are OOM-killed
|
||||
|
||||
The jobs worker spikes RAM while processing video, enough to get Postgres or other containers
|
||||
killed on a 4 GB server even though the stack idles comfortably. See
|
||||
[server requirements](/self-hosted#server-requirements).
|
||||
|
||||
- Confirm with `docker inspect <container> | grep -i oom` and `free -h`.
|
||||
- Add swap as a buffer, or resize to 8 GB for reliable operation.
|
||||
|
||||
## "No space left on device"
|
||||
|
||||
Also surfaces as MinIO's "Storage reached its minimum free drive threshold".
|
||||
|
||||
```zsh title="Terminal"
|
||||
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
|
||||
```
|
||||
|
||||
Then restart: `./classroomio.sh start`
|
||||
|
||||
:::danger[Never use `docker volume prune` or `down -v`]
|
||||
`docker volume prune` removes volumes not attached to a running container: run after
|
||||
`docker compose down`, it deletes your **database, Redis, and uploads** (`postgres-data`,
|
||||
`redis-data`, `minio-data`). `docker compose down -v` does the same explicitly. Use only the prune
|
||||
commands above to free space.
|
||||
:::
|
||||
|
||||
## API container exits at boot
|
||||
|
||||
The API runs database migrations before it starts listening. If a migration fails, the container
|
||||
exits rather than serve a half-migrated database. That's deliberate.
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh logs api # the migration error is at the end
|
||||
```
|
||||
|
||||
Common causes: wrong `DATABASE_URL`/`POSTGRES_*` credentials after an edit, or Postgres not yet
|
||||
healthy. Fix the cause and `./classroomio.sh start` again. Migrations are safe to re-run.
|
||||
|
||||
## Transcription never happens
|
||||
|
||||
Transcription (video captions) requires `OPENAI_API_KEY` on the **jobs** service. Without it, media
|
||||
jobs still complete (thumbnails and processing work), but the transcription step is skipped with a
|
||||
warning in the jobs logs. Set the key and re-trigger transcription; see
|
||||
[Integrations](/self-hosted/configuration/integrations).
|
||||
|
||||
## Crash loops during Redis maintenance
|
||||
|
||||
Older versions could crash-loop with cascading `read ECONNABORTED` errors when a managed Redis
|
||||
provider (e.g. Redis Cloud) ran scheduled maintenance or failover. This was fixed upstream: the
|
||||
stack now reconnects and resumes automatically. If you hit it, upgrade:
|
||||
|
||||
```zsh title="Terminal"
|
||||
./classroomio.sh upgrade
|
||||
```
|
||||
|
||||
## Platform-specific issues
|
||||
|
||||
Deploying on a DigitalOcean-style VPS with Caddy? That guide has its own
|
||||
[troubleshooting table](/self-hosted/digitalocean#troubleshooting) covering certificates, firewalls,
|
||||
and proxy issues. For anything else, [open an issue](https://github.com/classroomio/classroomio/issues)
|
||||
and include the output of `./classroomio.sh logs`.
|
||||
@@ -12,7 +12,7 @@ Token Auth requires an **Enterprise** plan.
|
||||
|
||||
**Cloud:** [Contact us](mailto:[email protected]) to upgrade.
|
||||
|
||||
**Self-hosted:** Requires an Enterprise license key. Set `LICENSE_KEY` in the API service environment — see [Self Hosting → SSO & licensing](/self-hosted/configuration/sso).
|
||||
**Self-hosted:** Requires an Enterprise license key. Set `LICENSE_KEY` in the API service environment — see [Self Hosting → Enterprise & licensing](/self-hosted/enterprise).
|
||||
:::
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@tailwindcss/typography": "0.5.19",
|
||||
"@tailwindcss/vite": "4.1.18",
|
||||
"astro": "7.0.9",
|
||||
"blume": "1.1.2",
|
||||
"blume": "1.2.0",
|
||||
"mermaid": "^11.16.0",
|
||||
"tailwindcss": "4.1.18"
|
||||
},
|
||||
|
||||
@@ -45,6 +45,8 @@ const REDIRECTS = `# Generated by scripts/package-assets.mjs — do not edit.
|
||||
|
||||
/docs/contributor-guides /docs/guides/contributing 301
|
||||
/docs/contributor-guides/* /docs/guides/contributing/:splat 301
|
||||
|
||||
/docs/self-hosted/configuration/sso /docs/self-hosted/enterprise 301
|
||||
${GUIDE_REDIRECTS}
|
||||
`;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { client } from '$lib/utils/posthog';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const load = ({ request }) => {
|
||||
client.capture({
|
||||
distinctId: request.headers.get('x-forwarded-for') || new Date().getTime().toString(),
|
||||
event: 'classroomio.sh downloaded'
|
||||
});
|
||||
|
||||
redirect(307, 'https://raw.githubusercontent.com/classroomio/classroomio/main/classroomio.sh');
|
||||
};
|
||||
Reference in New Issue
Block a user