cicd-pipeline
Skill0 starsChanged 2 months agoMITView on GitHub
Method and checklists for shipping a containerized application with GitHub Actions, Docker and Kubernetes — build once, publish a signed immutable image, promote that exact digest through gated environments to production. Covers the inventory to gather first, the dev/infra boundary, build-once/promote-by-digest, supply-chain integrity, deployment strategies, secrets, health-gated rollout, GitHub Environments approvals, and rollback. Use when asked to create or configure a pipeline, onboard an app to CI/CD, set up an environment, promote to production, or migrate a hand-run deploy to an automated one.
- Reads credentials
9 other projects use a version of this.
Adopted by
Most recently taken up by
- SWEStash/swe-workflow-skillstheir own version, first seen Nov 8, 2026
- d-o-hub/github-template-ai-agentstheir own version, first seen Nov 3, 2026
- NinjaSln-labs/agent-skillstheir own version, first seen Oct 2, 2026
- timwukp/agent-skills-best-practicetheir own version, first seen Sep 16, 2026
- Abdox-menara/guard-skillstheir own version, first seen Aug 24, 2026
- drewid74/ai_skillstheir own version, first seen May 20, 2026
Other versions9 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 9
- timwukp/agent-skills-best-practice★ 10same name
- drewid74/ai_skills★ 8same name
- d-o-hub/github-template-ai-agents★ 2same name
- albertdobmeyer/openskill-forge★ 1same name
- mmondora/claude-skills★ 1same name
- NinjaSln-labs/agent-skills★ 1same name
- SWEStash/swe-workflow-skills★ 1same name
- Abdox-menara/guard-skills★ 0same name
- herbert051281/mission-control★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository2 other files it gives its agents.
- network-troubleshootingSkill
- writing-styleSkill
The file
We split this skill into the rules it gives an agent
5 rules. 0 are common, 4 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 8 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 24
Inventory before any file: No manifest, workflow or secret declaration without the full dependency inventory (next section).
- Distinctive: line 44
Every workload (Deployment / StatefulSet / CronJob) and per workload: image (own vs upstream), ports, env vars, mounted config, command/args
- Distinctive: line 124
Remove dev-only config (debug, hot-reload, local hosts)
- Distinctive: line 217
Validate: run the review; verify the Environment approval actually blocks.
- Very specific: line 228
TLS / file secrets: prefer an automated in-cluster issuer so no static key exists.
“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CI/CD pipeline — GitHub Actions, Docker, Kubernetes
Build one immutable, signed image in CI; promote that exact image by digest through environments in CD, each stage gated by checks and — for production — a GitHub Environment approval. Details differ per app; the sequence, the boundaries and the failure modes do not.
- CI/CD — GitHub Actions. Registry — GHCR (
ghcr.io/<org>/<app>). Build —docker buildx. - Deploy target — a Kubernetes cluster (Deployment + Service + Ingress, via Helm or Kustomize). A single-host Docker Compose target is the lightweight option.
Reference architecture
- Delivery config separate from app code — a
deploy/folder or a dedicated repo. Reason: different change cadence and review rules. It holds the workflows and per-environment values. - Build once, promote by digest. One image per release candidate, identified by content digest (
@sha256:…). The same digest flows staging → production — never rebuilt or re-tagged per environment. Tag with the version (X.Y.Z), pin by digest in every manifest. Environment is a deploy-time input. - Promotion, not branches. Trunk-based: one
main, every merge is a release candidate, promoted through workflow stages gated by tests, policy and approvals. Environment chosen by a workflow input — one parameterized workflow, not drifting copies. (Branch-per-environment works too; map the stages onto branches.) - Keyless auth. Workflows reach the cluster and cloud APIs via GitHub OIDC, not stored long-lived credentials. GHCR uses
GITHUB_TOKENwithpackages: write. - Scoped identity. Each deploy identity is bound to one environment and expires. No human has standing write access to production; changes go through the pipeline. Break-glass is pre-approved, logged, time-boxed.
- Version marker: a signed git tag
X.Y.Z(SemVer). CI and CD fail closed when it is absent.
Core rules
- Inventory before any file. No manifest, workflow or secret declaration without the full dependency inventory (next section). Missing item → list it and stop. Never infer a value from a template default or another app.
- Zero assumptions about real config. Ports, DB hosts, domains, credentials, sizing, versions — confirmed with whoever operates the target. When migrating, every inherited value is a candidate to confirm, not copy.
- Dev/infra boundary. The
Dockerfileand source belong to the developer — do not edit them. Manifests, workflows, secrets, networking, ingress, DNS belong to infra. A build/code failure → report and wait; do not work around it in the pipeline. For a framework-level issue, say which value is missing and which layer owns it (manifest = infra;Dockerfile/app config = dev), then hand the check back. - Build once, promote by digest. The image validated in staging is bit-for-bit the one that reaches production.
- Supply-chain integrity. CI generates an SBOM, signs the image, attaches provenance (GitHub artifact attestations); vulnerability and secret scanning are blocking gates; the cluster verifies signature and provenance before admitting the image (admission policy).
- No secret in plaintext — not in a repo, manifest, workflow, build arg, or image layer. Prefer runtime injection into the workload over writing secrets to disk or the job environment.
- Immutable infrastructure. Config change → redeploy through the pipeline, never a live
kubectl edit/execin production. Roll the whole workload. - Validate before commit. Render every manifest with values resolved and lint it (
helm template/lint,kustomize build,kubectl apply --dry-run=server,docker compose config); fail on any unresolved required value. - Health-gated, progressive delivery. CD reports success only when the new version serves: readiness passed,
kubectl rollout statusconverged, smoke test green, error/latency SLOs held. Any failure → automatic rollback. In production use a strategy that limits blast radius (Part 1). - Production is gated and audited. A GitHub Environment with required reviewers and a freeze policy stands between a green build and production; the approval and outcome are recorded.
- Schema changes are backward-compatible (expand/contract). Old and new code both run against the DB during a rollout; migrations are decoupled from the code deploy and safe while the previous version still serves; never destructive in the same release that stops using the column.
- Rollback is a first-class path, exercised, run through the pipeline (redeploy the previous digest) — not a manual cluster edit.
- Observe the deploy. Emit a deployment marker; track DORA metrics; correlate alerts with deploys.
- Document on completion. Record the app in the wiki (delivery repo, environments, identities, strategy, rollback command) and every deviation.
Dependency inventory
Per application, and per environment where values differ:
- Cluster + namespace; deploy path (OIDC role,
kubeconfigcontext) - Every workload (Deployment / StatefulSet / CronJob) and per workload: image (own vs upstream), ports, env vars, mounted config, command/args
- Secrets: which values, where consumed, which store holds them
- External dependencies with real addresses (DB, cache, broker, SSO/LDAP, object storage, APIs) — host, port, DB/bucket
- Persistence: PVCs, storage class, size, backup/restore expectation
- Networking: who talks to whom, internal vs exposed, NetworkPolicy
- Exposure: domain, public URL, Ingress class, TLS termination and cert source, DNS record
- Health: readiness/liveness/startup endpoints, expected response, startup time, SLOs to gate on
- Resources: requests, memory limit, replica count, HPA bounds, PodDisruptionBudget
- Deployment strategy and its parameters; previous known-good digest and where it is recorded
Partial inventory → stop.
Placeholders
<org>/<repo> · <env>/<ENV> · <image> = ghcr.io/<org>/<app> · <digest> = sha256:… from CI · <workload> = Deployment name · <namespace> · <SECRET> = name identical to its manifest reference.
Common pitfalls
Manifest
- Indentation — a misindented
env:/volumeMounts:/resources:attaches to the wrong object or is dropped silently (compose:networks:/volumes:underservices:become a bogus service). Render and diff. - Unresolved reference — a value with no binding resolves empty; the workload starts without the credential. Fail the render.
- Mutable image reference —
latest, a floating minor, an overwritable tag. Pin by digest. - Isolation — wrong namespace, or missing
Service/NetworkPolicy→ unreachable. - Naming drift — inconsistent names/labels, or a leftover
-stagingstring in production values. - No
securityContext— baseline:runAsNonRoot,readOnlyRootFilesystem,allowPrivilegeEscalation: false, dropALLcaps,seccompProfile: RuntimeDefault. - Probe mistakes — no readiness; liveness and readiness on the same dependency-heavy endpoint (a downstream outage restart-loops every pod); no startup probe for a slow starter.
- Single replica / no PodDisruptionBudget — a node drain takes the service down.
Workflow
- Third-party actions pinned to a tag or
@main, not a commit SHA. - Over-broad
permissions:; secrets exposed to untrusted PR code (pull_request_target+ PR-head checkout). - Misaligned step blocks (
env:,with:). - Build+publish for an upstream image (unneeded), or missing for one you own.
- Environment selected by drifting copy-pasted workflows instead of one parameterized workflow with an
environment:input. - OIDC role / cluster context not scoped to the target environment.
- CD on
on: pushinstead of after the previous stage's gate.
Deploy execution
- Success reported on
kubectl applywithoutkubectl rollout status. - A non-backward-compatible migration in the same release as the code; a migration Job run non-blocking.
- CD re-deriving the version from git instead of consuming the digest CI produced.
- Auth not refreshed each run — an expired OIDC token fails with
unauthorized.
Part 1 — Building the pipeline
Once per application: produces the parameterized workflow plus one environment (typically staging). Adding production is Part 2.
1.1 Prerequisites
- App repo;
mainprotected (PR + review, required status checks, linear history) deploy/folder (or delivery repo), same protection- GHCR enabled;
GITHUB_TOKENpackages: writein the build job - Per environment: a namespace + scoped ServiceAccount/RBAC, reached from Actions via an OIDC role
- A secret store for real secrets only; non-sensitive values stay in the manifest
- Cluster admission policy: verify signature + provenance, enforce Pod Security "restricted", forbid mutable tags
- GitHub Environments (
staging,production);productionwith required reviewers and a freeze policy - Observability: a deployment-marker integration; a dashboard and SLO alerts
1.2 Layout
.github/workflows/ci.yml build, scan, sign, publish — emits digest + version
.github/workflows/cd.yml reusable; inputs: environment, digest
deploy/
base/ Helm chart or Kustomize base
envs/<env>/values.yaml per-environment overrides only1.3 Deployment manifest
From the developer's reference manifest, produce base/ + envs/<env>/:
- Image
<image>@${digest}— digest supplied at deploy time, never an inline build or mutable tag securityContextrestricted; readiness + liveness + startup probes distinct; requests set, memory limit = request, no CPU limit- Replica count, HPA and PodDisruptionBudget on every stateless workload
- Ports per the inventory; secrets via
secretKeyReffrom aSecretsynced by the store, never inlined Service/Ingress/PVCat the right scope;NetworkPolicyfor anything not fully public- Remove dev-only config (debug, hot-reload, local hosts)
- The per-environment file holds only what differs (replicas, resources, hostnames)
- Validate:
helm template/kustomize build+kubectl apply --dry-run=server
1.4 CI (ci.yml)
Steps, in order: trigger on merge to main and tag v* → checkout at the tag → resolve the version from the signed tag, exit non-zero if none → docker login ghcr.io with GITHUB_TOKEN → docker buildx build --push to <image>:X.Y.Z, capture the digest from --metadata-file → generate SBOM → scan image + deps + secrets, block on policy violations → sign the digest and attach provenance → set digest and version as job outputs.
permissions: { contents: read, packages: write, id-token: write, attestations: write }
# key steps
- id: ver
run: |
TAG=$(git describe --tags --exact-match) || { echo "no release tag"; exit 1; }
echo "version=$TAG" >> "$GITHUB_OUTPUT"
- id: build
uses: docker/build-push-action@<sha>
with: { push: true, tags: "ghcr.io/${{ github.repository }}:${{ steps.ver.outputs.version }}" }
- uses: actions/attest-build-provenance@<sha>
with: { subject-name: "ghcr.io/${{ github.repository }}", subject-digest: "${{ steps.build.outputs.digest }}" }1.5 CD (cd.yml, reusable)
Inputs: environment, digest (from CI — never re-derived). Steps: environment: on the job so GitHub enforces reviewers/rules → assume the environment's OIDC role, set the kubeconfig context → backward-compatible migration Job if any (apply, wait, abort on failure) → deploy with the digest pinned → wait for convergence + health, roll back on failure → smoke test by the service address → emit the deployment marker.
on: { workflow_call: { inputs: {
environment: { type: string, required: true }, digest: { type: string, required: true } } } }
permissions: { id-token: write, contents: read }
jobs:
deploy:
environment: ${{ inputs.environment }} # GitHub enforces reviewers / rules
steps:
- uses: actions/checkout@<sha>
- uses: <helm-setup-action>@<sha> # + your cloud's OIDC login action for the kubeconfig
- run: |
helm upgrade --install <workload> deploy/base -n <namespace>-${{ inputs.environment }} \
-f deploy/envs/${{ inputs.environment }}/values.yaml \
--set image.digest=${{ inputs.digest }} --wait --timeout 5m --atomic
kubectl rollout status deploy/<workload> -n <namespace>-${{ inputs.environment }} --timeout=5m1.6 Wire the stages
ci.yml on merge to main → on success call cd.yml with environment: staging (automatic) → on staging success call cd.yml with environment: production (the Environment's required reviewers gate it).
1.7 Deployment strategy (production)
Pick by: can two versions run at once? and is there a metric to gate on?
- Rolling (default, stateless) —
RollingUpdate,maxSurge: 25%,maxUnavailable: 0. Needs backward-compatible changes. - Recreate —
strategy: Recreate. Only when two versions cannot coexist; accepts downtime. - Blue-green — a second Deployment behind the same
Service; flip the selector once healthy; keep the old for instant rollback. ~2× resources briefly. - Canary — a small second Deployment + a weighted
Ingresssplit; grow the weight while watching error rate / latency, abort on regression. Strongest gate; needs reliable metrics and, in practice, a progressive-delivery controller for the analysis.
1.8 External exposure
- DNS → the Ingress controller's load balancer
Ingressindeploy/with the environment's class and annotations- TLS from an automated in-cluster certificate issuer, not a hand-placed key
- Expose only the intended host/path; everything else
ClusterIP+NetworkPolicy
1.9 Cutover (only when migrating an existing deployment)
Deploy the new pipeline's build to a new namespace alongside the old stack → validate → shift traffic (DNS / Ingress weight) gradually, keeping the old stack until stable → decommission the old stack and its manual access.
1.10 Post-deploy verification
- Manifest renders clean; running pods report the expected digest
- Each workload answers on its port; readiness/liveness/startup healthy;
rollout statusclean - Auth; DB and external-service access; data-writing paths; CronJobs
- Secrets resolved at runtime (nothing started empty); no plaintext secret on a pod filesystem
- Signature enforcement works — an unsigned image is rejected
- Logs and metrics reach the sinks; the deployment marker landed
- PDB present; the rollout survives a node drain; previous known-good digest recorded
Reviewing an existing set: diff against another environment's config and walk 1.3–1.6 + the pitfalls; the checks not visible in files are the protected branch, the gated Environment, per-environment secrets and OIDC trust, and the cluster admission policy.
Part 2 — Adding production
Production is another call of cd.yml, not a copy.
Reused: the workflows, the GHCR package and OIDC trust, the image (same digest), the smoke-test suite.
Add:
- A production namespace + a separate scoped OIDC role, never staging's
- Production secrets — production values differ from staging's; never reuse one
- The
productionGitHub Environment: required reviewers, deployment branch/tag rule, freeze calendar deploy/envs/production/values.yaml: replica count and sizing for real load (≠ staging), production hostnames, HPA bounds, PDB- The production deployment strategy (1.7) and its analysis metrics
- Alert routing and on-call
Confirm real production values (do not copy from staging): DB server/instance/port/name; integration hosts; domain and public URL; certificate source; replicas, sizing, HPA bounds; canary metrics. Each confirmed with whoever operates production.
Validate: run the review; verify the Environment approval actually blocks; verify signature enforcement and Pod Security in the production cluster; rehearse a rollback in staging first.
Part 3 — Secrets
Order of preference: (1) a Secret synced from your cluster/cloud secret store by an operator or CSI driver — the value never touches Actions, rotation is automatic; (2) the workflow writes a Secret at deploy time (GitOps: commit a sealed/encrypted reference only); (3) the workflow injects the value into the deploy step's environment. Never (4) plaintext in a repo/manifest/image.
- Runtime injection (preferred): a secrets-store CSI driver or a secret-sync operator pulls from the store and materializes a
Secret; the manifest references it bysecretKeyRef/envFrom. Nothing sensitive in CI. - Workflow-mediated (fallback): authenticate to the store via OIDC in the deploy job, then
kubectl create secret generic <name> --from-literal=<SECRET>="$VALUE" --dry-run=client -o yaml | kubectl apply -f -, or inject at the step (env:with${{ secrets.<NAME> }}) only for values the render needs as env vars. - Rules: name identical to the manifest reference; least-privilege read; never
echoit or put it in a step name; never a Dockerbuild-arg(it lands in a layer); rotate on schedule and on suspected exposure; keep${{ secrets.* }}out of untrusted-PR workflows. - TLS / file secrets: prefer an automated in-cluster issuer so no static key exists. Otherwise pull it at deploy time into a
SecretmountedreadOnly,defaultMode: 0400; never commit or bake it in; rotate.
Part 4 — Operation
Triggering: CI on merge to main (release candidate) or a signed tag X.Y.Z (release), fail closed with no tag. CD staging automatic on green CI. CD production behind the Environment's required reviewers + deployment rule + freeze check.
Rollback — through the pipeline, the same mechanism as a deploy, so the audit trail holds and the next deploy does not re-ship the bad version:
- Helm —
helm rollback <release> <revision>as an immediate stop-gap, then re-runcd.ymlpinning the previous digest. - Kustomize / raw —
kubectl rollout undo deploy/<workload>stop-gap, then re-runcd.ymlwith the previous digest. --atomic— a failedhelm upgradealready rolled itself back; the job just fails loudly.- Blue-green / canary — flip the selector back / drop the canary weight to zero; the stable set never left.
- Compose —
DIGEST=<previous> docker compose … up -d --wait.
The previous known-good digest comes from the last successful production deploy (every published digest is in GHCR). Verify convergence and health after a rollback as for a deploy, and open a fix-forward — a rollback is a pause, not a resolution.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

