For published-image deployments, use `docker/docker-compose.prod.yaml` (same services, but `api`, `dashboard`, and `db-init` run from published images instead of local builds).
Notes:
- Postgres and Redis are internal-only in compose (not exposed on host ports by default).
-`db-init` is expected to exit with code `0` after setup.
- If you run seed-only commands, make sure setup has already run once so schema exists.
Create a root `.env` file for compose. For local Docker, you can leave the token values blank and the startup script will generate secure values and keep them synchronized:
```bash
POSTGRES_DB=classroomio
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
BETTER_AUTH_SECRET=replace-with-a-strong-secret
AUTH_BEARER_TOKEN=
PRIVATE_SERVER_KEY=
# Optional: required only for Enterprise-only features (SSO, token-auth, no-tracking)
LICENSE_KEY=
PUBLIC_SERVER_URL=https://api.your-domain.com
PRIVATE_SERVER_URL=http://api:3081
TRUSTED_ORIGINS=https://app.your-domain.com
AUTH_COOKIE_DOMAIN=
```
Optional integrations (if used):
```bash
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASSWORD=
SMTP_SENDER=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
UNSPLASH_API_KEY=
```
Object storage (MinIO included by default; script auto-sets these):
-`./run-docker-full-stack.sh` auto-generates secure `AUTH_BEARER_TOKEN`/`PRIVATE_SERVER_KEY` values when missing or insecure placeholders are used.
-`./run-docker-full-stack.sh` always uses root `.env` (`docker compose --env-file .env ...`) so compose build/runtime variables come from repository-root `.env`.
- API key middleware for dashboard/server-to-server calls validates `PRIVATE_SERVER_KEY`.
-`AUTH_BEARER_TOKEN` is a separate token and is not used by `apiKeyMiddleware`.
- Enterprise-only features (SSO, token-auth, no-tracking) require `LICENSE_KEY`. If you do not use those features, leave it unset.
- Dashboard URL selection is environment-aware: browser calls use `PUBLIC_SERVER_URL`, while SSR/server-side calls use `PRIVATE_SERVER_URL` (fallback: `PUBLIC_SERVER_URL`).
- In Docker, set `PRIVATE_SERVER_URL=http://api:3081` for internal service-to-service traffic, and keep `PUBLIC_SERVER_URL` as your public API URL (do not use `localhost` in hosted deployments).
-`AUTH_COOKIE_DOMAIN` is required for setting cookies. Set it to `.your-domain.com` for cross-subdomain cookies, or to your exact API host when you need host-scoped cookies. Leave empty to use Better Auth defaults.
MinIO is **included by default** when you run `./run-docker-full-stack.sh`. The script auto-configures env vars and starts MinIO + minio-init (creates videos, documents, media buckets).
To start MinIO manually (e.g. if you used `--no-minio` previously):
```bash
docker compose -f docker/docker-compose.yaml --profile minio up -d
| API | `http://localhost:9000` | Used by the ClassroomIO API for S3-compatible storage |
| Web UI | `http://localhost:9001` | Login to browse buckets, manage objects (default: `minioadmin` / `minioadmin`) |
> **Security:** Change the default credentials (`minioadmin` / `minioadmin`) in production. Set `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` in `.env`, and use the same values for `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY`.
### Environment variables
Set in `.env`:
```bash
# MinIO container credentials (must match OBJECT_STORAGE_* below)
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
# When API runs in Docker: use minio (Docker service name)
> **API in Docker:** Use `OBJECT_STORAGE_ENDPOINT=http://minio:9000` so the API container can reach MinIO on the Docker network. Use `OBJECT_STORAGE_ENDPOINT=http://localhost:9000` when the API runs locally (e.g. `pnpm api:dev`).
`MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` configure the MinIO container. Set them to the same values as `OBJECT_STORAGE_ACCESS_KEY_ID` and `OBJECT_STORAGE_SECRET_ACCESS_KEY` when using the built-in MinIO service.
### MinIO troubleshooting
#### Web UI not loading
MinIO assigns a **random port** for the Web Console by default. If the Web UI at `http://localhost:9001` does not load, your container may have been created before we pinned the console port.
**Fix:** Remove the old container and recreate it:
```bash
docker rm -f cio-minio
docker compose -f docker/docker-compose.yaml --profile minio up -d
```
Our `docker-compose.yaml` uses `--console-address ":9001"` so the Web UI consistently listens on port 9001. Your data is stored in the `minio-data` volume and is preserved across container recreation.
#### Container name conflict
If you see `The container name "/cio-minio" is already in use`, remove the existing container first:
```bash
docker rm -f cio-minio
docker compose -f docker/docker-compose.yaml --profile minio up -d
```
## Related Docs
-`docker/docs/commands.md` for quick command snippets
-`docker/docs/PUBLISHING_IMAGES.md` for Docker Hub publishing