A comprehensive guide to publishing container images to container registries for research software projects. This skill covers registry setup and authentication, image tagging strategies, multi-architecture builds with Docker Buildx, and CI/CD automation for publishing images on push, tag, and release events. Whether you are publishing to GitHub Container Registry (GHCR), Docker Hub, Amazon ECR, or Azure ACR, these patterns help you ship container images reliably, reproducibly, and securely.
Resources in This Skill
This skill includes supporting materials for container registry tasks:
References (detailed guides -- consult the table of contents in each file and read specific sections as needed):
references/ghcr-publishing.md - GHCR setup and publishing: PAT vs GITHUB_TOKEN authentication, visibility settings, org-level packages, linking images to repos, cleanup actions, and OCI artifact storage
references/ci-image-publishing.md - CI image publishing: docker/build-push-action usage, matrix builds for multi-arch, caching strategies (gha, registry), conditional publishing, release-triggered builds, and attestation with SBOM
Assets (ready-to-use workflow templates):
assets/ghcr-publish-workflow.yml - GitHub Actions workflow for building and publishing images to GHCR on tag push with multi-architecture support and OCI metadata
assets/docker-hub-publish-workflow.yml - GitHub Actions workflow for building and publishing images to Docker Hub with automated tagging
Publish container images to GHCR, Docker Hub, ECR, or ACR
Set up CI/CD pipelines for automated image publishing
Choose and implement an image tagging strategy (semver, SHA, date-based)
Build and publish multi-architecture images (amd64, arm64)
Configure authentication for container registries in CI environments
Set up image retention policies and cleanup stale images
Link GHCR images to GitHub repositories for discoverability
Evaluate which container registry fits your project
Registry Comparison
Pricing and Limits
GHCR (GitHub Container Registry):
Free for public images, storage and bandwidth included with GitHub plan
Private images count against GitHub storage quotas (Actions and Packages share a pool)
No pull rate limits for public images
Best choice for open-source projects already on GitHub
Docker Hub:
Free tier allows one private repo and unlimited public repos
Anonymous pulls limited to 100 per 6 hours per IP; authenticated pulls limited to 200 per 6 hours
Pro/Team/Business plans remove rate limits and add more private repos
Widest ecosystem support and largest public image library
AWS ECR:
Pay-per-use: $0.10/GB/month storage, $0.09/GB data transfer
ECR Public is free with generous limits (50 GB storage, 500 GB/month bandwidth)
No pull rate limits for private repos within the same AWS account
Best choice for AWS-deployed workloads
Azure ACR:
Basic tier starts at ~$5/month with 10 GB storage
Standard and Premium tiers add geo-replication, retention policies, and content trust
Best choice for Azure-deployed workloads
When to Choose Which
Scenario
Recommended Registry
Open-source project on GitHub
GHCR
Widest community distribution
Docker Hub
AWS-deployed application
ECR
Azure-deployed application
ACR
Multi-cloud or registry-agnostic
GHCR or Docker Hub
Air-gapped / self-hosted
Harbor, GitLab Registry, or ECR
Image Tagging Strategies
Tags identify specific versions of a container image. A good tagging strategy makes it easy to deploy specific versions, roll back, and audit what is running.
Semantic Versioning (semver)
Apply version tags from git tags. Publish multiple granularity levels so consumers can pin to a major, minor, or patch version.
myapp:1.2.3 # exact version
myapp:1.2 # latest patch in 1.2.x
myapp:1 # latest minor in 1.x
Git SHA
Tag images with the short git commit SHA for traceability from image back to source code.
myapp:sha-a1b2c3d
Latest
The latest tag points to the most recent build from the default branch. It is mutable and should not be used for production deployments, but it is convenient for development.
myapp:latest
Date-Based
Useful for nightly or periodic builds where semver does not apply.
myapp:2024.01.15
myapp:nightly-2024-01-15
Recommended Combination
Apply multiple tags to each image for flexibility:
# On a release tag (v1.2.3):
myapp:1.2.3
myapp:1.2
myapp:1
myapp:sha-a1b2c3d
myapp:latest
# On a default branch push (no tag):
myapp:sha-a1b2c3d
myapp:edge
The docker/metadata-action in GitHub Actions automates this tagging strategy. See the asset workflows for examples.
GHCR Setup
GitHub Container Registry stores images alongside your code in the GitHub ecosystem.
Enable GHCR for your account:
GHCR is enabled by default for all GitHub accounts and organizations
Images are published under ghcr.io/<owner>/<image>
Automated builds: Docker Hub's built-in automated builds from GitHub have been deprecated for free accounts. Use GitHub Actions with docker/build-push-action instead.
See assets/docker-hub-publish-workflow.yml for a ready-to-use workflow.
ACR retention: Use az acr config retention update to set days-to-retain for untagged manifests.
Authentication in CI
Secrets Management
Never hardcode credentials. Use CI platform secret stores:
CI Platform
Secret Mechanism
GitHub Actions
secrets.GITHUB_TOKEN (auto), repository secrets
GitLab CI
CI/CD variables (masked, protected)
CircleCI
Environment variables, contexts
Jenkins
Credentials plugin
OIDC / Workload Identity (Recommended)
Avoid long-lived credentials by using OIDC federation:
# AWS with OIDC-uses:aws-actions/configure-aws-credentials@v4with:role-to-assume:arn:aws:iam::123456789:role/github-actionsaws-region:us-east-1# Azure with OIDC-uses:azure/login@v2with:client-id:${{secrets.AZURE_CLIENT_ID}}tenant-id:${{secrets.AZURE_TENANT_ID}}subscription-id:${{secrets.AZURE_SUBSCRIPTION_ID}}
Common Mistakes
Using latest as a deployment tag -- The latest tag is mutable and does not guarantee a specific version. Use semver or SHA tags for deployments.
Hardcoding registry credentials -- Never put tokens or passwords in workflow files. Use CI secret stores or OIDC federation.
Not setting up image cleanup -- Untagged and old images accumulate quickly. Configure retention policies or scheduled cleanup actions.
Skipping multi-arch builds -- If your users run on ARM (Apple Silicon, Graviton), a single-platform image forces emulation or fails outright.
Publishing on every commit -- Publish on tags or releases for versioned software. Use edge or sha-* tags for continuous delivery builds.
Forgetting to link GHCR images to repos -- Without the org.opencontainers.image.source label, GHCR images are orphaned from their source repository.
Using passwords instead of tokens -- Docker Hub and GHCR support access tokens with scoped permissions. Avoid using account passwords.
Not caching builds in CI -- Without caching, every CI build downloads and installs everything from scratch. Use cache-from with registry or gha cache.
Best Practices
Use semver tags plus SHA tags for every published image
Automate publishing with CI/CD (never push manually from local machines)
Use docker/metadata-action to generate consistent tags and OCI labels
Enable multi-architecture builds (amd64 + arm64) for broad compatibility
Authenticate with OIDC / workload identity where supported (avoid long-lived tokens)
Set up image retention policies to clean up untagged and old images
Add org.opencontainers.image.source label to link images to source repositories
Cache Docker layers in CI to speed up builds (gha cache or registry cache)
Sign images with cosign for supply chain security (cross-reference container-security skill)
Scan images before publishing (cross-reference container-security skill)
Use access tokens with minimal scopes instead of account passwords
Test multi-arch images on target platforms before publishing