Coolify DevOps
The Problem with Environment Branches
Using test/prod branches to represent environments causes merge conflict pain. The modern solution is to use main as the single source of truth and trigger environments via tags/releases + GitHub Actions calling Coolify’s deploy webhook.
Recommended Strategy
Drop the test/prod branches entirely.
| Environment | Trigger | How |
|---|---|---|
| Dev | Push to main | Coolify built-in auto-deploy webhook |
| Test | Push a tag vX.Y.Z-rc1 | GitHub Action → Coolify webhook |
| Prod | Publish a GitHub Release | GitHub Action → Coolify webhook |
Setup
1. Coolify
Create 3 separate apps (dev/test/prod), all pointing to main. Enable auto-deploy only on the dev app.
The Two Webhooks in Coolify
Manual git webhook — this is what Coolify uses to receive push events from GitHub. It’s the same URL across all projects (Coolify routes by the shared secret). Not what you want for triggering test/prod.
Deploy webhook (auth required) — this is Coolify’s API endpoint that you call to trigger a deployment. It’s project-specific and requires a Bearer token. This is the one to use.
Setting Up the Deploy Webhook
Step 1 — Generate a Coolify API Token: In your Coolify dashboard → Security (or profile) → API Tokens → create one. This token can be reused across all your projects.
Step 2 — Get each app’s UUID: It’s embedded in the deploy webhook URL Coolify shows you on the app’s settings page:
https://your-coolify.com/api/v1/deploy?uuid=abc123xyz
Step 3 — Add GitHub Secrets per project:
| Secret | Value |
|---|---|
COOLIFY_API_TOKEN | The API token from Coolify (shared across projects) |
COOLIFY_TEST_APP_UUID | UUID from the test app’s deploy webhook URL |
COOLIFY_PROD_APP_UUID | UUID from the prod app’s deploy webhook URL |
2. GitHub Action for Test
Triggers on v*-rc* tags:
on:
push:
tags: ['v*-rc*']
jobs:
deploy-test:
runs-on: ubuntu-latest
steps:
- name: Trigger Coolify test deployment
run: |
curl -X GET "https://your-coolify.com/api/v1/deploy?uuid=${{ secrets.COOLIFY_TEST_APP_UUID }}&force=false" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}"3. GitHub Action for Prod
Triggers on GitHub Release published:
on:
release:
types: [published]
jobs:
deploy-prod:
runs-on: ubuntu-latest
steps:
- name: Trigger Coolify prod deployment
run: |
curl -X GET "https://your-coolify.com/api/v1/deploy?uuid=${{ secrets.COOLIFY_PROD_APP_UUID }}&force=false" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}"Workflow in Practice
commit → main → dev auto-deploys ✅
git tag v1.2.0-rc1
git push --tags → test deploys ✅
gh release create v1.2.0 → prod deploys ✅
No more PRs between branches, no merge conflicts. Everything flows forward from main. The tag/release is just metadata that triggers the webhook — Coolify deploys whatever commit main is at.