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):

  1. DOCKERHUB_USERNAME: Your Docker Hub username.
  2. 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.

Want structured learning?

Take the full Github-actions course →