
Coolify Compose
- 48 installs
- 1 repo stars
- Updated March 1, 2026
- cachemoney/agent-toolkit
A Claude Code skill for coolify compose.
About
Skill: coolify-compose. Used during build phase for development. This skill provides essential functionality for the development workflow.
- coolify-compose
Coolify Compose by the numbers
- 48 all-time installs (skills.sh)
- Ranked #1,329 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cachemoney/agent-toolkit --skill coolify-composeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 48 |
|---|---|
| repo stars | ★ 1 |
| Last updated | March 1, 2026 |
| Repository | cachemoney/agent-toolkit ↗ |
What it does
A Claude Code skill for coolify compose.
Files
Coolify Docker Compose
Convert standard Docker Compose files into Coolify-compatible templates with automatic credential generation, dynamic URLs, and one-click deployment.
Two Deployment Modes
Coolify supports two ways to deploy compose files with different capabilities:
1. Raw Compose (Paste Content)
Paste compose YAML directly into Coolify's UI. Limited feature set:
| Feature | Supported |
|---|---|
image: | ✅ Yes |
build: | ❌ No - must use pre-built images |
| External config files | ❌ No - must use inline content: |
YAML anchors (&, *) | ✅ Yes - resolved by YAML parser |
| Coolify magic variables | ✅ Yes |
content: for inline files | ✅ Yes |
Use when: Quick deployments, simple services, no custom images needed.
2. Repository Mode (Git URL)
Point Coolify to a Git repository containing your compose file. Full Docker Compose features:
| Feature | Supported |
|---|---|
image: | ✅ Yes |
build: | ✅ Yes - builds from Dockerfile in repo |
| External config files | ✅ Yes - relative paths work |
| Coolify magic variables | ✅ Yes |
content: for inline files | ✅ Yes |
Use when: Custom images needed, complex multi-file setups, existing docker-compose.yml in a repo.
Repository setup:
my-service/
├── compose.yml # or docker-compose.yml
├── custom-image/
│ ├── Dockerfile
│ └── config.sql
└── other-files/# compose.yml - can use build:
services:
app:
build:
context: ./custom-image
dockerfile: DockerfileWhich Mode to Use?
| Original compose has... | Recommended mode |
|---|---|
Only image: references | Either works |
build: directives | Repository mode |
| External config files to mount | Repository mode (or use content: in raw) |
| Single simple service | Raw mode is faster |
Quick Start
Every Coolify template needs a header and magic variables:
# documentation: https://example.com/docs
# slogan: Brief description of the service
# category: backend
# tags: api, database, docker
# logo: svgs/myservice.svg
# port: 3000
services:
app:
image: myapp:latest
environment:
- SERVICE_URL_APP_3000 # Generates URL, routes proxy to port 3000
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/mydb
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 5s
timeout: 10s
retries: 10
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 5s
timeout: 10s
retries: 10Conversion Checklist
When converting a standard docker-compose.yml:
0. Check for build: Directives
If the compose has build: entries:
- Repository mode: Keep them. Coolify will build from the Dockerfile.
- Raw mode: Replace with pre-built
image:references or find equivalent images.
# Original with build:
services:
custom-db:
build: ./custom-postgres
# Raw mode: find or create pre-built image
custom-db:
image: your-registry.com/custom-postgres:latest
# Repository mode: keep build:, include Dockerfile in repo
custom-db:
build:
context: ./custom-postgres
dockerfile: Dockerfile1. Add Header Metadata
# documentation: https://... # Required: URL to official docs
# slogan: ... # Required: One-line description
# category: ... # Required: backend, cms, monitoring, etc.
# tags: ... # Required: Comma-separated search terms
# logo: svgs/....svg # Required: Path in Coolify's svgs/ folder
# port: ... # Recommended: Main service port2. Replace Hardcoded Credentials
# ❌ Before
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_USER=admin
# ✅ After
POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
POSTGRES_USER=$SERVICE_USER_POSTGRES3. Replace URLs with Magic Variables
# ❌ Before
APP_URL=https://myapp.example.com
# ✅ After
- SERVICE_URL_APP_3000 # Declares URL + proxy routing
- APP_URL=$SERVICE_URL_APP # References it4. Remove ports: for Proxied Services
Coolify's Traefik proxy handles routing. Only keep ports: for SSH, UDP, or proxy bypass.
# ❌ Before
ports:
- "3000:3000"
# ✅ After
environment:
- SERVICE_URL_APP_3000 # Proxy routes to container port 3000
# No ports: needed5. Add Health Checks
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 10s
retries: 106. Use depends_on with Conditions
depends_on:
db:
condition: service_healthyMagic Variables Reference
Coolify generates values using SERVICE_<TYPE>_<IDENTIFIER>:
| Type | Example | Result |
|---|---|---|
PASSWORD | SERVICE_PASSWORD_DB | Random password |
PASSWORD_64 | SERVICE_PASSWORD_64_KEY | 64-char password |
USER | SERVICE_USER_ADMIN | Random 16-char string |
BASE64_64 | SERVICE_BASE64_64_SECRET | 64-char random string |
REALBASE64_64 | SERVICE_REALBASE64_64_JWT | Actual base64-encoded string |
HEX_32 | SERVICE_HEX_32_KEY | 64-char hex string |
URL | SERVICE_URL_APP_3000 | https://app-uuid.example.com + proxy |
FQDN | SERVICE_FQDN_APP | app-uuid.example.com (no scheme, no port suffix) |
Declaration vs Reference (Critical)
For SERVICE_URL, the port suffix configures proxy routing but is not part of the variable name:
# Declare WITH port suffix (configures proxy to route to port 3000)
- SERVICE_URL_MYAPP_3000
# Reference WITHOUT port suffix (gets the URL value)
- APP_URL=$SERVICE_URL_MYAPP
- WEBHOOK_URL=${SERVICE_URL_MYAPP}/webhooksSERVICE_FQDN is automatically available when SERVICE_URL is declared — no separate declaration needed:
# This single declaration...
- SERVICE_URL_MYAPP_3000
# ...makes BOTH of these available:
- FULL_URL=$SERVICE_URL_MYAPP # https://myapp-uuid.example.com
- HOSTNAME=${SERVICE_FQDN_MYAPP} # myapp-uuid.example.com⚠️ Important: Use hyphens, not underscores, before port numbers:
SERVICE_URL_MY_SERVICE_3000 # ❌ Breaks parsing
SERVICE_URL_MY-SERVICE_3000 # ✅ WorksSee references/magic-variables.md for complete list.
Coolify-Specific Extensions
Create Directory
volumes:
- type: bind
source: ./data
target: /app/data
is_directory: true # Coolify creates thisCreate File with Content
Useful in raw mode when you can't reference external files. In repository mode, you can just mount files normally.
# Raw mode: embed file content inline
volumes:
- type: bind
source: ./config.json
target: /app/config.json
content: |
{"key": "${SERVICE_PASSWORD_APP}"}
# Repository mode: reference actual file in repo
volumes:
- ./config/settings.json:/app/config.json:roExclude from Health Checks
For migration/init containers that exit after running:
services:
migrate:
command: ["npm", "run", "migrate"]
exclude_from_hc: trueCommon Patterns
Database Connection
environment:
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/${POSTGRES_DB:-myapp}Shared Credentials
Same SERVICE_PASSWORD_* identifier = same value across all services:
services:
app:
environment:
- DB_PASS=$SERVICE_PASSWORD_POSTGRES
db:
environment:
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES # Same valueMulti-Service URLs
services:
frontend:
environment:
- SERVICE_URL_FRONTEND_3000
- API_URL=$SERVICE_URL_API
api:
environment:
- SERVICE_URL_API_8080=/api # Path suffixHealth Check Patterns
# HTTP
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080"]
# PostgreSQL
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
# MySQL/MariaDB
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
# Redis
test: ["CMD", "redis-cli", "ping"]
# Always pass (use sparingly)
test: ["CMD", "echo", "ok"]Environment Variable Syntax
environment:
- NODE_ENV=production # Hardcoded, hidden from UI
- API_KEY=${API_KEY} # Editable in UI (empty)
- LOG_LEVEL=${LOG_LEVEL:-info} # Editable with default
- SECRET=${SECRET:?} # Required - blocks deploy if emptyTroubleshooting
| Problem | Solution |
|---|---|
| "No Available Server" error | Check docker ps for unhealthy containers; verify healthcheck passes |
| Variables not in Coolify UI | Use ${VAR} syntax; hardcoded VAR=value won't appear |
| Magic variables not generating | Check spelling; ensure SERVICE_ prefix; verify Coolify v4.0.0-beta.411+ |
| Port routing broken | Use SERVICE_URL_NAME_PORT; avoid underscores before port; remove ports: |
Examples
First, check for an official template: Many popular services have official Coolify templates at github.com/coollabsio/coolify/tree/main/templates/compose. If one exists, use it as the reference for correct patterns.
When converting a compose file without an official template, analyze it and use the matching example:
| Compose file has... | Use example |
|---|---|
| 1 service, no database | examples/simple/ |
| 2 services: app + database (postgres/mysql/mariadb) | examples/with-database/ |
| 3+ services, or mounted config files, or multiple databases | examples/multi-service/ |
Quick analysis:
- Count the
services:— if just 1, usesimple/ - Look for
postgres,mysql,mariadb,mongoimages — if 1 database, usewith-database/ - Look for mounted
.xml,.json,.ymlconfig files — if present, usemulti-service/ - Look for
clickhouse,redis, multiple databases — usemulti-service/
References
- references/magic-variables.md — Complete variable type reference
- references/categories.md — Valid category values
- Official Coolify Compose Docs — Authoritative documentation
- Official Service Templates — Reference implementations for correct patterns
# documentation: https://plausible.io/docs/self-hosting
# slogan: Simple, open-source, lightweight and privacy-friendly web analytics alternative to Google Analytics.
# category: analytics
# tags: analytics, privacy, google-alternative, web, statistics
# logo: svgs/plausible.svg
# port: 8000
services:
plausible:
image: ghcr.io/plausible/community-edition:v3.0.1
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
environment:
- SERVICE_URL_PLAUSIBLE_8000
- BASE_URL=$SERVICE_URL_PLAUSIBLE
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
- TOTP_VAULT_KEY=$SERVICE_REALBASE64_32_TOTP
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@plausible-db:5432/${POSTGRES_DB:-plausible}
- CLICKHOUSE_DATABASE_URL=http://plausible-events-db:8123/plausible_events_db
- MAILER_ADAPTER=${MAILER_ADAPTER:-Bamboo.LocalAdapter}
depends_on:
plausible-db:
condition: service_healthy
plausible-events-db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 45s
plausible-db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-plausible}
volumes:
- plausible-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
plausible-events-db:
image: clickhouse/clickhouse-server:24.12-alpine
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
volumes:
- plausible-events-data:/var/lib/clickhouse
- type: bind
source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml
read_only: true
content: |
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_thread_log remove="remove"/>
<query_log remove="remove"/>
<text_log remove="remove"/>
<trace_log remove="remove"/>
<metric_log remove="remove"/>
<asynchronous_metric_log remove="remove"/>
<session_log remove="remove"/>
<part_log remove="remove"/>
</clickhouse>
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 -O - http://127.0.0.1:8123/ping || exit 1"]
interval: 5s
timeout: 20s
retries: 10
start_period: 30s
volumes:
plausible-postgres-data:
plausible-events-data:
# Standard Docker Compose for Plausible Analytics
# Source: https://github.com/plausible/community-edition
services:
plausible:
image: ghcr.io/plausible/community-edition:v3.0.1
command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
ports:
- "8000:8000"
environment:
- BASE_URL=https://analytics.example.com
- SECRET_KEY_BASE=super-long-secret-key-at-least-64-chars-xxxxxxxxxxxxxxxxxxxxxxxx
- TOTP_VAULT_KEY=another-secret-key-for-totp-xxxxxxx
- DATABASE_URL=postgres://plausible:password123@plausible-db:5432/plausible
- CLICKHOUSE_DATABASE_URL=http://plausible-events-db:8123/plausible_events_db
depends_on:
- plausible-db
- plausible-events-db
plausible-db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=plausible
- POSTGRES_PASSWORD=password123
- POSTGRES_DB=plausible
volumes:
- postgres-data:/var/lib/postgresql/data
plausible-events-db:
image: clickhouse/clickhouse-server:24.12-alpine
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
volumes:
- clickhouse-data:/var/lib/clickhouse
- ./clickhouse/logs.xml:/etc/clickhouse-server/config.d/logs.xml:ro
ulimits:
nofile:
soft: 262144
hard: 262144
volumes:
postgres-data:
clickhouse-data:
Multi-Service Conversion: Plausible Analytics
Changes Made
| Original | Coolify | Why |
|---|---|---|
| External config file | Inline content: | Self-contained, no extra files needed |
| Hardcoded secrets | SERVICE_BASE64_64_*, SERVICE_REALBASE64_* | Cryptographically secure generation |
Simple depends_on | condition: service_healthy | Proper startup ordering |
| No healthchecks | All services have healthchecks | Deployment verification |
Added sleep 10 | Command modification | Gives databases time to fully initialize |
Key Patterns
Different Secret Types
# 64-char random string (for general secrets)
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
# Actual base64-encoded string (required by TOTP libraries)
- TOTP_VAULT_KEY=$SERVICE_REALBASE64_32_TOTPUse REALBASE64 when the application expects actual base64 encoding (common for encryption keys, TOTP secrets).
Inline Configuration Files
Instead of mounting external files:
volumes:
- type: bind
source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml
read_only: true
content: |
<clickhouse>
<logger>
<level>warning</level>
...
</logger>
</clickhouse>Coolify creates this file at deploy time. The template is fully self-contained.
Dual Database Pattern
Plausible uses both PostgreSQL (metadata) and ClickHouse (events):
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@plausible-db:5432/...
- CLICKHOUSE_DATABASE_URL=http://plausible-events-db:8123/plausible_events_dbClickHouse doesn't need credentials (internal network only), but PostgreSQL does.
Health Check Start Period
healthcheck:
start_period: 45s # Give app time to run migrationsPlausible runs database migrations on startup. The start_period prevents false failures during this initialization.
ulimits Preservation
ulimits:
nofile:
soft: 262144
hard: 262144ClickHouse needs high file descriptor limits. Standard Docker Compose features work in Coolify.
Migration Container Pattern (Alternative)
If migrations are slow, consider a separate migration container:
services:
migrate:
image: ghcr.io/plausible/community-edition:v3.0.1
command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate"
exclude_from_hc: true # Don't wait for this to stay running
depends_on:
plausible-db:
condition: service_healthy
plausible:
command: /entrypoint.sh run # Just run, no migrations
depends_on:
migrate:
condition: service_completed_successfullyCoolify Compose Examples
Before/after examples showing standard Docker Compose files converted to Coolify templates.
Which Example to Use
Analyze the user's compose file:
| Compose file characteristics | Example |
|---|---|
| 1 service, no database | simple/ |
| 2 services: app + single database | with-database/ |
| 3+ services, config file mounts, or multiple databases | multi-service/ |
Detection Rules
→ simple/
- Only 1 entry under
services: - No database images (postgres, mysql, mariadb, mongo, redis)
- Just needs URL routing + healthcheck
→ with-database/
- Exactly 2 services
- One is a database image (
postgres:*,mysql:*,mariadb:*) - App references database credentials in environment
→ multi-service/
- 3 or more services
- Multiple database images (e.g., postgres + clickhouse, postgres + redis)
- Config files mounted from host (
./config.xml:/etc/app/config.xml) - Init/migration containers that run once and exit
Each Example Contains
before.yml— Standard Docker Compose (user's input)after.yml— Coolify-compatible template (desired output)notes.md— Explanation of each transformation
# documentation: https://github.com/louislam/uptime-kuma
# slogan: A fancy self-hosted monitoring tool for tracking uptime of services.
# category: monitoring
# tags: monitoring, status, uptime, alerts, notifications
# logo: svgs/uptime-kuma.svg
# port: 3001
services:
uptime-kuma:
image: louislam/uptime-kuma:2
environment:
- SERVICE_URL_UPTIMEKUMA_3001
volumes:
- uptime-kuma-data:/app/data
healthcheck:
test: ["CMD-SHELL", "extra/healthcheck"]
interval: 5s
timeout: 5s
retries: 10
volumes:
uptime-kuma-data:
# Standard Docker Compose for Uptime Kuma
# Source: https://github.com/louislam/uptime-kuma
services:
uptime-kuma:
image: louislam/uptime-kuma:2
restart: unless-stopped
volumes:
- ./data:/app/data
ports:
- "3001:3001"
Simple Conversion: Uptime Kuma
Changes Made
| Original | Coolify | Why |
|---|---|---|
| No header | Added metadata comments | Required for Coolify template discovery |
ports: "3001:3001" | SERVICE_URL_UPTIMEKUMA_3001 | Traefik proxy handles routing |
./data bind mount | uptime-kuma-data named volume | Named volumes are more portable |
| No healthcheck | Added healthcheck | Coolify uses this to determine deployment success |
restart: unless-stopped | Removed | Coolify manages restart policy |
Key Patterns
URL Generation
- SERVICE_URL_UPTIMEKUMA_3001This single line: 1. Generates a URL like https://uptimekuma-abc123.example.com 2. Configures Traefik to route traffic to container port 3001 3. Creates an editable SERVICE_URL_UPTIMEKUMA variable in Coolify UI
Health Check
Uptime Kuma ships with a built-in healthcheck script at extra/healthcheck. Always prefer the application's native healthcheck when available.
# documentation: https://ghost.org
# slogan: Ghost is a powerful app for professional publishers to create, share, and grow a business around their content.
# category: cms
# tags: cms, blog, content, publishing, newsletter
# logo: svgs/ghost.svg
# port: 2368
services:
ghost:
image: ghost:5
environment:
- SERVICE_URL_GHOST_2368
- url=$SERVICE_URL_GHOST
- database__client=mysql
- database__connection__host=mysql
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE:-ghost}
volumes:
- ghost-content-data:/var/lib/ghost/content
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2368/ghost/api/v4/admin/site/"]
interval: 5s
timeout: 20s
retries: 10
mysql:
image: mysql:8.0
environment:
- MYSQL_USER=$SERVICE_USER_MYSQL
- MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL
- MYSQL_DATABASE=${MYSQL_DATABASE:-ghost}
- MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT
volumes:
- ghost-mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 5s
timeout: 20s
retries: 10
volumes:
ghost-content-data:
ghost-mysql-data:
# Standard Docker Compose for Ghost CMS
# Typical setup from documentation
services:
ghost:
image: ghost:5
ports:
- "2368:2368"
environment:
- url=https://myblog.example.com
- database__client=mysql
- database__connection__host=mysql
- database__connection__user=ghost
- database__connection__password=supersecretpassword
- database__connection__database=ghost
volumes:
- ./content:/var/lib/ghost/content
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_USER=ghost
- MYSQL_PASSWORD=supersecretpassword
- MYSQL_DATABASE=ghost
- MYSQL_ROOT_PASSWORD=rootsecretpassword
volumes:
- ./mysql-data:/var/lib/mysql
Database Conversion: Ghost with MySQL
Changes Made
| Original | Coolify | Why |
|---|---|---|
| Hardcoded URL | SERVICE_URL_GHOST_2368 | Dynamic URL based on Coolify domain |
| Hardcoded passwords | SERVICE_PASSWORD_MYSQL | Auto-generated secure credentials |
| Hardcoded username | SERVICE_USER_MYSQL | Auto-generated username |
depends_on: [mysql] | depends_on: mysql: condition: service_healthy | Wait for MySQL to be ready |
| Bind mounts | Named volumes | Portable, managed by Coolify |
| No healthchecks | Added to both services | Deployment verification |
Key Patterns
Shared Credentials
The same identifier is used in both services:
# In ghost service
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
# In mysql service
- MYSQL_USER=$SERVICE_USER_MYSQL
- MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQLCoolify generates these once and injects the same value everywhere they're referenced.
Separate Root Password
MySQL needs a root password separate from the app user:
- MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOTUsing a different identifier (MYSQLROOT vs MYSQL) generates a different password.
Optional Defaults
- MYSQL_DATABASE=${MYSQL_DATABASE:-ghost}This creates an editable variable in Coolify UI with "ghost" as the default value.
Health Check Dependency
depends_on:
mysql:
condition: service_healthyGhost won't start until MySQL's healthcheck passes, preventing connection errors on startup.
Template Categories
Valid values for the # category: header field.
Categories by Usage (from 337 Coolify templates)
| Category | Count | Examples |
|---|---|---|
productivity | 70 | Nextcloud, Outline, Vikunja |
cms | 38 | WordPress, Ghost, Strapi |
media | 24 | Jellyfin, Plex, Immich |
devtools | 20 | Hoppscotch, Code Server, Jenkins |
storage | 18 | MinIO, Seafile, Filebrowser |
monitoring | 18 | Uptime Kuma, Grafana, Prometheus |
ai | 18 | Ollama, Flowise, AnythingLLM |
automation | 15 | N8N, Activepieces, Huginn |
git | 14 | GitLab, Gitea, Forgejo |
analytics | 14 | Plausible, Umami, Matomo |
backend | 13 | Supabase, Appwrite, Pocketbase |
messaging | 10 | Mattermost, Rocket.Chat, Matrix |
auth | 10 | Authentik, Keycloak, Authelia |
database | 6 | pgAdmin, Redis Insight, Adminer |
security | 6 | Vaultwarden, CrowdSec |
search | 5 | Meilisearch, Elasticsearch, Typesense |
proxy | 3 | Traefik, Nginx Proxy Manager |
vpn | 2 | WireGuard, Tailscale |
RSS | 2 | FreshRSS, Miniflux |
email | 2 | Mailpit, Listmonk |
games | 1 | Minecraft, Terraria |
documentation | 1 | WikiJS, BookStack |
ci | 1 | Jenkins, Drone |
Header Template
# documentation: https://example.com/docs
# slogan: Brief one-line description
# category: backend
# tags: keyword1, keyword2, keyword3
# logo: svgs/myservice.svg
# port: 3000Optional Fields
| Field | Description |
|---|---|
# minversion: 4.0.0-beta.411 | Minimum Coolify version required |
# ignore: true | Hide template from the service list |
Magic Environment Variables
Complete reference for Coolify's auto-generated environment variables.
Syntax
SERVICE_<TYPE>_<IDENTIFIER>- TYPE: The generation method (see tables below)
- IDENTIFIER: Any alphanumeric name (used to reference the same value across services)
Credentials
| Type | Syntax | Generated Value |
|---|---|---|
PASSWORD | SERVICE_PASSWORD_<ID> | Random password, no symbols |
PASSWORD_64 | SERVICE_PASSWORD_64_<ID> | 64-char password, no symbols |
PASSWORDWITHSYMBOLS | SERVICE_PASSWORDWITHSYMBOLS_<ID> | Random password with symbols |
PASSWORDWITHSYMBOLS_64 | SERVICE_PASSWORDWITHSYMBOLS_64_<ID> | 64-char password with symbols |
USER | SERVICE_USER_<ID> | Random 16-char alphanumeric |
LOWERCASEUSER | SERVICE_LOWERCASEUSER_<ID> | Lowercase 16-char alphanumeric |
Random Strings
| Type | Syntax | Generated Value |
|---|---|---|
BASE64 | SERVICE_BASE64_<ID> | 32-char random string |
BASE64_32 | SERVICE_BASE64_32_<ID> | 32-char random string |
BASE64_64 | SERVICE_BASE64_64_<ID> | 64-char random string |
BASE64_128 | SERVICE_BASE64_128_<ID> | 128-char random string |
⚠️ Note:BASE64types are misleadingly named — they're just random alphanumeric strings, NOT base64-encoded. UseREALBASE64for actual base64 encoding.
Actual Base64
| Type | Syntax | Generated Value |
|---|---|---|
REALBASE64 | SERVICE_REALBASE64_<ID> | base64-encoded 32-char random |
REALBASE64_32 | SERVICE_REALBASE64_32_<ID> | base64-encoded 32-char random |
REALBASE64_64 | SERVICE_REALBASE64_64_<ID> | base64-encoded 64-char random |
REALBASE64_128 | SERVICE_REALBASE64_128_<ID> | base64-encoded 128-char random |
Hex Strings
| Type | Syntax | Generated Value |
|---|---|---|
HEX_32 | SERVICE_HEX_32_<ID> | 64-char hex (32 bytes) |
HEX_64 | SERVICE_HEX_64_<ID> | 128-char hex (64 bytes) |
HEX_128 | SERVICE_HEX_128_<ID> | 256-char hex (128 bytes) |
URLs & Domains
| Type | Syntax | Generated Value |
|---|---|---|
URL | SERVICE_URL_<NAME> | https://<name>-<uuid>.<domain> |
URL + port | SERVICE_URL_<NAME>_<PORT> | Same URL, proxy routes to container port |
URL + path | SERVICE_URL_<NAME>=/path | URL with path suffix |
URL + port + path | SERVICE_URL_<NAME>_<PORT>=/path | Combined |
FQDN | SERVICE_FQDN_<NAME> | <name>-<uuid>.<domain> (no scheme) |
FQDN + port | SERVICE_FQDN_<NAME>_<PORT> | Same FQDN, proxy routes to port |
URL Examples
environment:
# Basic URL generation
- SERVICE_URL_APP # https://app-abc123.example.com
# With port routing (proxy sends traffic to container port 3000)
- SERVICE_URL_APP_3000 # https://app-abc123.example.com
# With path
- SERVICE_URL_API=/v1 # https://api-abc123.example.com/v1
# With port AND path
- SERVICE_URL_API_8080=/v1 # https://api-abc123.example.com/v1
# FQDN only (no scheme)
- SERVICE_FQDN_APP # app-abc123.example.com
# Reference in other variables
- PUBLIC_URL=$SERVICE_URL_APP
- CORS_ORIGIN=${SERVICE_URL_APP}⚠️ Port Syntax Warning
Underscores in the identifier break port parsing:
# ❌ WRONG - underscore before port
SERVICE_URL_MY_SERVICE_3000 # Coolify can't parse the port
# ✅ CORRECT - use hyphen
SERVICE_URL_MY-SERVICE_3000 # Works correctlySpecial Types
| Type | Syntax | Use Case |
|---|---|---|
SUPABASEANON_KEY | SERVICE_SUPABASEANON_KEY | Supabase anon JWT (requires SERVICE_PASSWORD_JWT) |
SUPABASESERVICE_KEY | SERVICE_SUPABASESERVICE_KEY | Supabase service_role JWT |
These generate valid JWTs signed with SERVICE_PASSWORD_JWT for Supabase deployments.
Reusing Values
The same identifier always generates the same value:
services:
app:
environment:
- DB_PASSWORD=$SERVICE_PASSWORD_POSTGRES
worker:
environment:
- DB_PASSWORD=$SERVICE_PASSWORD_POSTGRES # Identical value
db:
environment:
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES # Identical valueDeclaration vs Reference
environment:
# Declaration - generates the URL AND sets up proxy routing
- SERVICE_URL_APP_3000
# Reference - uses the generated value in another variable
- PUBLIC_URL=$SERVICE_URL_APP
- WEBHOOK_URL=${SERVICE_URL_APP}/webhooksVersion Requirements
Magic environment variables in Git-sourced compose files require Coolify v4.0.0-beta.411 or later.
Official Coolify Docker Compose Documentation
Source: https://github.com/coollabsio/coolify-docs/blob/v4.x/docs/knowledge-base/docker/compose.md
This is the authoritative reference for Coolify Docker Compose behavior.
Key Concepts
Magic Environment Variables
Coolify generates dynamic environment variables using SERVICE_<TYPE>_<IDENTIFIER>:
- URL: Generates URL for the service. Add ports and paths as suffixes.
- FQDN: Generates FQDN (hostname without scheme). No port suffix needed.
- USER: Random 16-char string via
Str::random(16) - PASSWORD: Random password via
Str::password(symbols: false). UsePASSWORD_64for 64-char. - BASE64: Random string via
Str::random(32). UseBASE64_64orBASE64_128for longer.
Identifier Naming Rule
Identifiers with underscores (_) cannot use ports. Use hyphens (-) instead:
SERVICE_URL_APPWRITE_SERVICE_3000 ❌
SERVICE_URL_APPWRITE-SERVICE_3000 ✅Complete Example from Official Docs
services:
appwrite:
environment:
# http://appwrite-vgsco4o.example.com
- SERVICE_URL_APPWRITE
# http://appwrite-vgsco4o.example.com/v1/realtime
- SERVICE_URL_APPWRITE=/v1/realtime
# _APP_URL gets the value of SERVICE_URL_APPWRITE
- _APP_URL=$SERVICE_URL_APPWRITE
# http://appwrite-vgsco4o.example.com/ proxied to port 3000
- SERVICE_URL_APPWRITE_3000
# DOMAIN_NAME gets FQDN (appwrite-vgsco4o.example.com) - no port needed
- DOMAIN_NAME=${SERVICE_FQDN_APPWRITE}
# http://api-vgsco4o.example.com/api proxied to port 2000
- SERVICE_URL_API_2000=/api
# Password injected as SERVICE_SPECIFIC_PASSWORD
- SERVICE_SPECIFIC_PASSWORD=${SERVICE_PASSWORD_APPWRITE}
not-appwrite:
environment:
# Reuses password from Appwrite service
- APPWRITE_PASSWORD=${SERVICE_PASSWORD_APPWRITE}
# New URL: http://not-appwrite-vgsco4o.example.com/api
- SERVICE_URL_API=/apiKey observations: 1. SERVICE_URL_APPWRITE_3000 declares URL AND configures proxy to port 3000 2. $SERVICE_URL_APPWRITE references the URL value (without port suffix) 3. ${SERVICE_FQDN_APPWRITE} is available without explicit declaration 4. Same identifier (e.g., SERVICE_PASSWORD_APPWRITE) = same value everywhere
Environment Variable Syntax
environment:
- HARDCODED=hello # Not visible in Coolify UI
- VARIABLE=${UI_VARIABLE} # Editable in UI (empty)
- WITH_DEFAULT=${VAR:-hello} # Editable with default "hello"
- REQUIRED=${VAR:?} # Required - blocks deploy if empty
- REQUIRED_DEFAULT=${VAR:?default} # Required with prefilled defaultStorage Extensions
Create directory:
volumes:
- type: bind
source: ./data
target: /app/data
is_directory: true # Coolify creates thisCreate file with content:
volumes:
- type: bind
source: ./config.sql
target: /docker-entrypoint-initdb.d/config.sql
content: |
ALTER USER authenticator WITH PASSWORD :'pgpass';Exclude from Health Checks
services:
migration:
exclude_from_hc: trueVersion Requirements
Magic Environment Variables in Compose files based on Git sources requires Coolify v4.0.0-beta.411 and above.