2026-03-11 10:53:03 +01:00
# ClassroomIO Docker Self-Hosting
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
## Prerequisites
2026-03-09 11:18:22 +01:00
- Docker Desktop installed and running
- `git` installed
2026-03-11 10:53:03 +01:00
## Quick Start
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
git clone https://github.com/classroomio/classroomio.git
2026-03-09 11:18:22 +01:00
cd classroomio
2026-03-11 10:53:03 +01:00
cp .env.example .env
# Edit .env — set your domain, secrets, and ALLOWED_EXTERNAL_DOMAINS
2026-03-09 11:18:22 +01:00
./run-docker-full-stack.sh
```
2026-03-11 10:53:03 +01:00
The script builds and starts: **postgres** , **redis** , **minio** (object storage), **db-init** (schema setup), **api** (`localhost:3081` ), and **dashboard** (`localhost:3082` ).
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
It auto-generates secure values for `AUTH_BEARER_TOKEN` and `PRIVATE_SERVER_KEY` when they are missing.
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
## Environment Variables
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
All Docker services read from a single root `.env` file. See [`.env.example` ](../../.env.example ) for the full annotated template with required/optional grouping.
Key points:
- **Required:** `PUBLIC_SERVER_URL` , `TRUSTED_ORIGINS` , `BETTER_AUTH_SECRET` , `PUBLIC_IS_SELFHOSTED=true` , `ALLOWED_EXTERNAL_DOMAINS` , `DASHBOARD_ORIGIN` .
2026-03-11 15:52:04 +01:00
- **Auth cookies:** Set `AUTH_COOKIE_DOMAIN` to your root domain (e.g. `.yourdomain.com` ) when API and dashboard use different subdomains. Without it, auth cookies may not be set and login will fail.
2026-03-11 10:53:03 +01:00
- **Auto-generated:** `AUTH_BEARER_TOKEN` , `PRIVATE_SERVER_KEY` (by `./run-docker-full-stack.sh` ).
- **Auto-configured:** All `MINIO_*` / `OBJECT_STORAGE_*` vars (by the startup script).
- **Optional:** Email (SMTP or Zoho), Google OAuth, Unsplash, `LICENSE_KEY` (enterprise).
`PRIVATE_SERVER_KEY` must be the same value in both API and dashboard — the script ensures this.
## Verify
2026-03-09 11:18:22 +01:00
```bash
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml ps
2026-03-11 10:53:03 +01:00
curl -sS http://localhost:3081/ # API — returns JSON
curl -I http://localhost:3082/ # Dashboard — returns 200
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
## Seed Demo Data
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml \
run --rm db-init sh -c "pnpm --filter @cio/db db:setup -- --seed"
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
## Command Reference
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
All commands use this prefix — abbreviated as `dc` below:
```
dc = docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
| Task | Command |
|------|---------|
| Full stack (with MinIO) | `./run-docker-full-stack.sh` |
| Skip image rebuild | `./run-docker-full-stack.sh --no-build` |
| Exclude MinIO | `./run-docker-full-stack.sh --no-minio` |
| API-only smoke test | `dc up --build -d postgres redis db-init api` |
| Service status | `dc ps` |
| Stream logs | `dc logs -f api dashboard` |
| Rebuild one service | `dc up -d --build api` |
| Restart (env change, no rebuild) | `dc restart api dashboard` |
| Stop | `dc down` |
| Stop + delete volumes | `dc down -v` |
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
### Build Images Locally
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
docker build -f docker/Dockerfile.api -t classroomio/api:latest .
docker build -f docker/Dockerfile.dashboard -t classroomio/dashboard:latest .
```
### Inspect Container Env Vars
```bash
docker exec cio-api env | sort
docker exec cio-dashboard env | sort
```
Container names: `cio-postgres` , `cio-redis` , `cio-api` , `cio-dashboard` , `cio-minio` .
## MinIO (Object Storage)
MinIO is included by default. The startup script auto-configures env vars, starts MinIO, and creates `videos` , `documents` , and `media` buckets.
- **API endpoint:** `http://localhost:9000`
- **Web UI:** `http://localhost:9001` (default: `minioadmin` / `minioadmin` )
To start MinIO manually (e.g. after `--no-minio` ):
```bash
docker compose -f docker/docker-compose.yaml --profile minio up -d
```
**Security:** Change `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` in `.env` for production. Use the same values for `OBJECT_STORAGE_ACCESS_KEY_ID` / `OBJECT_STORAGE_SECRET_ACCESS_KEY` .
### MinIO Web UI Not Loading
If port 9001 does not respond, recreate the container:
```bash
docker rm -f cio-minio
docker compose -f docker/docker-compose.yaml --profile minio up -d
```
Data is preserved in the `minio-data` volume.
## Rebuild After Code Changes
This setup builds images from source (no live hot reload). Code changes require a rebuild:
```bash
# One service
2026-03-09 11:18:22 +01:00
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api
2026-03-11 10:53:03 +01:00
# Both (e.g. shared packages/* changed)
2026-03-09 11:18:22 +01:00
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml up -d --build api dashboard
2026-03-11 10:53:03 +01:00
# Full stack
2026-03-09 11:18:22 +01:00
./run-docker-full-stack.sh
```
2026-03-11 10:53:03 +01:00
## Troubleshooting
### "No space left on device"
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
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
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
> **Warning:** `docker volume prune` removes volumes not used by any container. If run after `docker compose ... down`, it deletes `postgres-data`, `redis-data`, and `minio-data`. Use only the prunes above to free space safely.
Then restart: `./run-docker-full-stack.sh`
### "The database system is in recovery mode" (57P03)
Postgres has not finished starting. Wait 30-60 seconds and retry. The compose healthcheck requires a successful `SELECT 1` before dependent services start.
### MinIO "Storage reached its minimum free drive threshold"
Same root cause as disk full. Free space using the steps above and restart MinIO:
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
docker compose --env-file .env -p classroomio -f docker/docker-compose.yaml restart minio
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
### SMTP Errors in Logs
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
`ECONNREFUSED 127.0.0.1:465` means SMTP is not configured. This does not block startup — configure SMTP or Zoho when you need email.
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
### Port Conflicts
2026-03-09 11:18:22 +01:00
```bash
2026-03-11 10:53:03 +01:00
lsof -nP -iTCP:3081 -sTCP:LISTEN
lsof -nP -iTCP:3082 -sTCP:LISTEN
2026-03-09 11:18:22 +01:00
```
2026-03-11 10:53:03 +01:00
## Related
2026-03-09 11:18:22 +01:00
2026-03-11 10:53:03 +01:00
- [Self-hosting docs ](https://classroomio.com/docs/quickstart/self-hosting ) — published guide with Railway + Docker instructions
- [`docker/docs/PUBLISHING_IMAGES.md` ](PUBLISHING_IMAGES.md ) — publishing Docker images to Docker Hub