GitHub Actions: CI/CD Workflows From First Run to Secure Deployments · Pipelines, Deployments and Security
Containers and Releases: Publish Images and Tagged Versions
Build and push a Docker image to GitHub Container Registry, and create a GitHub release when a version tag is pushed.
What you'll build
A workflow that builds a Docker image on every push to main, tags images from the Git tag on releases, pushes to ghcr.io, and creates a GitHub release with generated notes.
The idea
docker/login-actionsigns in to a registry. Forghcr.iousegithub.actorandsecrets.GITHUB_TOKEN, with thepackages: writepermission.docker/metadata-actionproduces image tags and labels from the branch, tag or SHA, so you never hand-write them.docker/build-push-actionbuilds with BuildKit and pushes;cache-from/cache-to: type=ghareuses layers between runs.- Triggering on
push: tags: ["v*"]turns Git tags into releases. The preinstalled GitHub CLI (gh release create) authenticates with the token and needscontents: write.
The code
name: Release
on:
push:
branches: [main]
tags: ["v*"]
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
release:
needs: image
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: gh release create "$GITHUB_REF_NAME" --generate-notes
Output
actionlint: no problems found workflow: Release triggers: push (branches: main; tags: v*) jobs, in the order they run: image: runs on ubuntu-latest | 5 step(s) release: runs on ubuntu-latest | 1 step(s) | after image | only if startsWith(github.ref, 'refs/tags/v')
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Missing
packages: write. Pushing then fails with a permission denied error. - Hand-written tags. Use metadata-action so branch builds, tag builds and SHA builds get consistent tags.
- Releasing from any push. The
if: startsWith(github.ref, ...)condition keeps the release job to version tags. - Uppercase image names. Registry names must be lowercase;
github.repositorycan contain capitals for some owners, so lowercase it if needed.
Key points
- Docker's official actions log in, generate tags, build and push.
GITHUB_TOKENwithpackages: writepushes to ghcr.io.- A tag trigger plus
gh release createautomates releases. Further reading: GitHub Docs.
Practice questions
Why use docker/metadata-action instead of writing the image tags yourself?
Answer
It derives consistent tags and labels from the Git ref (branch, version tag, SHA), so builds are named correctly without custom shell logic.
How does the workflow decide to create a GitHub release only for version tags?
Answer
The release job has if: startsWith(github.ref, 'refs/tags/v'), so it is skipped for branch pushes and runs only when a tag starting with v was pushed.
Open in the app to mark lessons complete, take notes and track your progress.