GitHub Actions CI
Why automate image builds in CI?
Section titled “Why automate image builds in CI?”Building and pushing images by hand is error-prone. A CI pipeline ensures that every merge to your main branch produces a tested, tagged, and published image — without manual steps.
GitHub Actions is the most common choice for projects hosted on GitHub. The Docker team publishes four official actions that make this straightforward:
| Action | Purpose |
|---|---|
docker/login-action | Authenticate to any registry |
docker/setup-buildx-action | Enable BuildKit (multi-platform, cache mounts) |
docker/metadata-action | Generate tags and labels from Git metadata |
docker/build-push-action | Build and push in a single step |
Setting up secrets
Section titled “Setting up secrets”Registry credentials must never appear in your repository. Store them as encrypted repository secrets:
- Go to your repository → Settings → Secrets and variables → Actions → New repository secret.
- Add
DOCKERHUB_USERNAMEandDOCKERHUB_TOKEN(use a Docker Hub access token, not your password).
The workflow below references these as ${{ secrets.DOCKERHUB_USERNAME }} and ${{ secrets.DOCKERHUB_TOKEN }} — GitHub Actions substitutes the values at runtime and masks them from logs.
The workflow file
Section titled “The workflow file”Create .github/workflows/docker.yml in your repository:
name: Build and push Docker image
on: push: branches: ["main"] pull_request: branches: ["main"]
jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write # needed if pushing to GHCR
steps: - name: Checkout uses: actions/checkout@v4
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) id: meta uses: docker/metadata-action@v5 with: images: acme/api-server tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix=sha-,format=short
- name: Build and push uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=maxPushing to GHCR instead of Docker Hub
Section titled “Pushing to GHCR instead of Docker Hub”To push to GitHub Container Registry, change the login step and the image name:
- name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}And update the images: value in metadata-action:
images: ghcr.io/${{ github.repository }}GITHUB_TOKEN is automatically provided by GitHub Actions — no manual secret needed.
What metadata-action produces
Section titled “What metadata-action produces”Given a git tag v2.1.0, the metadata action generates:
acme/api-server:2.1.0acme/api-server:2.1acme/api-server:latestacme/api-server:sha-abc1234On a plain branch push (no semver tag), only the sha- tag and branch name are emitted — latest is only set on a tagged release.
BuildKit cache
Section titled “BuildKit cache”The cache-from: type=gha and cache-to: type=gha,mode=max lines use GitHub Actions cache to store BuildKit layers between runs. This can cut build times by 70-90% on unchanged layers.
Key points
Section titled “Key points”push: ${{ github.event_name != 'pull_request' }}— build on every PR for validation, but only push on merges to main.setup-buildx-actionenables BuildKit features (layer cache, multi-platform,--mountinRUN).- Never put credentials directly in the YAML file. Always use
secrets.*.