In the previous article I built a custom nginx image and pushed it to Docker Hub by hand: build, tag, login, push. That works exactly once. The second time you forget the tag, the third time a teammate pushes from a laptop with a stale base image, and soon nobody can say which commit produced the image running in production. The fix is boring and reliable: let GitHub Actions do the build and push on every commit to master.
What you’ll build#
- A GitHub Actions workflow that builds and pushes a Docker image to Docker Hub on every push to master
- Docker Hub credentials stored as GitHub secrets, kept out of the repository
- Two tags per build:
latestplus the GitHub run number, so every image traces back to a specific build - A look at the run status in the Actions tab and the published tags on Docker Hub
1. Prerequisites#
You need two things before starting:
An application that is already Dockerized. I am reusing the simple setup from the last article, two files:
index.htmland aDockerfile. The example project is here on GitHub.A Docker Hub account with your username and an access token.
2. Adding Secrets in GitHub#
The workflow needs your Docker Hub credentials, and they belong in GitHub secrets, never in the workflow file itself. In your repository, go to Settings > Secrets > Actions and click New repository secret. Create two secrets:
DOCKERHUB_TOKEN— your Docker Hub access token.DOCKERHUB_USERNAME— your Docker Hub username.


GitHub Action Adding New Secret
With the secrets in place, add the workflow file to the repository like below:

3. Setting Up the GitHub Actions Workflow#
Create a new file at .github/workflows/docker-image.yml with the following contents:
name: Nginx Build
on:
push:
branches: master
paths:
- '**'
jobs:
main:
runs-on: ubuntu-22.04
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
context: .
file: Dockerfile
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/my-custom-nginx:latest, ${{ secrets.DOCKERHUB_USERNAME }}/my-custom-nginx:${{github.run_number}}
secrets: |
GIT_AUTH_TOKEN=${{ secrets.DOCKERHUB_TOKEN }}
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}This workflow runs each time code is pushed to the master branch:
Checkout: Pulls the latest code from the repository.
Set up QEMU: Installs QEMU for building images for different architectures.
Set up Docker Buildx: Enables advanced build options in Docker.
Log in to Docker Hub: Uses your stored credentials to log in.
Build and Push Image: Builds the image and tags it with both
latestand the GitHub run number, so each build gets its own version tag.Show Image Digest: Outputs the digest after a successful build.
4. Tagging the Docker Image#
The workflow tags the image in two ways:
latest: This tag will always point to the most recent image.${{github.run_number}}: A unique tag based on the GitHub Actions run number, giving each build its own version.
latest. A latest-only repository cannot be rolled back and cannot tell you which build a running container came from. The run-number tag costs nothing and answers both questions.5. Monitoring the Workflow#
Once you push the workflow file to the master branch, GitHub Actions automatically triggers the build. You can view the status under the repository’s Actions tab. A yellow dot shows a build in progress, and tags are updated upon successful completion.

Github Repository Showing Building Status
The below screenshot shows how tags get updated in any build.

Github Action Build Running Status 2
Below is the screenshot from a successful run.

Github Action Build Running Status
6. Viewing the Results#
After the successful push, Docker Hub shows two tags for the image: latest and 5. The 5 is the build number from GitHub Actions, which means I can pull that exact image back at any time or point a rollback at it.
Here’s a screenshot of the published image tags:

Docker Published Image Tags History
Key takeaways#
- Once an image gets pushed more than once, automate it. The workflow above is about 40 lines and removes the laptop from the release path.
- Credentials live in GitHub secrets and are scoped through a Docker Hub access token, not a password.
- Tag every build with something traceable (
github.run_numberhere) alongsidelatest; rollbacks depend on it.
What’s next#
Take the example repo, swap in your own Dockerfile, and push a commit; you should see both tags appear on Docker Hub within a couple of minutes. The same workflow shape works for other registries too. Point docker/login-action at ACR or ECR credentials and change the tag prefix, and you have the identical pipeline against a private registry.
