diff --git a/.env.example b/.env.example index 56faca5e..8ec0b0c0 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ # - For production, PIN an exact release (e.g. 1.4.2) so a merge to main never auto-breaks you. # "1.4.2" / "1.4" / "1" are immutable, published on a v* git tag. # Rollback = set this back to the previous version, then `... pull && up -d`. -CIO_VERSION=latest +CIO_VERSION=1 # ────────────────────────────────────────────── # Required diff --git a/apps/docs/content/docs/self-hosted/coolify.mdx b/apps/docs/content/docs/self-hosted/coolify.mdx index 907bb790..02c6232c 100644 --- a/apps/docs/content/docs/self-hosted/coolify.mdx +++ b/apps/docs/content/docs/self-hosted/coolify.mdx @@ -1,55 +1,277 @@ --- title: Coolify -description: Deploy ClassroomIO on Coolify using the pull-based Docker Compose stack. +description: Deploy ClassroomIO on Coolify using the pull-based Docker Compose stack, with a video walkthrough. --- import { Callout } from 'fumadocs-ui/components/callout'; +import { Step, Steps } from 'fumadocs-ui/components/steps'; -[Coolify](https://coolify.io) is a self-hosted PaaS (an open-source Heroku/Netlify alternative) you run on your own VPS. It deploys ClassroomIO as a **Docker Compose resource** using the pull-based [`docker-compose.images.yaml`](https://github.com/classroomio/classroomio/blob/main/docker-compose.images.yaml) — so your Coolify host **pulls** the published `classroomio/*` images 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 + +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 */} +
+ +
## Prerequisites -- A running Coolify instance with a connected server. -- A domain pointed at that server (Coolify's built-in proxy terminates HTTPS for you). +- 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. -## Steps + + 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: -1. **New Resource → Docker Compose.** Point Coolify at this repo and the compose path `docker-compose.images.yaml`, or paste the file's contents. + ```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) + ``` + -2. **Set environment variables** (Coolify's UI — these become the compose `.env`). Coolify does **not** run our `run-docker-full-stack.sh`, so generate secrets yourself: +## Step-by-step walkthrough - ```zsh title="Required" - CIO_VERSION=1.4.2 # pin a release (not "latest") so upgrades are deliberate - DASHBOARD_ORIGIN=https://app.yourdomain.com - BETTER_AUTH_SECRET= - PRIVATE_SERVER_KEY= # must be identical in the api and dashboard services - POSTGRES_PASSWORD= - # Email (required for signup/reset/invites) - SMTP_HOST= - SMTP_PORT= - SMTP_USER= - SMTP_PASSWORD= - SMTP_SENDER= - ``` +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**. - - `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` are required. The compose file fails fast if - either is empty, so it never boots with a predictable session-signing secret. - + -3. **Assign a domain.** Map your domain to the **dashboard** service (internal port `3082`); Coolify's reverse proxy issues the certificate. The dashboard proxies API calls server-side, so you normally only need the dashboard exposed publicly — the `api` service stays internal. + +### Create a project and add a Docker Compose resource -4. **Object storage.** Recommended: the **bundled MinIO** — enable its profile with - `COMPOSE_PROFILES=minio` and Coolify runs MinIO 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. Prefer fully-managed - storage instead? Point the same `OBJECT_STORAGE_*` variables at external **S3 / Cloudflare R2** - and leave the `minio` profile off — see the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2. +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. - **Upload size limits** (optional): set `UPLOAD_MAX_*_MB` in Coolify's env for both the `api` and - `dashboard` services (same values in each). See [Docker self-hosting → Upload file size limits](/docs/self-hosted/docker#optional--upload-file-size-limits). +![Showing list of options to choose from](/coolify-02-services.png) -5. **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. + + + +### 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](/coolify-01-add-resource.png) +![Coolify showing the parsed ClassroomIO services](/coolify-02-services.png) + + + +### 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= +PRIVATE_SERVER_KEY= # must be identical in the api and dashboard services +POSTGRES_PASSWORD= +# Email (signup verification, password reset, invites) +SMTP_HOST=smtp.yourprovider.com +SMTP_PORT=587 +SMTP_USER= +SMTP_PASSWORD= +SMTP_SENDER=ClassroomIO +# 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. + + + 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](/coolify-03-env-vars.png) + + + +### 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. + + + 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](/coolify-04-image-tag.png) + + + +### 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](/coolify-05-dashboard-domain.png) + + + +### 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](/coolify-06-deploy-logs.png) + + + +### 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](/railway-12-signup.png) + + + + +## 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. + + + **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= +PRIVATE_SERVER_KEY= # 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= + +# ── 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= +MINIO_ROOT_PASSWORD= +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= +OBJECT_STORAGE_SECRET_ACCESS_KEY= +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_PASSWORD= +SMTP_SENDER=ClassroomIO + +# ── 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. | + + + 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 + +Recommended: the **bundled MinIO** — enable its profile with `COMPOSE_PROFILES=minio` and Coolify +runs MinIO 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. Prefer fully-managed storage instead? Point the same `OBJECT_STORAGE_*` variables +at external **S3 / Cloudflare R2** and leave the `minio` profile off — see the +[Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2. + + + Single-node MinIO has no redundancy — its volume is the only thing protecting your uploads. Give + it persistent storage in Coolify and back it up. If you'd rather not operate storage, use external + **Cloudflare R2 / AWS S3** instead (R2 has no egress fees): point the same `OBJECT_STORAGE_*` + vars at the provider. + + +### 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 +[Docker self-hosting → Upload file size limits](/docs/self-hosted/docker#optional--upload-file-size-limits). ## Upgrades & rollback diff --git a/apps/docs/content/docs/self-hosted/docker.mdx b/apps/docs/content/docs/self-hosted/docker.mdx index c17a5bde..6dcc079d 100644 --- a/apps/docs/content/docs/self-hosted/docker.mdx +++ b/apps/docs/content/docs/self-hosted/docker.mdx @@ -30,7 +30,7 @@ curl -o .env https://raw.githubusercontent.com/classroomio/classroomio/main/.env 2. Edit `.env`. At minimum set `CIO_VERSION` (pin a release), `DASHBOARD_ORIGIN`, the auth secrets, and SMTP: ```zsh title=".env" -CIO_VERSION=1.4.2 # pin a release (not "latest") so upgrades are deliberate +CIO_VERSION=1.0.0 # pin a release (not "latest") so upgrades are deliberate DASHBOARD_ORIGIN=https://app.yourdomain.com BETTER_AUTH_SECRET= PRIVATE_SERVER_KEY= # must be identical in the api and dashboard @@ -112,8 +112,8 @@ The script builds and starts Postgres, Redis, MinIO (object storage), the API, t ClassroomIO publishes versioned images. `CIO_VERSION` controls which tag you run: -- Pin an exact release (e.g. `1.4.2`) in production so a merge to `main` never restarts you onto changed images unattended. -- `latest` = the freshest `main` build — it moves on every merge (rolling; may include unreleased code). Version tags (`1.4.2`, `1.4`, `1`) are the immutable, pinned releases. +- Pin an exact release (e.g. `1.0.0`) in production so a merge to `main` never restarts you onto changed images unattended. +- `latest` = the freshest `main` build — it moves on every merge (rolling; may include unreleased code). Version tags (`1.0.0`, `1.0`, `1`) are the immutable, pinned releases. **Pre-built images path** — bump `CIO_VERSION` in `.env`, then re-pull and recreate: diff --git a/apps/docs/content/docs/self-hosted/railway.mdx b/apps/docs/content/docs/self-hosted/railway.mdx index 36452101..ca0edd10 100644 --- a/apps/docs/content/docs/self-hosted/railway.mdx +++ b/apps/docs/content/docs/self-hosted/railway.mdx @@ -4,14 +4,148 @@ description: Deploy ClassroomIO on Railway with the one-click template. --- import { Callout } from 'fumadocs-ui/components/callout'; +import { Step, Steps } from 'fumadocs-ui/components/steps'; The quickest hosted deployment is the [Railway Template](https://railway.com/deploy/classroomio-1?referralCode=8OyIj5). It provisions the API, dashboard, and jobs worker services plus the Postgres and Redis plugins for you. [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/classroomio-1?referralCode=8OyIj5) -After deploying the template, fill in the environment variables for each service below. +## Watch the walkthrough -## cio-api +Prefer to follow along? This video walks through the whole deployment end to end. The written steps below mirror it, and the [Environment variable reference](#environment-variable-reference) has every value to copy. + +
+ +
+ +## Prerequisites + +- A **Railway account** (a paid plan is recommended — 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. + + + You'll reuse `BETTER_AUTH_SECRET` and `PRIVATE_SERVER_KEY` across services. Generate them once 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 cio-api and cio-dashboard) + ``` + + +## 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**. + + + + + +### Click "Deploy on Railway" + +Click the **Deploy on Railway** button above. Railway opens the template page where all of the ClassroomIO services are listed and ready to configure. + +![Railway template page for ClassroomIO after clicking Deploy](/railway-01-deploy-template.png) + + + +### Review the preconfigured services + +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). + +The next three steps cover the services that **do** need input: `cio-jobs`, `cio-minio`, and `cio-api`. + +![The Railway template listing all preconfigured ClassroomIO services](/railway-02-services-overview.png) + + + +### Configure cio-jobs (SMTP) + +`cio-jobs` sends invites, notifications, verification, and password-reset emails, so it needs SMTP credentials. Fill these in: + +- `SMTP_HOST` +- `SMTP_PORT` +- `SMTP_USER` +- `SMTP_SENDER` +- `SMTP_PASSWORD` + +![Entering the SMTP variables on the cio-jobs service in the template](/railway-03-jobs-smtp.png) + + + +### Configure cio-minio (object storage) + +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_ROOT_USER` +- `MINIO_ROOT_PASSWORD` + +![Configuring MINIO_SERVER_URL and credentials on the cio-minio service](/railway-04-minio-credentials.png) + + + +### Configure cio-api + +On `cio-api`, set the SMTP host and any additional values you want. See the [Environment variable reference](#environment-variable-reference) below for the full list (object storage, auth secrets, dashboard origin, and more). + +![Configuring the cio-api service variables in the template](/railway-05-api-config.png) +![Additional cio-api configuration options](/railway-06-api-config-additional.png) + + + +### Deploy + +Click **Deploy**. Railway begins building and bringing the services online — Redis, Postgres, and the rest come up first. + +![Railway deploying the ClassroomIO services after clicking Deploy](/railway-07-deploying.png) + + + +### First boot may crash — this is expected + + + 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. + + +![cio-api crashing on first boot before the dashboard domain exists](/railway-08-first-boot-crash.png) + + + +### Generate the dashboard domain + +Open **cio-dashboard → Settings → Networking** and click **Generate Domain**. Railway assigns the public domain the API needs. + +![cio-dashboard Settings and Networking with the Generate Domain button](/railway-09-generate-domain.png) + + + +### Redeploy cio-dashboard and cio-api + +Redeploy **cio-dashboard**, then **cio-api**, so both pick up the newly generated domain. They should now boot cleanly. + +![Redeploying the cio-dashboard](/railway-10-redeploy-dashboard.png) +![Redeploying the cio-api](/railway-11-redeploy-api.png) + + + +### Open the app and sign up + +Open the generated dashboard URL in a **new browser tab** and **sign up** to create your first account. + +![The running ClassroomIO dashboard sign-up page on the Railway domain](/railway-12-signup.png) + + + + +## 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. + +### cio-api ```zsh title="env" DATABASE_URL="${{db.DATABASE_URL}}" @@ -47,7 +181,7 @@ OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=https://storage.yourdomain.com/media Optional: add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` for OAuth, `UNSPLASH_API_KEY` for image search, or `LICENSE_KEY` for enterprise features. -## cio-dashboard +### cio-dashboard ```zsh title="env" # RAILWAY_PUBLIC_DOMAIN here = this dashboard service's own public domain. @@ -64,7 +198,7 @@ Optional: add `PUBLIC_APP_TITLE`, `PUBLIC_APP_DESCRIPTION`, `PUBLIC_OG_IMAGE_URL **Upload size limits** (optional): add the same `UPLOAD_MAX_*_MB` values to **cio-api** and **cio-dashboard** (e.g. `UPLOAD_MAX_DOCUMENT_MB=50`). See [Docker self-hosting → Upload file size limits](/docs/self-hosted/docker#optional--upload-file-size-limits). -## cio-jobs (background worker) +### 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: @@ -91,22 +225,18 @@ OPENAI_API_KEY= `PRIVATE_SERVER_KEY` must be the same value in both API and dashboard. It authenticates server-side dashboard requests to the API. Referencing `${{cio-api.PRIVATE_SERVER_KEY}}` in the dashboard keeps them in sync. -## cio-minio (object storage) +### cio-minio (object storage) -We recommend a self-hosted **MinIO** service for object storage. Add it as a new service from -the Docker image `minio/minio:latest`: - -- **Start command:** `server /data --console-address ":9001"` -- **Volume:** attach a Railway Volume mounted at **`/data`** — **required**, or every upload is - wiped on each redeploy. -- **Public domain:** e.g. `storage.yourdomain.com` with **target port `9000`** (the S3 API). - Railway also gives it a private domain (`cio-minio.railway.internal:9000`) for the api/jobs. +`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: ```zsh title="env" MINIO_ROOT_USER= MINIO_ROOT_PASSWORD= -# Optional (silences console redirect warnings behind the proxy): -MINIO_SERVER_URL=https://storage.yourdomain.com +# Recommended (not required — ClassroomIO reaches storage via OBJECT_STORAGE_* below; +# this only fixes MinIO's own console redirects behind the proxy). Template presets it. +MINIO_SERVER_URL=https://${{cio-minio.RAILWAY_PUBLIC_DOMAIN}} ``` The api and jobs services reach MinIO over **two** endpoints: the private `OBJECT_STORAGE_ENDPOINT` @@ -114,28 +244,17 @@ The api and jobs services reach MinIO over **two** endpoints: the private `OBJEC (browser-facing presigned downloads and public images) — already wired in the env blocks above. `OBJECT_STORAGE_FORCE_PATH_STYLE=true` is required for MinIO. -### Seed the buckets (one-time) - -Railway has no `minio-init`, so create the buckets once with [`mc`](https://min.io/docs/minio/linux/reference/minio-mc.html) -(run locally against the public domain): - -```zsh title="Terminal" -mc alias set cio https://storage.yourdomain.com "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" -mc mb cio/videos cio/documents cio/media --ignore-existing -mc anonymous set download cio/media # media bucket = public read (images) -``` - Single-node MinIO has no redundancy — the Railway Volume is the only thing protecting your uploads. Back it up. If you'd rather not operate storage, use external **Cloudflare R2 / AWS S3** - instead (R2 has no egress fees): point the same `OBJECT_STORAGE_*` vars at the provider and skip - the cio-minio service. See the [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2. + instead (R2 has no egress fees): point the same `OBJECT_STORAGE_*` vars at the provider. See the + [Cloudflare Setup](/docs/contributor-guides/cloudflare-setup) guide for R2. -## SMTP Setup +### SMTP Setup You need SMTP for email sending. See [Nodemailer SMTP docs](https://nodemailer.com/smtp/) for provider setup. -## SSO (Enterprise) +### SSO (Enterprise) SSO requires a license key. Set `LICENSE_KEY` in your API environment. [Book a call](http://cal.com/classroomio/enterprise) to get a license. diff --git a/apps/docs/public/coolify-01-add-resource.png b/apps/docs/public/coolify-01-add-resource.png new file mode 100644 index 00000000..ae881758 Binary files /dev/null and b/apps/docs/public/coolify-01-add-resource.png differ diff --git a/apps/docs/public/coolify-01-new-resource.png b/apps/docs/public/coolify-01-new-resource.png new file mode 100644 index 00000000..1323340d Binary files /dev/null and b/apps/docs/public/coolify-01-new-resource.png differ diff --git a/apps/docs/public/coolify-02-services.png b/apps/docs/public/coolify-02-services.png new file mode 100644 index 00000000..e78cd765 Binary files /dev/null and b/apps/docs/public/coolify-02-services.png differ diff --git a/apps/docs/public/coolify-03-env-vars.png b/apps/docs/public/coolify-03-env-vars.png new file mode 100644 index 00000000..efb33142 Binary files /dev/null and b/apps/docs/public/coolify-03-env-vars.png differ diff --git a/apps/docs/public/coolify-04-image-tag.png b/apps/docs/public/coolify-04-image-tag.png new file mode 100644 index 00000000..2d4c1629 Binary files /dev/null and b/apps/docs/public/coolify-04-image-tag.png differ diff --git a/apps/docs/public/coolify-05-dashboard-domain.png b/apps/docs/public/coolify-05-dashboard-domain.png new file mode 100644 index 00000000..42a88b6b Binary files /dev/null and b/apps/docs/public/coolify-05-dashboard-domain.png differ diff --git a/apps/docs/public/coolify-06-deploy-logs.png b/apps/docs/public/coolify-06-deploy-logs.png new file mode 100644 index 00000000..07428002 Binary files /dev/null and b/apps/docs/public/coolify-06-deploy-logs.png differ diff --git a/apps/docs/public/railway-01-deploy-template.png b/apps/docs/public/railway-01-deploy-template.png new file mode 100644 index 00000000..caed08bd Binary files /dev/null and b/apps/docs/public/railway-01-deploy-template.png differ diff --git a/apps/docs/public/railway-02-services-overview.png b/apps/docs/public/railway-02-services-overview.png new file mode 100644 index 00000000..2624e878 Binary files /dev/null and b/apps/docs/public/railway-02-services-overview.png differ diff --git a/apps/docs/public/railway-03-jobs-smtp.png b/apps/docs/public/railway-03-jobs-smtp.png new file mode 100644 index 00000000..d50dbedd Binary files /dev/null and b/apps/docs/public/railway-03-jobs-smtp.png differ diff --git a/apps/docs/public/railway-04-minio-credentials.png b/apps/docs/public/railway-04-minio-credentials.png new file mode 100644 index 00000000..a366713e Binary files /dev/null and b/apps/docs/public/railway-04-minio-credentials.png differ diff --git a/apps/docs/public/railway-05-api-config.png b/apps/docs/public/railway-05-api-config.png new file mode 100644 index 00000000..98174230 Binary files /dev/null and b/apps/docs/public/railway-05-api-config.png differ diff --git a/apps/docs/public/railway-06-api-config-additional.png b/apps/docs/public/railway-06-api-config-additional.png new file mode 100644 index 00000000..783677b3 Binary files /dev/null and b/apps/docs/public/railway-06-api-config-additional.png differ diff --git a/apps/docs/public/railway-07-deploying.png b/apps/docs/public/railway-07-deploying.png new file mode 100644 index 00000000..adab6cf4 Binary files /dev/null and b/apps/docs/public/railway-07-deploying.png differ diff --git a/apps/docs/public/railway-08-first-boot-crash.png b/apps/docs/public/railway-08-first-boot-crash.png new file mode 100644 index 00000000..00316dde Binary files /dev/null and b/apps/docs/public/railway-08-first-boot-crash.png differ diff --git a/apps/docs/public/railway-09-generate-domain.png b/apps/docs/public/railway-09-generate-domain.png new file mode 100644 index 00000000..788e8ce2 Binary files /dev/null and b/apps/docs/public/railway-09-generate-domain.png differ diff --git a/apps/docs/public/railway-10-redeploy-dashboard.png b/apps/docs/public/railway-10-redeploy-dashboard.png new file mode 100644 index 00000000..9fe7c12e Binary files /dev/null and b/apps/docs/public/railway-10-redeploy-dashboard.png differ diff --git a/apps/docs/public/railway-11-redeploy-api.png b/apps/docs/public/railway-11-redeploy-api.png new file mode 100644 index 00000000..bf166927 Binary files /dev/null and b/apps/docs/public/railway-11-redeploy-api.png differ diff --git a/apps/docs/public/railway-12-signup.png b/apps/docs/public/railway-12-signup.png new file mode 100644 index 00000000..0ae728c0 Binary files /dev/null and b/apps/docs/public/railway-12-signup.png differ diff --git a/docker/coolify/docker-compose.yaml b/docker/coolify/docker-compose.yaml new file mode 100644 index 00000000..dc0c9d87 --- /dev/null +++ b/docker/coolify/docker-compose.yaml @@ -0,0 +1,146 @@ +# documentation: https://classroomio.com/docs/self-hosted/coolify +# slogan: Open-source LMS to build and teach courses with lessons, quizzes, certificates, student management, and an AI tutor. +# category: lms +# tags: lms, education, courses, teaching, e-learning +# logo: svgs/classroomio.svg +# port: 3082 + +services: + postgres: + image: postgres:16-alpine + environment: + - POSTGRES_DB=${POSTGRES_DB:-classroomio} + - POSTGRES_USER=$SERVICE_USER_POSTGRES + - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $SERVICE_USER_POSTGRES -d ${POSTGRES_DB:-classroomio}"] + interval: 5s + timeout: 5s + retries: 15 + + redis: + image: redis:7-alpine + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 10 + + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + - SERVICE_FQDN_MINIO_9000 + - MINIO_ROOT_USER=$SERVICE_USER_MINIO + - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO + - MINIO_SERVER_URL=$SERVICE_URL_MINIO + volumes: + - minio-data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 5s + timeout: 20s + retries: 10 + + minio-init: + image: minio/mc:latest + depends_on: + minio: + condition: service_healthy + exclude_from_hc: true + entrypoint: > + /bin/sh -c " + set -e; + mc alias set local http://minio:9000 $SERVICE_USER_MINIO $SERVICE_PASSWORD_MINIO; + mc mb local/videos --ignore-existing; + mc mb local/documents --ignore-existing; + mc mb local/media --ignore-existing; + mc anonymous set download local/media; + " + + api: + image: classroomio/api:${CIO_VERSION:-latest} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + - NODE_ENV=production + - PORT=3081 + - DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/${POSTGRES_DB:-classroomio} + - REDIS_URL=redis://redis:6379 + - PUBLIC_IS_SELFHOSTED=true + - BETTER_AUTH_SECRET=$SERVICE_HEX_64_BETTERAUTH + - PRIVATE_SERVER_KEY=$SERVICE_HEX_64_PRIVATESERVER + - DASHBOARD_ORIGIN=$SERVICE_URL_DASHBOARD + - PUBLIC_SERVER_URL=$SERVICE_URL_DASHBOARD + - OBJECT_STORAGE_ENDPOINT=http://minio:9000 + - OBJECT_STORAGE_PUBLIC_ENDPOINT=$SERVICE_URL_MINIO + - OBJECT_STORAGE_ACCESS_KEY_ID=$SERVICE_USER_MINIO + - OBJECT_STORAGE_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - OBJECT_STORAGE_FORCE_PATH_STYLE=true + - OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=$SERVICE_URL_MINIO/media + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - SMTP_SENDER=${SMTP_SENDER} + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://localhost:3081/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s + + dashboard: + image: classroomio/dashboard:${CIO_VERSION:-latest} + depends_on: + api: + condition: service_healthy + environment: + - SERVICE_FQDN_DASHBOARD_3082 + - NODE_ENV=production + - PORT=3082 + - ORIGIN=$SERVICE_URL_DASHBOARD + - PUBLIC_IS_SELFHOSTED=true + - PRIVATE_SERVER_URL=http://api:3081 + - PRIVATE_SERVER_KEY=$SERVICE_HEX_64_PRIVATESERVER + - CSP_MEDIA_SRC_DOMAINS=$SERVICE_URL_MINIO + + jobs: + image: classroomio/jobs:${CIO_VERSION:-latest} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + - NODE_ENV=production + - REDIS_URL=redis://redis:6379 + - DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgres:5432/${POSTGRES_DB:-classroomio} + - OBJECT_STORAGE_ENDPOINT=http://minio:9000 + - OBJECT_STORAGE_PUBLIC_ENDPOINT=$SERVICE_URL_MINIO + - OBJECT_STORAGE_ACCESS_KEY_ID=$SERVICE_USER_MINIO + - OBJECT_STORAGE_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_MINIO + - OBJECT_STORAGE_FORCE_PATH_STYLE=true + - OBJECT_STORAGE_MEDIA_PUBLIC_BASE_URL=$SERVICE_URL_MINIO/media + - OBJECT_STORAGE_BUCKET_VIDEOS=videos + - OBJECT_STORAGE_BUCKET_DOCUMENTS=documents + - OBJECT_STORAGE_BUCKET_MEDIA=media + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - SMTP_SENDER=${SMTP_SENDER} + - OPENAI_API_KEY=${OPENAI_API_KEY} + +volumes: + postgres-data: + redis-data: + minio-data: