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@dotclaudeAs 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-pipelineFiles 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-progressonly on pull requests; the deploy job holds its own concurrency group. - Only
mainpublishes and deploys. Every push condition readsgithub.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;mainonly tracks the newest build. shell: bashwith pipefail on every job that pipes curl into jq. Otherwise an empty body reaches jq silently. And never pipe curl intogrep -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_UUIDandCOOLIFY_TOKENbefore touching anything. - No secret is ever printed. Tokens only appear as
Authorizationheaders; 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.
More in deployment
- 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...