Deploy Pipeline Basics
This page walks through the concrete stages of a Kubernetes delivery pipeline: build, scan, sign, update the manifest, and let a GitOps controller sync. Each example is a small, runnable piece you can lift into your own CI.
The through-line is that CI produces a signed, scanned artifact and commits a desired state, while an in-cluster controller does the applying. No CI job ever holds cluster API credentials, and every change is a reviewable git commit.
Prerequisites
- Docker Engine 29.x with BuildKit (the default build backend).
- A container registry you can push to (GHCR, ECR, Artifact Registry, or self-hosted).
kubectland Helm 3 or Kustomize for rendering manifests.- Trivy or Grype for scanning, and Sigstore
cosignfor signing. - Argo CD or Flux installed in the cluster for pull-based sync.
Quick install of the client tools on a Debian-based runner:
# Trivy
apt-get install -y trivy
# cosign
curl -sSL -o /usr/local/bin/cosign \
https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x /usr/local/bin/cosignBasic Examples
1. Build an image with BuildKit
Build a reproducible image and tag it by commit SHA, not by a moving tag.
export IMAGE=registry.example.com/api
docker build -t $IMAGE:git-$GIT_SHA .
docker push $IMAGE:git-$GIT_SHA- The SHA tag ties the image back to an exact commit.
- BuildKit caches layers, so unchanged stages are reused across builds.
- Push happens once; every later stage refers to the pushed image.
- Never deploy
:latest; it is a mutable pointer with no rollback story.
2. Capture the immutable digest
Resolve the digest so downstream stages pin an unchangeable identity.
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \
$IMAGE:git-$GIT_SHA)
echo "$DIGEST" # registry.example.com/api@sha256:...RepoDigestsis populated after a successful push.- The
@sha256:reference is content-addressed and cannot drift. - Deploy by digest so the running Pod is always deterministic.
3. Scan for vulnerabilities
Fail the pipeline when high or critical CVEs are present.
trivy image --exit-code 1 --severity HIGH,CRITICAL \
--ignore-unfixed "$DIGEST"--exit-code 1turns findings into a hard gate.--ignore-unfixedavoids blocking on CVEs with no available patch.- Scan the digest, not the tag, so you scan exactly what deploys.
4. Sign the image
Attach a Sigstore signature so runtime admission can verify origin.
cosign sign --yes "$DIGEST"- Signing binds provenance to the digest, not to a tag.
- Use keyless (OIDC) signing in CI to avoid managing private keys.
- The signature is stored in the registry alongside the image.
5. Update the manifest with Kustomize
CI writes the new image into git; it does not run kubectl.
cd deploy/overlays/production
kustomize edit set image api="$DIGEST"
git commit -am "api: deploy $GIT_SHA" && git pushkustomize edit set imagerewrites the image field in place.- The commit is the deploy record and the rollback point.
- Nothing here touches the cluster API server directly.
6. A minimal Deployment manifest
The Deployment the overlay patches. Note the runtime security defaults.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels: { app: api }
template:
metadata:
labels: { app: api }
spec:
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: api
image: registry.example.com/api # patched to a digest by CI
ports:
- containerPort: 8080
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }runAsNonRootandRuntimeDefaultseccomp meet the restricted Pod Security Standard.- Requests enable the scheduler to place the Pod; the memory limit caps it.
- The readiness probe gates traffic until the container is actually ready.
7. Sync with Argo CD
An Application object tells Argo CD which git path to reconcile.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/org/deploy.git
path: overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: api
syncPolicy:
automated: { prune: true, selfHeal: true }- Argo CD watches
overlays/productionand applies changes automatically. selfHeal: truereverts manual drift back to git.prune: truedeletes objects removed from git.
Intermediate Examples
8. Verify signatures at admission
Require a valid signature before any Pod for this image runs, using a Kyverno policy.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: check-signature
match:
any:
- resources: { kinds: [Pod] }
verifyImages:
- imageReferences:
- "registry.example.com/api*"
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/org/api/*"Enforceblocks unsigned images instead of only auditing.- The keyless attestor matches the OIDC identity that signed in CI.
- This closes the gap between "scanned in CI" and "trusted at runtime".
9. Promote staging to production
Promotion is a reviewable git change, not a re-run of the build.
# Copy the digest already validated in staging into the prod overlay
DIGEST=$(yq '.images[0].newName + "@" + .images[0].digest' \
overlays/staging/kustomization.yaml)
cd overlays/production
kustomize edit set image api="$DIGEST"
git commit -am "promote api $GIT_SHA to production"- The exact digest that passed staging is what ships to production.
- No rebuild means no chance of a different artifact reaching prod.
- The promotion PR is the audit trail and the approval gate.
10. Roll back a bad deploy
Reverting the deploy commit reconciles the previous digest.
git revert --no-edit HEAD
git push
# Argo CD detects the change and syncs the prior digest back in- Rollback is ordinary git, so anyone on the team can do it.
- The controller restores the last-known-good state within a sync interval.
- Because images are digest-pinned, the rolled-back Pod is byte-identical to before.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).