Custom Template Sources Free
Add your own docker-compose service templates from any GitHub repository — they appear alongside Coolify's built-in 200+ one-click services.
Overview
Coolify ships with 200+ built-in one-click service templates (Ghost, Gitea, Uptime Kuma, n8n, etc.). Custom Template Sources lets you extend this list with your own templates hosted in any GitHub repository — public or private.
- Same format as Coolify — templates use the exact same YAML structure as built-in templates
- Seamless integration — custom templates appear in the New Resource page with a small label identifying their source
- Auto-sync — templates are fetched from GitHub periodically (default: every 6 hours)
- Safe — after deploying a service, the compose YAML is stored in the database; removing a template source has zero impact on running services
Quick Start
- Create a GitHub repository with a
templates/compose/folder - Add a YAML template file (see Template Format)
- In Coolify, go to Settings > Templates
- Click + Add Source and enter your repository URL
- Click Save & Sync
- Go to New Resource — your template now appears in the Services list
Adding a Template Source
Via the UI
- Navigate to Settings > Templates
- Click + Add Source
- Fill in:
- Name — a display name (e.g., "My Company Templates")
- Repository URL — the GitHub repository URL
- Branch — the branch to fetch from (default:
main) - Folder Path — path to the templates folder (default:
templates/compose) - Auth Token (optional) — a GitHub Personal Access Token for private repositories
- Click Save & Sync
Via the API
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My Templates",
"repository_url": "https://github.com/myorg/coolify-templates",
"branch": "main",
"folder_path": "templates/compose"
}' \
https://coolify.example.com/api/v1/template-sources
Template Format
A custom template is a standard docker-compose YAML file with special comment headers that provide metadata.
Metadata Headers
Add these comment lines at the top of your YAML file, before any YAML content:
# documentation: https://docs.example.com/
# slogan: A brief description of your service.
# tags: tag1,tag2,tag3
# category: self-hosting
# logo: svgs/myservice.svg
# port: 8080
| Header | Required | Description |
|---|---|---|
documentation | No | URL to the service's documentation |
slogan | No | Short description shown below the service name (max 80 characters) |
tags | No | Comma-separated search tags |
category | No | Category for filtering (e.g., monitoring, cms, ai, development) |
logo | No | Path to the service logo (relative to repo root, or absolute URL). SVG preferred |
port | No | Primary port the service listens on |
type | No | database or application — overrides automatic classification |
ignore | No | Set to true to exclude from the template list |
Docker Compose Body
The rest of the file is a standard docker-compose definition:
# documentation: https://github.com/louislam/uptime-kuma
# slogan: A self-hosted monitoring tool.
# tags: monitoring,status,uptime
# category: monitoring
# 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
Environment Variables
Coolify detects environment variables using ${VARIABLE} syntax and displays them in the UI:
| Syntax | Behavior |
|---|---|
VARIABLE=hardcoded | Not visible in the Coolify UI |
VARIABLE=${MY_VAR} | Editable in UI, user must provide a value |
VARIABLE=${MY_VAR:-default} | Editable in UI, falls back to default |
VARIABLE=${MY_VAR:?} | Required — deployment fails if not set |
Magic Variables
Coolify generates dynamic variables using the pattern SERVICE_<TYPE>_<IDENTIFIER>:
| Pattern | Example | Description |
|---|---|---|
SERVICE_URL_<NAME>_<PORT> | SERVICE_URL_MYAPP_8080 | Generates the service URL |
SERVICE_FQDN_<NAME>_<PORT> | SERVICE_FQDN_MYAPP_8080 | Generates a fully-qualified domain name |
SERVICE_USER_<NAME> | SERVICE_USER_MYSQL | Generates a random username |
SERVICE_PASSWORD_<NAME> | SERVICE_PASSWORD_MYSQL | Generates a random password |
SERVICE_BASE64_64_<NAME> | SERVICE_BASE64_64_TOKEN | Generates a 64-character base64 string |
Repository Structure
your-repo/
├── templates/
│ └── compose/
│ ├── service-a.yaml
│ └── service-b.yaml
├── svgs/ # Optional: store logos here
│ ├── service-a.svg
│ └── service-b.svg
└── README.md
- Template files must have
.yamlor.ymlextension - Only files in the configured folder are scanned (no recursive subdirectories)
- The template name is derived from the filename:
my-service.yamlbecomes "My Service" - Maximum 500 templates per source, 1MB per template file
Auto-Sync
All enabled template sources are automatically synced every 6 hours by default. Configure the interval via environment variable:
# Every 6 hours (default)
CORELIX_TEMPLATE_SYNC_FREQUENCY="0 */6 * * *"
# Every hour
CORELIX_TEMPLATE_SYNC_FREQUENCY="0 * * * *"
# Disable auto-sync
CORELIX_TEMPLATE_SYNC_FREQUENCY=
You can also trigger a manual sync from Settings > Templates or via the API:
# Sync one source
curl -X POST -H "Authorization: Bearer $TOKEN" \
https://coolify.example.com/api/v1/template-sources/{uuid}/sync
# Sync all sources
curl -X POST -H "Authorization: Bearer $TOKEN" \
https://coolify.example.com/api/v1/template-sources/sync-all
Source Labels
Custom templates display a small badge on the service card in the New Resource page, showing which source they came from. This helps distinguish custom templates from built-in ones.
Name Collisions
When a custom template has the same name as a built-in template:
- Built-in templates always win — the custom template gets a suffix (e.g.,
ghost-my-company) - Custom-to-custom collisions — both get the source slug suffix
Troubleshooting
Templates not appearing
- Check that the source is enabled in Settings > Templates
- Click Sync to trigger a manual refresh
- Check for sync errors — the status badge shows
synced,failed, orsyncing - Ensure YAML files have a
servicessection — files without it are skipped
Authentication errors
For private repositories, add a GitHub Personal Access Token with repo scope.
Rate limits
Unauthenticated GitHub requests are limited to 60/hour. Add a token to get 5,000/hour.