dotclaude

Skill in deployment

ghcr-coolify-pipeline

Write the single GitHub Actions workflow that takes a repository from pull request to production through GHCR and Coolify. Use when adding deployment to a repo, replacing a CI-only workflow, or debugging a deploy job.

Arguments
(optional) the stack (next or express), the production URL, and the existing workflow file

Install

As a plugin

Bundled in the deploy plugin. Add the marketplace once, then install the bundle; it updates with the marketplace.

/plugin marketplace add KhaledSaeed18/dotclaude
/plugin install deploy@dotclaude

As a single item

Run from the project root; the skill lands under .claude/. Copy it to ~/.claude/ to make it global.

npx shadcn@latest add KhaledSaeed18/dotclaude/ghcr-coolify-pipeline

Files installed

  • .claude/skills/ghcr-coolify-pipeline/SKILL.md
  • .claude/skills/ghcr-coolify-pipeline/reference/pipeline-anatomy.md
  • .claude/skills/ghcr-coolify-pipeline/templates/pipeline.express.yml
  • .claude/skills/ghcr-coolify-pipeline/templates/pipeline.nextjs.yml

What it does

One workflow, five jobs: classify what changed, lint the ops layer, run the quality gates, build and smoke-test the runtime image, deploy through the Coolify API and prune the registry. Pull requests prove the image without publishing it; a push to main that changes something shipped publishes and deploys; "Run workflow" on main redeploys. Nothing in it needs a long-lived cloud key beyond one Coolify token.

Hard rules: never break these

  • A main run is never cancelled mid-flight and deploys never overlap. cancel-in-progress only on pull requests; the deploy job holds its own concurrency group.
  • Only main publishes and deploys. Every push condition reads github.ref == 'refs/heads/main' && github.event_name != 'pull_request'.
  • The image is proven before it is pushed. The smoke test runs the image the way production will: own user, own probe, both locales or a real database, and a timed stop.
  • Immutable tags. sha-<12> is what deploys and rollbacks pin; main only tracks the newest build.
  • shell: bash with pipefail on every job that pipes curl into jq. Otherwise an empty body reaches jq silently. And never pipe curl into grep -q: it closes the pipe early and fails curl; write to a file first.
  • Fail fast when configuration is missing. The deploy job checks COOLIFY_URL, COOLIFY_APP_UUID and COOLIFY_TOKEN before touching anything.
  • No secret is ever printed. Tokens only appear as Authorization headers; the token value is pasted into GitHub by the owner.

Step 1: Choose the template and read the repo

Stack Template Smoke test proves
Next.js pipeline.nextjs.yml healthy, non-root, both locales, auth redirect, headers, image optimizer, stop within 10 s
Express and Prisma pipeline.express.yml migrations against a fresh Postgres, readiness probe, non-root, stop within 10 s, plus a schema and migration guard in the quality job

Read the existing workflow for the gates it already runs (lint, typecheck, tests, coverage, unused-code checks, build) and keep every one of them in the quality job. Check package.json for packageManager and .nvmrc for the Node version; the setup actions read both, so pin them if missing. Decide which Playwright or end-to-end suites stay local: anything that provisions its own database and sibling services usually does, because a free plan shares a small minutes budget across repositories.

Step 2: Adapt the change classifier

The changes job decides code (anything the gates can judge) and runtime (anything that ends up in the image). Edit the two regexes to the repo's layout: source folders, config files, lockfiles, Dockerfile, entrypoint, probe and shutdown scripts count as runtime; tests and docs do not. A docs-only push then stops after one job. The anatomy of every job and the reason for each line is in reference/pipeline-anatomy.md.

Step 3: Fill the variables and secrets

Name Kind Set by Value
SITE_URL variable you, gh variable set the URL the deploy job probes; the rehearsal domain first, the real one after cutover
API_ORIGIN or API_BASE_URL variable you the build-time backend origin baked into the image
COOLIFY_URL variable you the Coolify instance
COOLIFY_APP_UUID variable you from the application's URL
COOLIFY_TOKEN secret owner API token with deploy, read, write
VPS_SSH_KEY, VPS_HOST_KEY, VPS_HOST secrets owner only for the Express template's pre-deploy backup over SSH

GITHUB_TOKEN with packages: write pushes to GHCR; nothing else is needed for the registry.

Step 4: Lint and run it

docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7   # includes shellcheck of every run block

Open a pull request; the run must be green with the deploy job skipped. After the merge, watch the main run to completion and read the conclusion field of each job rather than the overall badge:

gh run watch <id> --exit-status; gh run view <id> --json conclusion,jobs --jq '.jobs[] | "\(.name): \(.conclusion)"'

The first main run's live probe fails if the domain has no DNS yet; that is expected and clears with the live-domain-cutover skill. The fix-ci skill covers a failing step; the github-actions-pipeline skill the general workflow rules.

Report the workflow path, the gates kept, the variables set, the secrets the owner must add, and the first run's per-job result.

This is the skill as installed, rendered from its source on GitHub. Read it as Markdown.

  • coolify-image-appCreate and configure a Coolify application that runs a prebuilt image from a registry. Use when adding an app to Coolify, fixing its health check...
  • coolify-vps-baselinePrepare a Linux VPS to run production containers under Coolify. Use when setting up a new server, auditing an existing one, or connecting a registry...
  • deploy-verificationProve a deployment from the server side. Use when a deploy finished and needs sign-off, when a rolling update may have dropped requests, or when a proxy...
  • express-prisma-coolify-imagePackage an Express and Prisma API as a runtime-only image that migrates its Postgres database at start. Use when containerising a Node API with a...
  • live-domain-cutoverMove a live domain from one host to another without downtime or lost sessions, with a rehearsal, a timed DNS switch, a soak and the old host removed. Use...
  • nextjs-coolify-imagePackage a Next.js app as a runtime-only container image with a health probe and a fast shutdown. Use when containerising a Next.js app, when its image is...