Configuration Coolify + Doppler

Provisionne et met à jour un déploiement Coolify à partir de coolify.yaml, avec injection de secrets Doppler.

Spar Skills Guide Bot
DeveloppementIntermédiaire
0027/07/2026
Claude Code
#coolify#doppler#deployment#devops#provision

Recommandé pour


name: setup-coolify description: This skill should be used when the user runs /setup-coolify, /setup-coolify provision, /setup-coolify plan, /setup-coolify seed, /setup-coolify init_cicd, /setup-coolify init_app, or /setup-coolify validate. Provisions and updates a Coolify deployment for the current repo from coolify.yaml, configures Doppler secret injection (all env_vars including NEXT_PUBLIC_* injected at runtime via DOPPLER_TOKEN — same-image promotion model), and generates .github/workflows/deploy.yml. Reads coolify.yaml from the working directory and credentials from ~/.claude/coolify.json. Designed to work across multiple repos and multiple Coolify servers via the server alias in coolify.yaml. disable-model-invocation: true argument-hint: "[provision | plan | seed | init_cicd | init_app | validate | (blank = provision)]" allowed-tools: Read Write Bash

setup-coolify

Arguments: $ARGUMENTS

Provision or update a Coolify + Doppler deployment from coolify.yaml in the current working directory. Same skill works for any repo and any Coolify server — the server: alias in coolify.yaml selects both the Coolify URL and the Doppler account.

Subcommands

| Form | Action | |------|--------| | /setup-coolify | Provision/update: ensures Doppler keys exist, upserts every app in the environments: map (staging + production required; extra envs like qa provisioned identically), syncs env vars, mounts Doppler-fallback volume. Does NOT deploy — the first deploy fires on push to main via the generated workflow. Idempotent. | | /setup-coolify provision | Explicit alias for blank — same as /setup-coolify with no arguments. Provision/update all apps idempotently. | | /setup-coolify plan | Read-only diff (Terraform-style): runs provision.sh --plan, reporting + CREATE / = EXISTS / ~ PATCH per resource (project, apps, volumes, Doppler tokens, DNS records) against live state, then exits. Nothing is mutated. Use before re-running provision on a production server. | | /setup-coolify seed | Explicit Doppler gap-fill from .env.local (seeds dev + stg) and .env.production (seeds prd). Runs bash $HOME/.claude/skills/setup-coolify/scripts/seed.sh. Logs every key set. Never overwrites an existing Doppler value. Run after creating a new repo's Doppler project before running provision. | | /setup-coolify init_cicd | Interactive setup of ~/.claude/coolify.json for a new server alias. Prompts for url, api_key, doppler_account, ssh_host, doppler_token. Validates existing credentials before prompting for replacement. | | /setup-coolify init_app | Bootstraps coolify.yaml and .github/workflows/deploy.yml in the current repo. Prompts for project name, server alias, domains, env vars, and optional deploy_server/deploy_ssh_host. Seeds dev+stg Doppler configs from .env.local when present. | | /setup-coolify validate | Strictly read-only validation: verifies coolify.yaml schema, coolify.json Tier 1 fields (url, api_key, doppler_account, ssh_host, doppler_token), Tier 2 feature-gated fields (cloudflare_api_token when dns.credential_source: coolify_json; deploy_ssh_host/deploy_vps_ip when deploy_server is set), Doppler key presence, Coolify API reachability. No Doppler mutations — run /setup-coolify seed separately to fill missing keys. |

Secrets injection model (same-image promotion)

Coolify receives only DOPPLER_TOKEN — a service token scoped to the matching Doppler config (stg or prd). No other secret values are stored in or pass through Coolify. At container start, the Dockerfile ENTRYPOINT runs doppler run which uses DOPPLER_TOKEN to fetch all env_vars secrets directly from Doppler and inject them into the process environment.

The same Docker image is promoted from staging to production without a rebuild; the only thing that differs between the two app instances is the DOPPLER_TOKEN (scoped to the matching Doppler config). This means:

  • Staging gets a service token for the stg Doppler config → all staging secrets
  • Production gets a service token for the prd Doppler config → all production secrets
  • DOPPLER_TOKEN itself is stored in Coolify and is visible in the Coolify UI, API responses, and deployment logs. It is a scoped service token — an attacker who obtains it can read all secrets in the matching Doppler config. Protect it accordingly (rotate on exposure; --rotate-tokens flag in provision).
  • Actual secret values (DATABASE_URL, API keys, etc.) are never stored in or pass through Coolify. They flow directly from Doppler to the container at start time via doppler run.

The # build_time: true trailing-comment annotation in coolify.yaml is reserved for a future per-env build mode and is NOT currently parsed by this skill. Under the current model, the annotation has no behavioural effect — every env_var is treated identically (runtime-injected). Do not rely on the annotation to change provisioning behaviour today.

Execution flow (provision = blank arguments)

  1. Load and validate config

    • Parse ./coolify.yaml. Bail if missing or invalid YAML.
    • Read ~/.claude/coolify.json. Look up servers.$SERVER_ALIAS entry. Bail if missing.
    • Run bash $HOME/.claude/skills/setup-coolify/scripts/validate.sh. If non-zero, print errors and bail BEFORE touching Coolify.
  2. Discover Coolify topology by lookup-by-name (no hardcoded UUIDs)

    • Source lib-coolify-api.sh. Call coolify_upsert_project "$PROJECT_NAME" to get project UUID.
    • Resolve DEPLOY_SERVER_NAME: uses deploy_server in coolify.yaml if set, otherwise reads server_name from ~/.claude/coolify.json (servers.<alias>.server_name, default localhost). Call coolify_get_server_uuid "$DEPLOY_SERVER_NAME". Bail if not found.
    • Call coolify_get_destination_uuid "$DEPLOY_SERVER_UUID" (which scans existing applications on the server first, falling back to /destinations).
    • Resolve DEPLOY_SSH_HOST: uses deploy_ssh_host in coolify.json if set, falling back to ssh_host. REQUIRED — bail if missing (used in step 3 to create the Doppler-cache Docker volume on the deployment VPS).
  3. Upsert staging app

    • Compute name: ${PROJECT_NAME}-staging (e.g. skillmap-staging).
    • coolify_find_app_by_name — if UUID returned, skip create. Else POST /applications/private-github-app with source_type: registry and docker_registry_image_name: $REGISTRY_IMAGE. PATCH is_auto_deploy_enabled=false.
    • Source lib-doppler-api.sh. Create a service token scoped to staging config. Set only DOPPLER_TOKEN on the Coolify app — no other secret values are stored in Coolify. The app's Dockerfile ENTRYPOINT runs doppler run which uses DOPPLER_TOKEN to fetch all env_vars secrets directly from Doppler at container start.
    • SSH to the deployment VPS (DEPLOY_SSH_HOST) and run docker volume create ${APP_UUID}-doppler-cache. PATCH the app with custom_docker_run_options: --mount source=${APP_UUID}-doppler-cache,target=/etc/doppler-cache.
  4. Upsert production app (same flow, name = ${PROJECT_NAME}-production)

  5. Write coolify_app_ids back to coolify.yaml (consumed by generate-workflow.sh to embed app UUIDs in deploy.yml; provisioning never reads it back — every run re-resolves by name)

  6. Done. provision.sh does NOT trigger an initial deploy. The first deploy is fired by pushing to main, which activates the generated .github/workflows/deploy.yml (build → GHCR → deploy-staging → smoke-test → deploy-production). To redeploy manually, push any commit to main or trigger the workflow from the GitHub Actions UI.

init_cicd flow

Interactive prompts (server credential collection):

  • Server alias to add (string, e.g. my-server)
  • Coolify URL (e.g. https://coolify.example.com)
  • API key (paste — token displayed once in Coolify UI)
  • Doppler account name (e.g. my-doppler-account)
  • SSH host alias (e.g. my-vps)
  • Cloudflare API token (optional — leave blank if DNS is managed outside Cloudflare or manually). When provided, stored as cloudflare_api_token under the server entry. Token requires Zone:DNS:Edit scope. Also prompt for dns_default block: zone name (e.g. example.com) and credential key (cloudflare_api_token). This pre-populates the dns: block for future coolify.yaml generation and is used by provision.sh and test/e2e.sh to auto-create DNS A records.

If the alias already exists in ~/.claude/coolify.json, validates existing credentials first (Coolify API ping + doppler whoami). Prompts to replace only if validation fails.

Re-run to fill missing fields: As the skill evolves, new fields are added to the server entry schema. Re-running /setup-coolify init_cicd for an existing alias is the supported upgrade path — it skips fields that already pass validation and only prompts for absent or failing fields. doppler_token is now a Tier 1 required field (not optional); validate will exit 1 if it is missing. Re-run init_cicd to add it to an existing entry.

Merge into ~/.claude/coolify.json (preserve existing servers). chmod 0600.

Example resulting server entry:

"my-server": {
  "url": "https://coolify.example.com",
  "api_key": "...",
  "doppler_account": "MyOrg",
  "ssh_host": "my-vps",
  "cloudflare_api_token": "...",
  "dns_default": {
    "provider": "cloudflare",
    "zone_name": "example.com",
    "credential_source": "coolify_json",
    "credential_key": "cloudflare_api_token"
  }
}

init_app flow

Run from the target repo root (bash ~/.claude/skills/setup-coolify/init/init.sh). Writes ./coolify.yaml and .github/workflows/deploy.yml.

Interactive prompts:

  • Project name, server alias, Doppler project, registry image, staging/production domains
  • DNS provider (cloudflare/none) and optional DNS zone + credential config
  • Optional deploy_server (Coolify-registered server name) and build context/Dockerfile
  • Env var keys

After writing files, detects .env.local and offers to seed dev and stg Doppler configs from it.

plan flow

Runs bash $HOME/.claude/skills/setup-coolify/scripts/provision.sh --plan.

Read-only: runs validate, resolves topology (project, server, destination, DNS zone), then for every environment reports + CREATE (resource absent), = EXISTS (matches desired state), or ~ PATCH (lists exactly which fields would change: domains, volume mount, image name, health_check_path, DNS record IP). Exits 0 without mutating anything.

validate flow

Runs bash $HOME/.claude/skills/setup-coolify/scripts/validate.sh.

Strictly read-only — no Doppler mutations.

  1. Parses and schema-checks coolify.yaml.
  2. Verifies coolify.json Tier 1 fields: url, api_key, doppler_account, ssh_host, doppler_token (hard fail if missing).
  3. Verifies coolify.json Tier 2 fields gated on coolify.yaml features: cloudflare_api_token (required when dns.credential_source: coolify_json), deploy_ssh_host/deploy_vps_ip (required when deploy_server: is set).
  4. Verifies Coolify API reachability, SSH connectivity, DNS credentials.
  5. Checks that every env_vars key from coolify.yaml exists in Doppler staging AND production with a non-placeholder value. Reports all failures before exiting (error-accumulation pattern).

To fill missing Doppler keys from local .env files, run /setup-coolify seed separately.

See also

  • ~/.claude/skills/setup-coolify/references/api-reference.md — Coolify + Doppler API endpoint reference
  • docs/schema.md (in the repo) — coolify.yaml and coolify.json schema reference, including the reserved build_time: true annotation
Skills similaires