GitHub Actions can be a massive time-saver for automating your Docker build and push workflows.
Let’s say you have a simple Node.js app and you want to build a Docker image for it and push it to Docker Hub whenever you push changes to your main branch.
Here’s a docker-build-push.yml workflow file you can drop into your .github/workflows/ directory:
name: Docker Build and Push
on:
push:
branches: [ main ]
env:
DOCKER_IMAGE_NAME: your-dockerhub-username/your-repo-name
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ env.DOCKER_IMAGE_NAME }}:latest,${{ env.DOCKER_IMAGE_NAME }}:$(git rev-parse --short HEAD)
When you push to main, this workflow kicks off. The checkout step pulls your code. Then, docker/login-action securely logs you into Docker Hub using credentials you’ll set up as GitHub secrets. Finally, docker/build-push-action does the heavy lifting: it builds your Docker image using the Dockerfile in your repository’s root (context: .), pushes it to Docker Hub (push: true), and tags it with both latest and the short Git commit hash of the push.
The real magic here is how the docker/build-push-action abstracts away a lot of the manual docker build and docker push commands. It handles multi-platform builds if you specify them, can cache layers to speed up subsequent builds, and integrates seamlessly with the login-action for authentication.
To make this work, you need to add two secrets to your GitHub repository’s settings (Settings -> Secrets and variables -> Actions -> New repository secret):
DOCKERHUB_USERNAME: Your Docker Hub username.DOCKERHUB_TOKEN: A Docker Hub access token. You can generate this from your Docker Hub account settings under "Security". Make sure this token has "Write" permissions.
The env block defines DOCKER_IMAGE_NAME. This should be your Docker Hub username followed by your desired repository name (e.g., myusername/myawesomeapp). The tags parameter in the build-push step is crucial. It creates two tags: latest for the most recent build, and a unique tag based on the short Git commit hash ($(git rev-parse --short HEAD)). This allows you to pin specific versions of your image to commits.
The context: . tells the action where to find the Dockerfile and any other build context files. If your Dockerfile is in a subdirectory, say docker/, you’d change this to context: ./docker.
The push: true flag is what tells the action to not just build the image, but also push it to the registry after a successful build. Without it, the image would only be available on the runner.
One subtle but powerful aspect of docker/build-push-action is its ability to leverage build cache. By default, it tries to use a cache to speed up builds. If you want to explicitly disable caching, you can add cache-from: type=local,src=... and cache-to: type=local,mode=max with appropriate settings, but for most common workflows, letting the action manage the cache is the most efficient.
If you’re pushing to a private GitHub Container Registry (GHCR) instead of Docker Hub, the login action and tags would change. You’d use docker/login-action@v2 with registry: ghcr.io, username: ${{ github.actor }}, and password: ${{ secrets.GITHUB_TOKEN }}. The tags would then be formatted like ghcr.io/${{ github.repository_owner }}/your-repo-name:latest.
The next thing you’ll likely want to automate is deploying this new image to a server or a Kubernetes cluster.