Search…
CI/CD Pipelines · Part 7

Artifact management

In this series (10 parts)
  1. What CI/CD actually means
  2. Pipeline anatomy and design
  3. GitHub Actions in depth
  4. GitLab CI/CD in depth
  5. Jenkins fundamentals
  6. Testing in CI pipelines
  7. Artifact management
  8. Pipeline security and supply chain
  9. Progressive delivery
  10. Self-hosted runners and pipeline scaling

Prerequisite: Testing in CI pipelines.

A CI pipeline that passes all tests but produces nothing deployable is just an expensive linter. The real goal is to produce a build artifact: a container image, binary, package, or tarball. That artifact is the unit of deployment.


What is a build artifact?

An artifact is the output of a build process. It is immutable: once created, never modified. The same artifact that passes tests in staging runs in production.

Common artifact types:

TypeExampleRegistry
Container imageapp:v1.4.2Docker Hub, GitHub Container Registry, ECR, GCR
npm package@myorg/utils@3.1.0npm, GitHub Packages
Python wheelmylib-2.0.0-py3-none-any.whlPyPI, Artifactory
Java JAR/WARapi-service-1.0.0.jarMaven Central, Nexus
Binarycli-linux-amd64GitHub Releases, S3
Helm chartmyapp-chart-0.5.0.tgzOCI registry, ChartMuseum

Artifact flow

graph LR
  SRC["Source Code"] --> BUILD["Build Stage"]
  BUILD --> TAG["Tag + Version"]
  TAG --> SIGN["Sign Artifact"]
  SIGN --> PUSH["Push to Registry"]
  PUSH --> SCAN["Vulnerability Scan"]
  SCAN --> PROMOTE["Promote to Production"]

  style SRC fill:#64748b,color:#fff
  style BUILD fill:#3b82f6,color:#fff
  style TAG fill:#8b5cf6,color:#fff
  style SIGN fill:#ec4899,color:#fff
  style PUSH fill:#f59e0b,color:#000
  style SCAN fill:#ef4444,color:#fff
  style PROMOTE fill:#22c55e,color:#000

Artifact lifecycle from source to production.

Every artifact has a version, a signature, a scan result, and a promotion record.


Versioning strategies

Semantic Versioning (SemVer)

The format is MAJOR.MINOR.PATCH. Bump major for breaking changes, minor for features, patch for fixes. Works well for libraries and public APIs.

# Tag a release
git tag v2.3.1
git push origin v2.3.1

Git SHA

Use the short commit SHA as the version. This ties the artifact directly to a specific commit. No ambiguity.

    steps:
      - name: Build and push image
        run: |
          IMAGE=ghcr.io/myorg/api:${{ github.sha }}
          docker build -t $IMAGE .
          docker push $IMAGE

Date-based

Format: YYYY.MM.DD-BUILD_NUMBER. Useful for applications with frequent releases where SemVer would create noise.

VERSION="2026.04.20-${GITHUB_RUN_NUMBER}"
docker build -t "ghcr.io/myorg/api:${VERSION}" .

Hybrid approach

Most teams combine strategies. Container images get SHA tags for traceability and SemVer tags for human readability. The same image has two tags pointing to the same digest.

      - name: Tag image
        run: |
          SHA_TAG="ghcr.io/myorg/api:${{ github.sha }}"
          SEMVER_TAG="ghcr.io/myorg/api:v2.3.1"
          docker tag $SHA_TAG $SEMVER_TAG
          docker push $SHA_TAG
          docker push $SEMVER_TAG

Container registries

A container registry stores and serves container images. The major options:

  • GitHub Container Registry (ghcr.io): free for public images, integrated with GitHub Actions.
  • Amazon ECR: tight IAM integration with AWS services.
  • Google Artifact Registry: supports containers, npm, Maven, and Python in one place.
  • Docker Hub: the original. Rate limits on free tier can cause pipeline failures.
# GitHub Actions: login and push to GHCR
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.sha }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

The cache-from and cache-to directives use GitHub Actions cache for Docker layer caching. This can cut build times by 50% or more on subsequent runs.


Package registries

Not everything is a container. Libraries go to package registries.

# Publish an npm package to GitHub Packages
  publish:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://npm.pkg.github.com
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The registry-url in setup-node configures the .npmrc automatically. No manual token file creation.


Artifact signing and provenance

An unsigned artifact is a trust-me artifact. Anyone with registry write access could replace it. Signing proves who built it and that it has not been modified.

Cosign (part of Sigstore)

      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign image
        run: |
          IMAGE="ghcr.io/${{ github.repository }}:${{ github.sha }}"
          cosign sign --yes $IMAGE
        env:
          COSIGN_EXPERIMENTAL: "true"

Keyless signing with Sigstore uses OpenID Connect tokens from the CI provider. No key management required. The signature is stored in a transparency log (Rekor) that anyone can audit.

Verifying signatures

cosign verify \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --certificate-identity-regexp="https://github.com/myorg/.*" \
  ghcr.io/myorg/api:v2.3.1

If verification fails, the image was not built by your pipeline. Do not deploy it.

Build provenance

Provenance goes beyond signing. It records what source code was used, what build commands ran, and what dependencies were included. GitHub Actions can generate SLSA provenance attestations natively.

      - name: Generate provenance
        uses: actions/attest-build-provenance@v2
        with:
          subject-name: ghcr.io/${{ github.repository }}
          subject-digest: ${{ steps.build.outputs.digest }}
          push-to-registry: true

Retention policies

Artifacts accumulate fast. A team pushing 10 builds a day generates 300 images a month. Without cleanup, registry costs grow linearly.

Set retention policies based on environment:

EnvironmentRetention
Development7 days
Staging30 days
Production1 year or indefinite

GitHub Container Registry supports lifecycle policies via the API. ECR has built-in lifecycle rules.

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 10 production images",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["v"],
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "Expire untagged images after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": { "type": "expire" }
    }
  ]
}

Tag your production images with SemVer. Tag development images with SHA or branch name. Let lifecycle rules clean up the rest.


Key principles

  1. Build once, deploy everywhere: the same artifact moves through staging and production. Never rebuild per environment.
  2. Immutable artifacts: once published, an artifact is never overwritten. Use unique tags.
  3. Sign everything: unsigned artifacts are a supply chain risk.
  4. Automate cleanup: retention policies prevent storage costs from growing without bound.
  5. Trace from production to source: every running artifact should link back to its commit, build job, and test results.

What comes next

Artifacts flow through your pipeline, but how do you ensure nothing malicious enters that flow? Pipeline security and supply chain covers dependency scanning, SAST, container scanning, SBOM generation, and the SLSA framework.

Start typing to search across all content
navigate Enter open Esc close