coolify/SKILL.md
Complete Coolify deployment and management: CLI operations, API access, Docker patterns, networking, Prometheus/Grafana, WordPress troubleshooting, SSL. Use for deploy, debug, manage Coolify services.
npx skillsauth add snqb/my-skills coolifyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Complete guide for deploying and managing applications on Coolify.
# Install Coolify CLI
curl -fsSL https://get.coolify.io/cli | bash
# Or manual install to ~/.local/bin
mkdir -p ~/.local/bin
curl -L https://github.com/coollabsio/coolify-cli/releases/latest/download/coolify-linux-amd64 -o ~/.local/bin/coolify
chmod +x ~/.local/bin/coolify
export PATH="$HOME/.local/bin:$PATH"
# Add Coolify instance
coolify context add production https://coolify.example.com YOUR_API_TOKEN
# List contexts
coolify context list
# Switch context
coolify context use staging
# Verify connection
coolify context verify
Get API token from Coolify dashboard: /security/api-tokens
# List all resources
coolify resource list
# Services
coolify service get SERVICE_UUID
coolify service start SERVICE_UUID
coolify service stop SERVICE_UUID
coolify service restart SERVICE_UUID
# Applications
coolify app get APP_UUID
coolify app logs APP_UUID --lines 500
coolify app start APP_UUID
coolify app restart APP_UUID
# Databases
coolify database list
coolify database start DB_UUID
coolify database backup DB_UUID
# Deploy application
coolify deploy APP_UUID
# Check deployment status
coolify deploy list APP_UUID
# Get deployment details
coolify deploy get DEPLOY_UUID
# List env vars
coolify app env list APP_UUID
# Set env var
coolify app env set APP_UUID DATABASE_URL "postgresql://..."
# Delete env var
coolify app env delete APP_UUID OLD_VAR
# Restart to apply
coolify app restart APP_UUID
# List servers with IPs
coolify server list -s
# Validate connection
coolify server validate SERVER_UUID
# Get server domains
coolify server domains SERVER_UUID
# Get all unhealthy services
coolify resource list --format json | jq '.[] | select(.status | contains("unhealthy"))'
# Extract service UUIDs
coolify service list --format json | jq -r '.[].uuid'
# Find running apps
coolify resource list --format json | jq '.[] | select(.type=="application" and .status=="running")'
When CLI doesn't support an operation:
# Get service details
curl -H "Authorization: Bearer $API_TOKEN" \
https://coolify.example.com/api/v1/services/SERVICE_UUID
# List all applications
curl -H "Authorization: Bearer $API_TOKEN" \
https://coolify.example.com/api/v1/applications
# Trigger deployment
curl -X POST -H "Authorization: Bearer $API_TOKEN" \
https://coolify.example.com/api/v1/applications/APP_UUID/deploy
Coolify appends unique suffixes on every deploy:
my-app-eg488k8w0o44o80800wwws4c-214629924110 # Deploy 1
my-app-eg488k8w0o44o80800wwws4c-222239183078 # Deploy 2
Solution: Use DNS Aliases
Coolify creates stable DNS aliases:
{
"DNSNames": [
"my-app-eg488k8w0o44o80800wwws4c-222708068545", // Dynamic (avoid!)
"my-app", // STABLE (use this!)
"3e280ed24989" // Container ID
]
}
# Find stable alias
docker inspect $(docker ps -q -f name=my-app) \
--format '{{json .NetworkSettings.Networks}}' | \
jq -r '.[].DNSNames[] | select(. | test("^[a-z]+-[a-z]+$"))'
# Check container network
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}' | jq 'keys[]'
# Check if on coolify network
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}' | jq 'has("coolify")'
# Connect to coolify network
docker network connect coolify CONTAINER
Cross-stack communication: Enable "Connect to Predefined Network" in Coolify UI.
Coolify UI can save placeholder text as values:
# What you see: SMARTPROXY_PASSWORD=required
# What deploys: SMARTPROXY_PASSWORD="required" # Broken!
Solution: Detect placeholders in code
def get_env(key: str, default: str = None) -> str:
val = os.getenv(key, default)
if val and val.lower() in ("required", "todo", key.lower()):
return default # Placeholder detected
return val
services:
api:
container_name: my-api # Stable name
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:8000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
default:
external: true
name: coolify # Connect to shared network
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your App │────▶│ Prometheus │────▶│ Grafana │
│ :8001 │ │ :9090 │ │ :3000 │
└─────────────┘ └─────────────┘ └─────────────┘
coolify network (shared)
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: my-app
static_configs:
- targets: ['my-app:8001'] # Use stable DNS alias!
metrics_path: /metrics
# provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090 # Container name on coolify network
isDefault: true
Avoid port 8000 — reserved by Coolify. Use 8001+ for metrics.
Via Coolify dashboard → Service → Terminal → select "wordpress"
Or via SSH:
docker exec -it CONTAINER bash
cd /var/www/html
# Check .htaccess
cat /var/www/html/.htaccess
# Remove last line (usually the problem)
sed -i '$d' /var/www/html/.htaccess
# Add to .htaccess (note: space, not =)
echo "php_value max_input_vars 3000" >> /var/www/html/.htaccess
echo "php_value upload_max_filesize 64M" >> /var/www/html/.htaccess
echo "php_value post_max_size 128M" >> /var/www/html/.htaccess
Test externally first:
curl https://site.com/wp-json/
If JSON returns, it's a false positive — Site Health loopback is blocked, not the API.
Ensure .htaccess has:
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Coolify uses Traefik with Let's Encrypt (auto-renewal).
echo | openssl s_client -servername domain.com -connect domain.com:443 2>/dev/null | \
openssl x509 -noout -dates -subject
Coolify supports team-level shared variables referenced via {{ team.VARIABLE_NAME }} in app env vars. These are encrypted in the shared_environment_variables table.
The REST API does NOT expose shared variable CRUD endpoints. The {{ team.X }} syntax works when setting app env vars, but you cannot list/create/delete shared variables via API.
The only reliable programmatic method:
sshpass -p 'PASSWORD' ssh user@COOLIFY_HOST \
"docker exec coolify php artisan tinker --execute=\"
\\\$var = new \App\Models\SharedEnvironmentVariable;
\\\$var->key = 'mybot';
\\\$var->value = 'TOKEN_VALUE';
\\\$var->team_id = 0;
\\\$var->save();
echo 'Created: ' . \\\$var->id;
\""
# Via Coolify's own DB (not the app DB)
sshpass -p 'PASSWORD' ssh user@COOLIFY_HOST \
"docker exec \$(docker ps -q -f name=coolify-db) psql -U coolify \
-c 'SELECT id, key, team_id FROM shared_environment_variables ORDER BY key;'"
Values are Laravel-encrypted (base64 blobs) — cannot read plaintext from DB.
The REST API POST /applications/{uuid}/envs creates duplicate entries (bug in beta). Use artisan tinker instead:
sshpass -p 'PASSWORD' ssh user@COOLIFY_HOST << 'EOF'
docker exec coolify php artisan tinker --execute="
\$v = \App\Models\EnvironmentVariable::create([
'key' => 'DATABASE_URL',
'value' => '{{ team.PROD_DB_URL }}',
'resourceable_type' => 'App\\\Models\\\Application',
'resourceable_id' => APP_ID, // from applications table
'is_preview' => false,
'is_runtime' => true,
'is_buildtime' => true,
'is_shared' => true, // true when using {{ team.X }}
]);
echo 'Created id=' . \$v->id;
"
EOF
Important: Coolify's model observer auto-creates a preview entry. Creating one runtime entry produces TWO rows (runtime + preview) — this is correct. Don't manually create preview entries.
| Issue | Detail |
|-------|--------|
| API POST duplicates | REST API creates 2 entries per POST (build + runtime). Use artisan tinker instead. |
| f-string brace eating | Python f"{{ team.{name} }}" produces SINGLE braces. Use raw strings or quadruple braces f"{{{{ team.{name} }}}}". |
| Preview entries are normal | Each key has 2 DB rows (runtime: is_preview=f, is_runtime=t, is_buildtime=t + preview: is_preview=t, is_runtime=f, is_buildtime=f). The API shows both — this looks like duplicates but is correct. |
| Values encrypted | value column uses Laravel encryption. Cannot read/compare directly in DB. |
| Delete auto-created extras | If you accidentally create 3 rows, delete the middle one: is_preview=t AND is_runtime=t AND is_buildtime=t. |
Every domcom bot app should have these 8 vars:
DATABASE_URL = {{ team.PROD_DB_URL }} 🔗 shared
TELEGRAM_BOT_TOKEN_XX = {{ team.xxbot }} 🔗 shared
COLLAGE_BOT_TOKEN = {{ team.alertmalertbot }} 🔗 shared
REDIS_URL = redis://parser-redis:6379/0
LOG_LEVEL = INFO
ADMIN_PASSWORD = domcom2024
ADMIN_SECRET_KEY = xx-admin-secret-key-change-in-prod
PAYMENT_STUB_MODE = false
sshpass -p 'PASSWORD' ssh user@COOLIFY_HOST \
"docker exec \$(docker ps -q -f name=coolify-db) psql -U coolify \
-c \"SELECT id, uuid, name FROM applications WHERE name LIKE '%bot%' ORDER BY name;\""
# Container running?
docker ps | grep name
# Logs
docker logs CONTAINER --tail 100
# Env vars
docker exec CONTAINER printenv | grep KEY
# Network connectivity
docker exec CONTAINER curl -s http://other-service:port/health
# DNS resolution
docker exec CONTAINER nslookup other-service
# Health check status
docker inspect CONTAINER | jq '.[0].State.Health'
curl -s 'http://localhost:9090/api/v1/targets' | \
jq '.data.activeTargets[] | {job: .labels.job, health: .health, lastError: .lastError}'
| Issue | Cause | Fix |
|-------|-------|-----|
| no such host | Wrong network | Enable "Connect to Predefined Network" |
| Target down | Dynamic container name | Use stable DNS alias |
| Env var placeholder | UI saved placeholder | Detect in code or fix in UI |
| 502 Bad Gateway | Container unhealthy | Check health checks |
| Port conflict | Port 8000 used | Use 8001+ |
Service Down?
├── Check status: coolify resource list
├── Get details: coolify service get UUID
├── Check logs: coolify app logs UUID
├── Identify issue
├── Fix (restart, config, files)
└── Verify: coolify resource list
# Context
coolify context add NAME URL TOKEN
coolify context use NAME
# Resources
coolify resource list
coolify service restart UUID
coolify app logs UUID
# Deploy
coolify deploy UUID
# Env
coolify app env set UUID KEY "value"
coolify app restart UUID
# Find stable DNS alias
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}' | jq '.[].DNSNames'
# Check network
docker inspect CONTAINER | jq '.[0].NetworkSettings.Networks | keys'
# Connect to coolify network
docker network connect coolify CONTAINER
documentation
Enrich Markdown articles with inline Wikipedia links. First mention of each notable entity gets a hyperlink. Use when asked to add wiki links, enrich, or add references to .md files.
development
Structured visual QA: screenshot → batch issues → fix all → verify. Replaces the 300-cycle screenshot→edit death spiral. Optional bishkek review as exit gate. Use when building/polishing UI with browser testing, or when user asks for N iterations/reviews.
development
Find complex code, analyze intent, recommend battle-tested library replacements. Uses radon/eslint for detection, GitHub quality search for alternatives.
research
Research real-world UI patterns from curated galleries (Collect UI, Component Gallery, Mobbin). Use when exploring what exists: dropdowns, accordions, inputs, navigation, cards, modals, etc.