Skip to content

Artifact Management

First PublishedByAtif Alam

Artifacts are the outputs of your CI/CD pipeline — Docker images, compiled binaries, npm packages, Helm charts, and more. Artifact management covers how you store, version, scan, promote, and distribute these outputs reliably.

Artifact TypeWhat It IsWhere It’s Stored
Container imageDocker/OCI imageContainer registry (GHCR, ECR, ACR, Docker Hub)
Packagenpm, PyPI, Maven, NuGet packagePackage registry (npmjs.com, PyPI, GitHub Packages)
BinaryCompiled executableObject storage (S3) or artifact repo (Artifactory)
Helm chartKubernetes packageHelm repository or OCI registry
Build outputStatic site, compiled assetsCI/CD artifact storage or object storage
Terraform planSaved plan fileCI/CD artifact storage

Container registries store Docker/OCI images — the most common artifact in modern CI/CD.

RegistryTypeNotes
Docker HubPublic SaaSDefault registry, free public images, rate limits on free tier
GitHub Container Registry (GHCR)GitHub-nativeFree for public repos, integrates with GitHub Actions
AWS ECRAWS-nativePrivate, integrates with ECS/EKS, lifecycle policies
Azure ACRAzure-nativePrivate, integrates with AKS, ACR Tasks for builds
Google Artifact RegistryGCP-nativeSupports Docker, npm, Maven, Python, Go
GitLab Container RegistryGitLab-nativeBuilt-in, $CI_REGISTRY variable
JFrog ArtifactoryUniversal (commercial)Supports all artifact types, enterprise features
HarborOpen sourceSelf-hosted, RBAC, vulnerability scanning, replication

For cloud-specific registry details, see AWS ECR and Azure ACR.

Terminal window
# Docker Hub
docker login
docker build -t myorg/myapp:1.4.0 .
docker push myorg/myapp:1.4.0
# GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
docker build -t ghcr.io/myorg/myapp:1.4.0 .
docker push ghcr.io/myorg/myapp:1.4.0
# AWS ECR
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
docker build -t 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.4.0 .
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.4.0

How you tag images determines how you identify, deploy, and roll back versions.

StrategyExampleProsCons
Git SHAmyapp:a1b2c3dUnique, traceable to exact commitNot human-readable
SemVermyapp:1.4.0Clear versioning, human-readableRequires release process
Branch + SHAmyapp:main-a1b2c3dIdentifies branch and commitVerbose
Build numbermyapp:42Simple, incrementingNot traceable to commit
latestmyapp:latestConvenient for devAmbiguous, breaks reproducibility
Terminal window
# Tag with both SemVer and Git SHA
docker tag myapp:build ghcr.io/myorg/myapp:1.4.0
docker tag myapp:build ghcr.io/myorg/myapp:a1b2c3d
docker tag myapp:build ghcr.io/myorg/myapp:latest
docker push ghcr.io/myorg/myapp:1.4.0
docker push ghcr.io/myorg/myapp:a1b2c3d
docker push ghcr.io/myorg/myapp:latest
  • SemVer for humans and deployment configurations.
  • SHA for debugging and exact traceability.
  • latest for convenience (but never use in production Kubernetes manifests).

Enable tag immutability to prevent overwriting existing tags:

Terminal window
# AWS ECR — enable immutability
aws ecr put-image-tag-mutability \
--repository-name myapp \
--image-tag-mutability IMMUTABLE
# Once myapp:1.4.0 is pushed, it can never be overwritten

This ensures that myapp:1.4.0 always refers to the exact same image — critical for reproducibility and auditing.

Registries accumulate images over time. Lifecycle policies automatically clean up old images:

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

Use scheduled GitHub Actions or CLI scripts to prune old images:

# Prune old GHCR images
- uses: actions/delete-package-versions@v5
with:
package-name: myapp
package-type: container
min-versions-to-keep: 10
delete-only-untagged-versions: true

Scan images for known CVEs before deploying:

# GitHub Actions — Trivy scan
- uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/myorg/myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1 # Fail pipeline on findings
RegistryScanning
AWS ECRBasic scanning (Clair) or Enhanced (Inspector)
Azure ACRDefender for Containers
Docker HubDocker Scout
GHCRDependabot alerts for container images
HarborBuilt-in Trivy scanning

Configure registries to scan images automatically when pushed:

Terminal window
# AWS ECR — enable scan on push
aws ecr put-image-scanning-configuration \
--repository-name myapp \
--image-scanning-configuration scanOnPush=true

Beyond containers, CI/CD pipelines publish packages to language-specific registries:

LanguageRegistryPublishing
JavaScriptnpm (npmjs.com)npm publish
PythonPyPI (pypi.org)twine upload or gh-action-pypi-publish
JavaMaven Central, GitHub Packagesmvn deploy
GoGo Module ProxyGit tag (automatic)
.NETNuGet (nuget.org)dotnet nuget push
Rustcrates.iocargo publish
HelmHelm repository or OCI registryhelm push

GitHub Packages supports multiple package types from one place:

Terminal window
# npm
npm publish --registry=https://npm.pkg.github.com
# Docker / OCI
docker push ghcr.io/myorg/myapp:1.4.0
# Maven
mvn deploy -DaltDeploymentRepository=github::default::https://maven.pkg.github.com/myorg/myapp

Promotion is the practice of moving a tested artifact from one stage to the next — instead of rebuilding:

Build: myapp:a1b2c3d ──► push to registry
Test: pull myapp:a1b2c3d ◄───┘
run tests
tests pass
Staging: deploy myapp:a1b2c3d ◄──┘
smoke tests pass
Production: deploy myapp:a1b2c3d ◄──┘ (same image, never rebuilt)
PracticeRisk
Rebuild for each environmentDifferent builds may produce different binaries (dependency drift, build non-determinism)
Promote the same artifactExact binary tested in staging is deployed to production — guaranteed consistency
PatternHow
Tag promotionAdd a tag: myapp:a1b2c3d → also tag as myapp:staging, then myapp:production
Registry promotionCopy image from dev-registryprod-registry
Manifest updateUpdate the image tag in the deployment manifest (GitOps)
Terminal window
# Tag promotion
docker tag myapp:a1b2c3d myregistry/myapp:staging
docker push myregistry/myapp:staging
# Later, after staging validation:
docker tag myapp:a1b2c3d myregistry/myapp:production
docker push myregistry/myapp:production

Helm charts are another key artifact type for Kubernetes deployments:

Terminal window
# Push chart to OCI registry
helm push myapp-1.4.0.tgz oci://ghcr.io/myorg/charts
# Pull and install
helm install myapp oci://ghcr.io/myorg/charts/myapp --version 1.4.0
Terminal window
# Package and upload to a chart repo (e.g. ChartMuseum, S3, GitHub Pages)
helm package myapp/
helm repo index . --url https://charts.myorg.com
# Upload index.yaml and .tgz to the repo

For organizations managing many artifact types, universal repositories consolidate everything:

ToolSupportsType
JFrog ArtifactoryDocker, npm, Maven, PyPI, Helm, Go, NuGet, genericCommercial
Sonatype NexusDocker, npm, Maven, PyPI, NuGet, HelmOpen source + commercial
GitHub PackagesDocker, npm, Maven, NuGet, GradleGitHub-native
Google Artifact RegistryDocker, npm, Maven, Python, Go, Apt, YumGCP-native
PracticeWhy
Never use latest in productionAmbiguous, not reproducible
Enable tag immutabilityPrevent overwriting released versions
Scan images on pushCatch vulnerabilities before deployment
Use lifecycle policiesAvoid unbounded storage growth
Promote, don’t rebuildSame artifact in staging and production
Sign imagesProve provenance (cosign, Notary)
Use minimal base imagesFewer packages = fewer vulnerabilities
Automate cleanupDelete untagged images and old versions
Multi-architecture imagesBuild for amd64 and arm64 with docker buildx
Store SBOM alongside imagesTrack components for compliance and incident response
  • Container registries (GHCR, ECR, ACR, Docker Hub) are the primary artifact store in modern CI/CD.
  • Tag images with SemVer + Git SHA — human-readable and traceable.
  • Enable tag immutability and scan-on-push for security and reproducibility.
  • Lifecycle policies automatically clean up old images to control storage costs.
  • Promote the same artifact through environments — never rebuild for production.
  • Package registries (npm, PyPI, Maven) publish libraries; Helm repos publish Kubernetes charts.
  • Use universal repositories (Artifactory, Nexus) if you manage many artifact types.