Artifact Management
Build once, promote many. This chapter covers artifact types, registry hygiene, digest-based promotion, SBOM generation, SLSA attestations, and Cosign signing—with pipeline YAML for GitHub Actions and GitLab CI.
Artifact types in the supply chain
An artifact is any immutable output of a build—container images, JARs, Helm charts, Terraform modules, npm packages. CI produces once; staging and production promote the same digest, never rebuild.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| Container image | OCI | Primary deployable for K8s | CRITICAL gate typical |
| Helm chart | Packaged K8s manifests | App + config bundle | HIGH gate typical |
| Binary / JAR | Language-specific | VM or serverless deploy | HIGH gate typical |
| SBOM | SPDX / CycloneDX JSON | Vulnerability + license input | MEDIUM gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: Build artifacts
on: [push]
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v5
id: build
with:
push: true
tags: ghcr.io/org/app:${{ github.sha }}
outputs: type=image,name=ghcr.io/org/app,push=true
build-image:
stage: build
image: docker:24
services: [docker:24-dind]
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
reports:
dotenv: build.env
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store Artifact types in the supply chain metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.
flowchart LR SRC["Source commit"] --> CI["CI pipeline"] CI --> IMG["OCI image digest"] CI --> JAR["JAR / wheel"] CI --> CHART["Helm chart .tgz"] IMG --> REG["Registry"] REG --> GITOPS["GitOps digest pin"]
Artifact repositories & registries
Registries (ECR, GCR, Harbor, GitHub GHCR, GitLab Registry) store OCI artifacts with RBAC, vulnerability scanning, retention, and replication. Treat registries as production systems—with backup, HA, and audit logs.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| Harbor | Self-hosted, signing | On-prem / air-gap | HIGH gate typical |
| Amazon ECR | AWS-native | EKS workloads | HIGH gate typical |
| GHCR | GitHub packages | GHA pipelines | MEDIUM gate typical |
| Artifactory | Universal | Polyglot enterprises | MEDIUM gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: Push to registry
on: [push]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
publish:
stage: publish
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store Artifact repositories & registries metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.
Registry hardening
- Enable vulnerability scanning on push (Harbor, ECR, GHCR native).
- Immutable tags for release channels—:latest is for local dev only.
- Geo-replication for DR; test failover pull quarterly.
- Audit log shipping to SIEM—who pushed digest sha256:abc?
SolarWinds-style attacks target build and registry paths—signing and provenance verify publisher, not just image contents.
Artifact promotion patterns
Promotion copies or retags an existing digest through environments—dev → staging → prod—without rebuilding. Rebuilds introduce non-determinism and invalidate scan results.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| Digest pin | Deploy by sha256 | GitOps | CRITICAL gate typical |
| Retag promotion | staging-ok → prod | Simple pipelines | HIGH gate typical |
| Cross-registry copy | crane skopeo | Multi-cloud | HIGH gate typical |
| GitOps bump | PR updates image digest | ArgoCD/Flux | CRITICAL gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: Promote image
on:
workflow_dispatch:
inputs:
digest:
required: true
jobs:
promote:
runs-on: ubuntu-latest
steps:
- run: |
crane copy ghcr.io/org/app@${{ inputs.digest }} ghcr.io/org/app:prod-approved
env:
CRANE_USERNAME: ${{ github.actor }}
CRANE_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
promote-prod:
stage: deploy
when: manual
script:
- crane copy $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:prod
rules:
- if: $CI_COMMIT_BRANCH == "main"
flowchart LR BUILD["CI build single digest"] --> STG["Deploy staging by digest"] STG --> TEST["Integration tests"] TEST --> PROMO["Promote digest crane copy / retag"] PROMO --> PROD["Prod GitOps PR updates digest only"]
Rebuilding 'the same' Dockerfile on prod deploy produces a different digest—SBOM and scan results no longer apply.
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store Artifact promotion patterns metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.
SBOM — software bill of materials
SBOMs list every component in an artifact—name, version, supplier, hashes. Fed by build tools (Syft, Trivy, CycloneDX Maven plugin) and consumed by scanners, license tools, and incident response.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| Syft | Multi-format SBOM | Post-build | MEDIUM gate typical |
| CycloneDX | Standard format | Language plugins | MEDIUM gate typical |
| Trivy | SBOM + scan | Container CI | HIGH gate typical |
| Dependency-Track | SBOM aggregator | Org dashboard | MEDIUM gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: SBOM
on: [push]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anchore/sbom-action@v0
with:
artifact-name: sbom.spdx.json
format: spdx-json
- uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.spdx.json
sbom:
stage: build
image:
name: anchore/syft:latest
entrypoint: [""]
script:
- syft $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -o spdx-json=sbom.spdx.json
artifacts:
paths: [sbom.spdx.json]
NTIA minimum elements require supplier name, component name, version, and unique ID—Syft and CycloneDX plugins emit these by default when built from lockfiles.
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store SBOM metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.
SLSA provenance & supply chain levels
SLSA (Supply-chain Levels for Software Artifacts) defines increasing assurance—provenance, signed attestations, hermetic builds, two-person review. Level 2 is a realistic enterprise target with OIDC + Sigstore.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| SLSA L1 | Provenance exists | Audit trail | LOW gate typical |
| SLSA L2 | Hosted build + signed | Enterprise target | MEDIUM gate typical |
| SLSA L3 | Hardened platform | Regulated industries | HIGH gate typical |
| in-toto | Attestation format | Policy verify | MEDIUM gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: SLSA provenance
on: [push]
jobs:
provenance:
permissions:
id-token: write
contents: read
attestations: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/attest-build-provenance@v1
with:
subject-name: ghcr.io/org/app
subject-digest: sha256:abc123...
push-to-registry: true
provenance:
stage: attest
image: slsa-framework/slsa-verifier:latest
script:
- echo "Generate provenance with GitLab runner OIDC + Sigstore"
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
flowchart TB L1["SLSA L1 Provenance"] --> L2["L2 Signed hosted build"] L2 --> L3["L3 Hardened hermetic"] L3 --> L4["L4 Two-person review"]
Most enterprises target SLSA L2: GitHub/GitLab hosted runners + OIDC + signed provenance—not L4 hermetic reproducibility.
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store SLSA provenance & supply chain levels metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.
Image signing with Cosign & policy admission
Signing binds publisher identity to a digest. Cosign + Sigstore keyless signing via OIDC produces short-lived certificates. Cluster admission (Kyverno, Gatekeeper, OCP) verifies signatures before pods start.
Key concepts
| Tool | Category | When to run | Default gate |
|---|---|---|---|
| Cosign | OCI signing | CI post-push | CRITICAL gate typical |
| Sigstore Fulcio | OIDC certs | Keyless | HIGH gate typical |
| Kyverno verifyImages | K8s admission | Deploy gate | CRITICAL gate typical |
| Notation | Microsoft alternative | Azure ACR | MEDIUM gate typical |
| Severity | Examples | Typical gate policy |
|---|---|---|
| CRITICAL | RCE, auth bypass, exposed secrets in prod | Block merge & deploy immediately |
| HIGH | SQLi, SSRF, privilege escalation paths | Block within SLA (24–72h) |
| MEDIUM | XSS, misconfigurations with limited blast radius | Track in backlog; fix before next release |
| LOW | Info leaks, best-practice deviations | Accept risk or fix opportunistically |
name: Sign image
on: [push]
jobs:
sign:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
packages: write
steps:
- uses: sigstore/cosign-installer@v3
- run: cosign sign --yes ghcr.io/org/app@${{ github.sha }}
sign-image:
stage: sign
image: gcr.io/projectsigstore/cosign:v2.2.0
script:
- cosign sign --yes $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["ghcr.io/org/*"]
attestors:
- entries:
- keyless:
subject: "https://github.com/org/repo/.github/workflows/sign.yml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
Verify signatures at admission—registry compromise or tag mutation cannot bypass Kyverno if deploy references digest.
Operational checklist
- Pin deploy manifests to image@sha256:... not floating tags.
- Generate SBOM on every main-branch build; upload to Dependency-Track or GUAC.
- Sign images before promotion; verify in cluster before prod traffic.
- Retention policy: keep prod digests 2+ years for forensic rebuild.
- Replicate critical images to DR registry—supply chain continuity during outage.
Store Image signing with Cosign & policy admission metadata in OCI artifact attachments—SBOM and signatures travel with the image reference.