Skip to content

Coolify

Coolify is a self-hosted PaaS that runs on Docker plus Traefik on a single VPS, or on a small Docker swarm. It reads docker-compose.yml directly, so Doable does not ship a Coolify-specific manifest. The connect flow points Coolify at deployment/docker/docker-compose.prod.yml, which pulls the prebuilt images from ghcr.io/doable-me/doable-*.

Prerequisites

  • A fresh Ubuntu 22.04 or 24.04 VPS, 2 vCPU, 4 GB RAM, 40 GB disk minimum.
  • A domain pointing at the VPS via an A record.
  • Coolify installed: see the Coolify install docs.

Connect flow

  1. Log into Coolify, click Resources, New, then Public Repository.
  2. Public git URL: https://github.com/doable-me/doable.git.
  3. Build Pack: Docker Compose.
  4. Docker Compose Location: deployment/docker/docker-compose.prod.yml. This is the prebuilt variant. It pulls from ghcr.io, so there is no on-VPS source build.
  5. Coolify auto-detects the four services plus Postgres. Configure each:
  6. postgres: persistent volume postgres_data (Coolify creates a Docker volume automatically). No public domain.
  7. migrate: no domain, no persistent volume. Set restart policy to no, because it is a one-shot job.
  8. api, ws: internal-only by default. No public domain assigned.
  9. web: assign a public domain. Coolify creates the Traefik route and issues a Let's Encrypt certificate.

Required environment variables

Use Coolify's env-secrets UI. The minimum set:

Variable How to generate
JWT_SECRET Coolify's generate-random, or openssl rand -hex 32
ENCRYPTION_KEY openssl rand -hex 32
INTERNAL_SECRET openssl rand -hex 32
DOABLE_KEK openssl rand -base64 32
POSTGRES_PASSWORD openssl rand -hex 16
INSTALL_BOOTSTRAP_TOKEN openssl rand -hex 32 (24 hour single-use)
INSTALL_BOOTSTRAP_TOKEN_EXPIRES_AT ISO8601 timestamp 24h ahead, e.g. 2099-01-01T00:00:00Z

URL contract (replace <your-domain> with the public domain you assigned to web):

NEXT_PUBLIC_API_URL=https://<your-domain>/api
NEXT_PUBLIC_WS_URL=wss://<your-domain>/ws
NEXT_PUBLIC_APP_URL=https://<your-domain>
CORS_ORIGINS=https://<your-domain>

Add at least one AI provider key (ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, and so on). The setup wizard can fill these in at runtime if you prefer, the env-var path just pre-configures step 2 of the wizard.

Deploy and bootstrap

Click Deploy. Coolify pulls the four images (about 30 seconds on a 100 Mbit/s VPS), runs the migrate container, then brings up api, ws, and web behind Traefik.

First user: open https://<your-domain>/auth/register. The first signup is auto-promoted to platform owner and the five-step setup wizard runs immediately after.

Coolify-specific notes

  • Traefik replaces the bundled nginx. Coolify owns ports 80 and 443 and adds Traefik labels automatically. The host-side nginx templates in deployment/docker/nginx-http.conf.template are unused on this path.
  • Port-binding rewrites. The prebuilt compose binds 127.0.0.1:<port>:<port>. Coolify reads the second number for routing. If a service is unreachable, double-check the Traefik labels that Coolify adds on Save.
  • NEXT_PUBLIC_* runtime substitution. Coolify sets these env vars at container runtime, and the web container's entrypoint sed-replaces the placeholders baked into the static bundle. One image, any deployment URL.
  • pgvector is mandatory. The bundled compose uses the pgvector/pgvector:pg16 image. If you swap in Coolify's managed Postgres, the default postgres:16-alpine does not ship pgvector. Either keep the bundled service or build a custom Coolify Postgres image with the extension.
  • Volume retention. Coolify deletes volumes when a stack is deleted. The compose names the volume postgres_data explicitly so Coolify retains it as <stack-name>_postgres_data across re-deploys.

Smoke test

curl https://<your-domain>/api/health
# {"status":"healthy","timestamp":"...","checks":{"database":{"status":"up"}}}

curl -I https://<your-domain>/
# HTTP/2 200, content-type: text/html

If both pass, run scripts/smoke-tests-docker.sh HOST=<your-domain> for the full smoke harness.

Upgrading

Coolify's Force Rebuild pulls fresh :latest images. To pin a specific build, set DOABLE_IMAGE_TAG=v0.2.0 in the env vars. The compose file references ${DOABLE_IMAGE_TAG:-latest}.

Reference

  • Source compose: deployment/docker/docker-compose.prod.yml
  • Original notes in repo: deployment/docker/coolify.md
  • Related: Docker + nginx, Custom Domains