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¶
- Log into Coolify, click Resources, New, then Public Repository.
- Public git URL:
https://github.com/doable-me/doable.git. - Build Pack: Docker Compose.
- Docker Compose Location:
deployment/docker/docker-compose.prod.yml. This is the prebuilt variant. It pulls fromghcr.io, so there is no on-VPS source build. - Coolify auto-detects the four services plus Postgres. Configure each:
- postgres: persistent volume
postgres_data(Coolify creates a Docker volume automatically). No public domain. - migrate: no domain, no persistent volume. Set restart policy to
no, because it is a one-shot job. - api, ws: internal-only by default. No public domain assigned.
- 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.templateare 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:pg16image. If you swap in Coolify's managed Postgres, the defaultpostgres:16-alpinedoes 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_dataexplicitly so Coolify retains it as<stack-name>_postgres_dataacross 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