Skip to content

Latest commit

 

History

History
245 lines (207 loc) · 13.2 KB

File metadata and controls

245 lines (207 loc) · 13.2 KB

Running Roomote on Coolify

This guide covers deploying Roomote on Coolify, the self-hosted PaaS, as a Docker Compose resource. The maintained service specification lives in docker-compose.yaml; you paste it into Coolify when creating the resource, and it is the source of truth that maintainers keep up to date.

For single-host Docker deployments without Coolify, use the one-command installer or the Compose paths in SELF_HOSTING.md instead. For a managed PaaS with no server of your own, see deploy/railway.

How the Coolify shape differs from single-host

  • Coolify's proxy is the HTTPS edge. There is no bundled Caddy and no /_roomote-api path routing. The web app, the API, and MinIO each get their own public domain through SERVICE_FQDN_* declarations in the compose file, so TRPC_URL points at the api service's domain directly. GitHub webhooks and sandbox workers call that API origin, and Slack webhooks arrive at the web origin and are proxied to the API internally.
  • The Docker socket is available. Unlike Railway, the Coolify host runs Docker, so the docker sandbox provider works and is the template default: the controller mounts /var/run/docker.sock and runs task workers on the same server. Hosted sandboxes (Modal, E2B, or Daytona) are a config-only swap; see Task execution.
  • No openssl provisioning step. The template sets R_AUTO_GENERATE_KEYS=true, so Roomote generates the JOB_AUTH_* / PREVIEW_AUTH_* P-256 keypairs at first boot and persists them encrypted (with ENCRYPTION_KEY) in Postgres. Every other secret is generated by Coolify's magic environment variables (SERVICE_PASSWORD_*, SERVICE_BASE64_*).
  • Shared secrets are defined once. Coolify resolves the same magic variable name to the same generated value across every service in the resource, so the compose file can reference SERVICE_PASSWORD_MINIO from both MinIO and the app services. There is no per-service secret anchor like Railway's ${{api.NAME}} indirection.
  • The setup wizard is token-gated by default. Coolify domains are publicly reachable, so the template generates SETUP_TOKEN (SERVICE_PASSWORD_SETUPTOKEN). Copy it from the resource's environment variables panel to open /setup.
  • Live previews are off by default. Preview subdomains need a wildcard domain routed to the optional preview-proxy service; everything else works without them (see below).

What you need

  • A server running Coolify with enough headroom for the stack. With the default docker sandbox provider, task workers run on the same host — plan for 8 GB+ RAM. With hosted sandboxes, 4 GB+ is enough for the control plane.
  • DNS you control for the web, api, and minio domains, or Coolify's generated domains for a quick trial. Set all three to https:// — GitHub App creation and webhooks require HTTPS origins, and Coolify's proxy issues Let's Encrypt certificates for https:// domains automatically.
  • A model provider API key, for example OpenRouter (entered in the setup wizard, not at deploy time). When using hosted sandboxes instead of the Docker socket, also a Modal, E2B, or Daytona account.

Image access

The published app and worker images (ghcr.io/roocodeinc/roomote-app and ghcr.io/roocodeinc/roomote-worker) are public and pulled anonymously; no registry login is needed.

Image channel and versions

The template tracks the mutable :develop alias, which the publish workflow moves to every new develop build (releases also move :latest). Nothing else in the template encodes a version: the images bake RELEASE_VERSION, the app derives DOCKER_WORKER_IMAGE from it when unset, and the controller reads the worker release version from the VERSION file inside worker-current.tar.gz.

To pin instead (recommended for production deployments): change the x-roomote-app-image anchor at the top of the compose file to an immutable tag (v* or develop-<sha>). No other edits are needed — the derived values follow the image.

Create the resource

  1. In Coolify, open (or create) a project and click + NewDocker Compose Empty.
  2. Paste the contents of docker-compose.yaml and save. Coolify parses the file, lists the services, and generates values for every SERVICE_* magic variable.
  3. In the service settings, set the domains: the web service's domain is the app origin users open, the api service's domain is what GitHub webhooks and workers call, and the minio service's domain serves presigned artifact URLs (routed to port 9000). Use https:// for all three. Coolify generates placeholder domains when the server has a wildcard domain configured; replace them with real ones for production.
  4. Deploy. The db-migrate one-shot runs schema migrations before the app services start serving (it exits after each run — that is expected; the exclude_from_hc field keeps it from affecting resource health).

Service topology

Service Image Public domain Healthcheck
postgres pinned postgres:17.5 no pg_isready
redis pinned redis:7-alpine no redis-cli ping
minio pinned MinIO + volume yes (routed to port 9000) mc ready local
db-migrate roomote-app:develop no (one-shot) excluded
web roomote-app:develop yes (port 3000) /health
api roomote-app:develop yes (port 3001) /health/liveness
controller roomote-app:develop no /health/controller via API
bullmq roomote-app:develop no /admin/health
preview-proxy roomote-app:develop optional (wildcard domain) /health

Environment variables

The shared env block is defined once as the x-roomote-shared-env YAML anchor and merged into every app service. The generated values live on the resource's environment variables panel in Coolify, where you can read SETUP_TOKEN and DASHBOARD_PASSWORD after the first deploy.

Notes:

  • Leave DOCKER_WORKER_IMAGE unset. The app derives it from the RELEASE_VERSION baked into the running image, so it always matches the deployed build. Setting it explicitly silently pins the worker to whatever version the value encodes.
  • Sandbox provider credentials (Modal token pair when using Modal) and model provider keys such as OPENROUTER_API_KEY are not template variables. Enter them in the /setup wizard (or Settings → Sandboxes / Models) after first boot; they are stored encrypted in Postgres.
  • Leave JOB_AUTH_* and PREVIEW_AUTH_* unset — R_AUTO_GENERATE_KEYS=true manages them. If you later provide explicit env values, they take precedence over the persisted keypairs.
  • Leave PREVIEW_PROXY_BASE_URL and PREVIEW_DOMAINS unset unless you enable live previews. Roomote boots without them; previews report as not configured in Settings → Live Previews until set.

Task execution

The template defaults to DEFAULT_COMPUTE_PROVIDER=docker: the controller mounts the host Docker socket and starts one worker container per task on the same server. Two things matter in this mode:

  • Worker network. The controller uses the network named by DOCKER_WORKER_NETWORK (roomote_default, declared at the bottom of the compose file) to discover the labeled API and optional preview-proxy containers. It creates a separate bridge network for each task and attaches only the API plus the preview proxy when enabled, so workers do not join roomote_default, see sibling tasks, or reach Postgres, Redis, and MinIO. If worker containers fail to start after a deploy, confirm the configured discovery network exists with docker network ls and that Coolify preserved the standard Compose service labels.

    Docker task writable layers fail closed when the host storage driver cannot enforce DOCKER_WORKER_DISK_LIMIT. Keep DOCKER_WORKER_ALLOW_UNBOUNDED_DISK=false unless the host already enforces an equivalent quota outside Docker.

  • Trust boundary. Mounting the Docker socket gives the controller control of the host Docker daemon. This matches the single-host production shape and is intended for a trusted, single-tenant server.

To use hosted sandboxes instead (no socket access, task execution bills through the provider):

  1. Set DEFAULT_COMPUTE_PROVIDER=modal (or e2b / daytona) and add EXCLUDED_COMPUTE_PROVIDERS=docker to the shared env block.
  2. Remove the /var/run/docker.sock volume from the controller service.
  3. Keep DOCKER_WORKER_RELEASE_PATH — the controller uploads the baked-in worker release archive into hosted sandboxes at spawn time.
  4. Enter the provider credentials in the /setup wizard after first boot.

The artifact bucket

MinIO does not auto-create buckets, so the template sets S3_AUTO_CREATE_BUCKET=true: at boot, the api creates the artifacts bucket when it is missing. Watch for [artifacts-bucket] Created S3 bucket ... in api's logs after the first deploy.

Presigned artifact URLs use the minio service's public domain (S3_PRESIGN_ENDPOINT), so workers and browsers can download artifacts without reaching into the compose network. Alternatively, skip bundled MinIO and point the S3 values at an external S3-compatible store (AWS S3 or Cloudflare R2, with S3_REGION=auto for R2). Roomote uses path-style addressing, and S3_PRESIGN_ENDPOINT must be reachable from workers.

First boot

  1. Wait for web and api to report healthy. On a brand-new resource the first boot also generates and persists the auth keypairs (watch for [auth-keypairs] Generated ... in an app service's logs).
  2. Copy SETUP_TOKEN from the resource's environment variables panel and open https://<web-domain>/setup?token=<SETUP_TOKEN>.
  3. Create the founding admin account (email/password works immediately; Slack or Microsoft sign-in can be added later).
  4. Connect GitHub with Create GitHub App — the manifest flow derives the callback and webhook URLs from R_APP_URL and TRPC_URL, so no manual URL entry is needed.
  5. Enter the model provider key when the wizard asks. With the default Docker provider there are no sandbox credentials to enter; with hosted sandboxes, enter the provider tokens and let the wizard build the E2B template or Daytona snapshot when applicable.
  6. Pick repositories, create an environment, and run a small task end to end (the SELF_HOSTING.md verification checklist applies from step 2 onward).

Live previews (optional)

Live previews need a wildcard domain, which requires DNS you control and a wildcard-capable TLS setup on the Coolify proxy:

  1. Uncomment the preview-proxy service in the compose file and set PREVIEW_PROXY_BASE_URL, NEXT_PUBLIC_PREVIEW_PROXY_BASE_URL, and PREVIEW_DOMAINS to your preview domain. The NEXT_PUBLIC_ variant must also be set on the web service — the web client builds preview links from it.
  2. Point previews.<your-domain> and *.previews.<your-domain> at the Coolify server and route them to the preview-proxy service. Wildcard HTTPS certificates require a DNS-01 challenge in Coolify's proxy configuration (see Coolify's wildcard/custom-proxy docs); this is the advanced part and the reason previews stay off by default.
  3. Opt in from Settings → Live Previews, which validates the wildcard hostname and enables previews per deployment and environment.

Upgrades, backups, and costs

  • Upgrade a deployment on :develop by redeploying the resource — Coolify pulls the current alias, db-migrate applies any schema changes before the app services restart, and the auto-generated keypairs persist in Postgres, so sessions, job tokens, and preview tokens survive redeploys. On an immutable pin, bump the tag in the x-roomote-app-image anchor first.
  • Back up the Postgres volume (or run pg_dump against the postgres container) and the MinIO volume. Everything else is reproducible from config plus the generated environment values on the resource.
  • Costs are your server. With the default Docker provider everything — control plane and task execution — runs on the Coolify host. With hosted sandboxes, task execution bills through the provider and model usage bills through your model provider.

Maintaining the template

When the template needs to change, edit docker-compose.yaml and re-run the first-boot verification on a scratch Coolify resource before merging. Keep the file paste-ready: Coolify-specific fields (SERVICE_* magic variables, exclude_from_hc) are intentional and are consumed by Coolify's compose parser, not by plain docker compose.