Files
classroomio/apps/docs/content/docs/self-hosted/coolify.mdx
T

279 lines
15 KiB
Plaintext
Raw Normal View History

---
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.
## Watch the walkthrough
Prefer to follow along? This video walks through the whole Coolify deployment end to end. The written steps below mirror it, and the [Environment variable reference](#environment-variable-reference) has every value to copy.
{/* TODO: replace REPLACE_WITH_VIDEO_ID with the Coolify walkthrough video id once recorded */}
<YouTube id="5pwzUn8JBws" />
## Prerequisites
- A running **Coolify instance** with a connected server.
- A **domain** with a DNS **A record pointing at your Coolify server's IP**. Coolify's built-in proxy terminates HTTPS and issues the certificate for you.
- An **SMTP provider** (or Zoho/ZeptoMail) for signup verification, password reset, and invite emails. See [Nodemailer SMTP docs](https://nodemailer.com/smtp/).
- *Optional:* an **OpenAI API key** (transcription + AI course generation) and Google OAuth / Unsplash keys.
:::note[Two secrets to generate first]
You'll reuse `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` across services. Coolify does **not**
run our secret-generation script, so generate them once yourself and keep them handy:
```zsh title="Terminal"
openssl rand -hex 32 # BETTER_AUTH_SECRET
openssl rand -hex 32 # PRIVATE_SERVER_KEY (must be identical in the api and dashboard services)
```
See [Configuration → Environment & secrets](/self-hosted/configuration/secrets) for what each does.
:::
## Step-by-step walkthrough
This uses a **Docker Compose resource**: you paste the compose file and set every environment value yourself. The flow is: **create a project → add the compose file → set environment variables → point the dashboard domain → deploy → sign up**.
<Steps>
<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.
![Showing list of options to choose from](/docs/coolify-02-services.webp)
</Step>
<Step>
### Confirm the services parsed
After saving, Coolify parses the compose file and lists the services: `postgres`, `redis`, `api`, `dashboard`, `jobs` (and `minio` once you enable its profile in the next step). Confirm they all appear before moving on.
![Select composer file type](/docs/coolify-01-add-resource.webp)
![Coolify showing the parsed ClassroomIO services](/docs/coolify-02-services.webp)
</Step>
<Step>
### Set the environment variables
Open the resource's **Environment Variables** page. This is where most Coolify deployments go wrong, so treat the [Environment variable reference](#environment-variable-reference) below as the source of truth: **set every variable it lists as required, and leave the optional ones blank.** The block here covers the essentials to get you started; the full copy-paste reference is a few sections down.
```zsh title="Essentials — set all of these"
COMPOSE_PROFILES=minio # runs the bundled MinIO object storage
DASHBOARD_ORIGIN=https://dash.yourdomain.com
BETTER_AUTH_SECRET=<openssl rand -hex 32>
PRIVATE_SERVER_KEY=<openssl rand -hex 32> # must be identical in the api and dashboard services
POSTGRES_PASSWORD=<strong password>
# Email (signup verification, password reset, invites)
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=<smtp username>
SMTP_PASSWORD=<smtp password>
SMTP_SENDER=ClassroomIO <you@yourdomain.com>
# 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.
:::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
only if you use that integration. Leaving an optional one blank never causes a build or boot error.
:::
![Setting the environment variables in Coolify](/docs/coolify-03-env-vars.webp)
</Step>
<Step>
### Confirm the image tag
`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
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.
:::
![Confirming the classroomio image tags on Docker Hub](/docs/coolify-04-image-tag.webp)
</Step>
<Step>
### Set the dashboard domain
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.
![Setting the dashboard service domain in Coolify](/docs/coolify-05-dashboard-domain.webp)
</Step>
<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.
![Coolify deploy logs showing the stack coming up](/docs/coolify-06-deploy-logs.webp)
</Step>
<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.
![The running ClassroomIO dashboard sign-up page](/docs/railway-12-signup.webp)
</Step>
</Steps>
## 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.
:::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
variable never causes a build or boot error, so don't put junk into fields you aren't using.
:::
```zsh title=".env — a complete working Coolify deployment"
# ── Secrets (generate with: openssl rand -hex 32) ──
BETTER_AUTH_SECRET=<openssl rand -hex 32>
PRIVATE_SERVER_KEY=<openssl rand -hex 32> # identical in the api and dashboard services
# ── Object storage profile ──
COMPOSE_PROFILES=minio # runs the bundled MinIO alongside the stack
# ── Database ──
POSTGRES_DB=classroomio
POSTGRES_USER=postgres
POSTGRES_PASSWORD=<strong password>
# ── Domains & routing ──
PUBLIC_IS_SELFHOSTED=true
DASHBOARD_ORIGIN=https://dash.yourdomain.com
PUBLIC_SERVER_URL=https://dash.yourdomain.com
PRIVATE_SERVER_URL=http://api:3081
PRIVATE_APP_HOST=dash.yourdomain.com
PRIVATE_APP_SUBDOMAINS=app
# ── Object storage (MinIO) ──
MINIO_ROOT_USER=<storage access key>
MINIO_ROOT_PASSWORD=<storage secret>
MINIO_SERVER_URL=https://storage.yourdomain.com
OBJECT_STORAGE_ENDPOINT=http://minio:9000
OBJECT_STORAGE_PUBLIC_ENDPOINT=https://storage.yourdomain.com
OBJECT_STORAGE_ACCESS_KEY_ID=<same as MINIO_ROOT_USER>
OBJECT_STORAGE_SECRET_ACCESS_KEY=<same as MINIO_ROOT_PASSWORD>
OBJECT_STORAGE_FORCE_PATH_STYLE=true
OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=https://storage.yourdomain.com/media
OBJECT_STORAGE_BUCKET_VIDEOS=videos
OBJECT_STORAGE_BUCKET_DOCUMENTS=documents
OBJECT_STORAGE_BUCKET_MEDIA=media
CSP_MEDIA_SRC_DOMAINS=https://storage.yourdomain.com # so browsers can load media
# ── Email (SMTP — signup verification, password reset, invites) ──
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=<smtp username>
SMTP_PASSWORD=<smtp password>
SMTP_SENDER=ClassroomIO <you@yourdomain.com>
# ── Optional — leave blank unless you need them ──
CIO_VERSION=1.0.0 # pinned release; blank = latest (rolling main build)
GOOGLE_CLIENT_ID= # Google OAuth sign-in
GOOGLE_CLIENT_SECRET=
OPENAI_API_KEY= # transcription + AI course generation
UNSPLASH_API_KEY= # Unsplash image search
LICENSE_KEY= # enterprise features (SSO)
ALLOWED_EXTERNAL_DOMAINS= # overrides all CSP_*_DOMAINS below
CSP_SCRIPT_SRC_DOMAINS=
CSP_STYLE_SRC_DOMAINS=
CSP_CONNECT_SRC_DOMAINS=
CSP_FRAME_SRC_DOMAINS=
CSP_FONT_SRC_DOMAINS=
TRUSTED_ORIGINS= # only for direct browser-to-API calls (proxy is same-origin)
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
| Variable | What it's for |
|----------|---------------|
| `BETTER_AUTH_SECRET` | Session-signing secret. Generate with `openssl rand -hex 32`. |
| `PRIVATE_SERVER_KEY` | Dashboard-to-API auth key. Must be **identical** in the api and dashboard services. |
| `COMPOSE_PROFILES` | Set to `minio` to run the bundled object storage with the stack. |
| `POSTGRES_DB` / `POSTGRES_USER` / `POSTGRES_PASSWORD` | Database name, user, and a **strong** password. |
| `PUBLIC_IS_SELFHOSTED` | `true` for self-hosted mode. |
| `DASHBOARD_ORIGIN` | Public dashboard URL — used as the auth base URL and in invite/email links. |
| `PUBLIC_SERVER_URL` | Public origin the browser uses (your dashboard domain). |
| `PRIVATE_SERVER_URL` | Internal API URL over the Docker network (`http://api:3081`). |
| `PRIVATE_APP_HOST` / `PRIVATE_APP_SUBDOMAINS` | Dashboard host and subdomain routing. |
| `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` | MinIO credentials — also used as the object-storage access key / secret. |
| `MINIO_SERVER_URL` | Public storage URL (fixes MinIO console redirects behind the proxy). |
| `OBJECT_STORAGE_ENDPOINT` | Internal endpoint the app uploads to (`http://minio:9000`). |
| `OBJECT_STORAGE_PUBLIC_ENDPOINT` | Public storage endpoint browsers download from. |
| `OBJECT_STORAGE_ACCESS_KEY_ID` / `OBJECT_STORAGE_SECRET_ACCESS_KEY` | Storage credentials (match the MinIO root user/password). |
| `OBJECT_STORAGE_FORCE_PATH_STYLE` | `true` — required for MinIO. |
| `OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL` | Public base URL for media (`…/media`). |
| `OBJECT_STORAGE_BUCKET_VIDEOS/DOCUMENTS/MEDIA` | Bucket names (`videos`, `documents`, `media`). |
| `CSP_MEDIA_SRC_DOMAINS` | Your storage domain — otherwise the browser's CSP blocks uploaded media. |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASSWORD` / `SMTP_SENDER` | SMTP server for signup verification, password reset, and invites. |
:::warning[If the stack won't start at all]
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.
:::
### Optional — leave blank unless you need them
| Variable | Enables |
|----------|---------|
| `CIO_VERSION` | Image tag to pull. Blank = `latest` (rolling main); pin an exact release such as `1.0.0` (see the tag step). |
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Google OAuth sign-in. |
| `OPENAI_API_KEY` | Transcription + AI course generation (jobs worker). |
| `UNSPLASH_API_KEY` | Unsplash image search. |
| `LICENSE_KEY` | Enterprise features — SSO requires a license ([book a call](http://cal.com/classroomio/enterprise)). |
| `ALLOWED_EXTERNAL_DOMAINS` | Overrides all `CSP_*_DOMAINS` vars with one comma-separated allowlist. |
| `CSP_SCRIPT_SRC_DOMAINS`, `CSP_STYLE_SRC_DOMAINS`, `CSP_CONNECT_SRC_DOMAINS`, `CSP_FRAME_SRC_DOMAINS`, `CSP_FONT_SRC_DOMAINS` | Per-directive CSP allowlists for extra CDN/font/frame domains. |
| `TRUSTED_ORIGINS` | Extra origins allowed to call the API directly (normal dashboard traffic is same-origin via the proxy). |
| `OBJECT_STORAGE_REGION` | Region for some external S3 providers (not needed for MinIO). |
| `SKIP_DB_SETUP` | Skips first-boot database setup — leave blank so it runs. |
| `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, `PUBLIC_OG_IMAGE_URL` | Custom branding / meta tags. |
### Object storage
Enable the **bundled MinIO** with `COMPOSE_PROFILES=minio` and Coolify runs it alongside the stack.
**Give the MinIO volume persistent storage in Coolify** and route its public URL
(`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
persistent storage in Coolify and back it up.
:::
Prefer managed storage? Point the `OBJECT_STORAGE_*` variables at external S3 / Cloudflare R2 and leave
the `minio` profile off. See [Configuration → Object storage & uploads](/self-hosted/configuration/storage).
### Upload size limits (optional)
Set `UPLOAD_MAX_*_MB` in Coolify's env for both the `api` and `dashboard` services (same values in
each), e.g. `UPLOAD_MAX_DOCUMENT_MB=50`. See
[Configuration → Upload file size limits](/self-hosted/configuration/storage#upload-file-size-limits).
## Upgrades & rollback
Bump `CIO_VERSION` to a newer release and redeploy; roll back by setting it to the previous version and redeploying. Because you pin a version, a new release never restarts you onto broken images unattended. See [Configuration → Versions & upgrades](/self-hosted/configuration/versions) for the full upgrade and backup steps.
:::warning[Back up before upgrading]
Schema migrations run automatically on API startup. Back up your Postgres (and object storage)
before a major upgrade.
:::