GitHub Actions: Build and Push a Docker Image to GitHub Container Registry
Introduction
Once your tests pass, the next step in many pipelines is building a container image and publishing it so it can be deployed. GitHub Container Registry, ghcr.io, integrates with Actions and can authenticate with the built-in token, so there is no password to manage. This lesson builds that pipeline.
Prerequisites
- A repository with a Dockerfile
- The previous GitHub Actions lessons
Step-by-Step Guide
Step 1: Grant the right permissions
Pushing to the registry needs write access to packages, and checking out code needs read access to contents. Declare exactly these at the job level. The workflow authenticates with the automatically provided GITHUB_TOKEN, so no extra secret is needed for ghcr.io.
permissions:
contents: read
packages: writeStep 2: Log in to the registry
docker/login-action logs in to a registry. For ghcr.io, use the registry address, the user who triggered the run, and the token. Image names on ghcr.io must be lowercase, which the metadata action takes care of for you.
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}Step 3: Generate tags and labels automatically
docker/metadata-action creates sensible image tags and labels from the branch, tag, or commit that triggered the run, so you do not hand-write them. This example tags with the branch name, the short commit SHA, and the semantic version when you push a version tag.
- id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha
type=semver,pattern={{version}}Step 4: Build and push
Set up Buildx, then build and push with docker/build-push-action. Only push when the run is not a pull request, so pull requests prove the image builds without publishing anything. The complete workflow is shown in the next block.
name: Publish image
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
permissions:
contents: read
packages: write
jobs:
image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha
type=semver,pattern={{version}}
- uses: docker/build-push-action@v7
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}Step 5: Use and secure the published image
After a run, your image appears under the repository's Packages. Check the package's access settings, since you may need to link it to the repository or change its visibility. Pull it anywhere with docker pull ghcr.io/OWNER/REPO:TAG. For stronger supply chain security, pin the third-party actions you use to full commit SHAs and let Dependabot keep them updated.
docker pull ghcr.io/OWNER/REPO:mainFAQs
- Why does the push fail with permission denied?
- Check that the job has packages write permission, that the image name is lowercase, and that the package's settings allow the repository to write to it.
- Can I push to Docker Hub instead?
- Yes. Use the same login action with docker.io, your Docker Hub username, and a personal access token stored as a repository secret, never your account password.
Summary
Grant packages write, log in to ghcr.io with the GITHUB_TOKEN, let the metadata action produce tags and labels, and push only outside pull requests. The image then becomes the artifact your deployment uses.
Comments
Log in or create a free account to join the discussion.