Hello! Welcome back to our journey into Continuous Integration and Deployment.
In our last lesson, you built a foundational CI pipeline using GitHub Actions. That pipeline automatically compiles your Spring Boot application and runs your tests every time you push code, ensuring your main branch is always in a healthy state. This is a huge step towards professional-grade software development.
However, the artifact produced—the .jar file—is currently lost when the pipeline finishes. To make our application distributable and ready for deployment, we need to package it in a standard, portable format. This is where Docker comes in.
Today, we will address the learning outcome: Extend the CI pipeline to automatically build and publish a Docker image to a registry. We'll modify the workflow you created to take the packaged application, build a Docker image using your Dockerfile, and push it to Docker Hub, a public container registry.

1. The Missing Piece: Secure Authentication
To push an image to Docker Hub (or any container registry), our GitHub Actions workflow needs to log in on our behalf. Hardcoding your username and password directly into the workflow file is a major security vulnerability, as this file is part of your code repository.
The correct way to handle this is by using GitHub Secrets. Secrets are encrypted environment variables that you create in your repository's settings. Your workflow can then securely access them without exposing their values in logs or the code itself.
To prepare for this, you will need two things:
- A Docker Hub account.
- A Personal Access Token from Docker Hub.
Using an access token is more secure than using your password because you can create multiple tokens with specific permissions and revoke them at any time without affecting your main account password.
The following video provides an excellent walkthrough of the entire process we'll be undertaking today. We will focus on specific parts of it to guide our work.
GitHub Actions Tutorial - Basic Concepts and CI/CD Pipeline with Docker
Let's watch a segment from 'GitHub Actions Tutorial' by TechWorld with Nana. This part will show you how to securely log in to Docker Hub and push an image from a GitHub Actions workflow.
Watch the video from 25:15 to 29:30. Pay close attention to these key steps: Using an Action: The video uses a pre-built action to handle Docker login, build, and push operations. Using Secrets: Notice how the workflow file references secrets.DOCKERHUB_USERNAME and secrets.DOCKERHUB_PASSWORD. (Note: We will use a token for the password). Creating Secrets: The video demonstrates where to go in your repository's Settings to create these secrets (Settings > Secrets and variables > Actions). For now, just watch to understand the process. We will create the secrets and modify the file together in the next section.
Now, let's get your credentials ready:
- If you don't have one, create a free account on Docker Hub.
- Once logged in, navigate to your Account Settings by clicking on your profile icon.
- Go to the Security tab and click "New Access Token".
- Give your token a descriptive name (e.g.,
github-actions-token), set its permissions to "Read & Write", and click "Generate". - Important: Docker Hub will only show you this token once. Copy it immediately and store it somewhere safe temporarily.
Next, add your credentials to your GitHub repository's secrets:
- In your GitHub repository, go to Settings > Secrets and variables > Actions.
- Click "New repository secret".
- Create a secret named
DOCKERHUB_USERNAMEand paste your Docker Hub username as the value. - Create another secret named
DOCKERHUB_TOKENand paste the access token you just generated as the value.
You are now set up to authenticate securely from your workflow!
2. Extending the CI Workflow
Let's modify the main.yml file from the previous lesson. We'll add new steps to the end of our existing build job. These steps will log in to Docker Hub and then build and push the image.
We will use a set of official, community-supported actions provided by Docker, which make this process straightforward and reliable.
Step-by-Step Guide to Spring Boot CI/CD Using GitHub Actions
The article 'Step-by-Step Guide to Spring Boot CI/CD Using GitHub Actions' provides excellent, production-ready examples of the actions we'll be using. We'll use this as a reference for our code.
Read the subsections 'Authenticate to Docker Hub', 'Set Up Docker Buildx', and 'Build and Push the Image'. Focus on the YAML snippets and the explanation for each action (docker/login-action, docker/setup-buildx-action, and docker/build-push-action). This will give you a clear picture of what we're about to add to our workflow.
Based on these best practices, let's update your .github/workflows/main.yml.
Here is the complete workflow file. The new steps are added at the end of the steps list.
name: Java CI with Maven
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
name: Build, Test, and Publish
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17 and cache Maven packages
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
# --- New steps start here ---
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/your-repo-name:latest
Let's break down the new steps:
-
Login to Docker Hub: This step uses the
docker/login-action. It retrieves the username and token you stored in GitHub Secrets and uses them to authenticate with Docker Hub. The${{ secrets.VARIABLE_NAME }}syntax is how you securely access secrets. -
Set up Docker Buildx: This step sets up Buildx, a Docker CLI plugin that enables advanced build features. While not strictly necessary for a simple build, it's a best practice that enables optimizations like build caching and multi-platform builds in the future.
-
Build and push Docker image: This is the main event. The
docker/build-push-actiondoes two things:- It runs
docker build, using your project'sDockerfile(thecontext: .tells it to look in the root directory). - Because
push: trueis set, it then pushes the built image to the registry. - The
tagsproperty specifies how the image should be named in the registry. You must replaceyour-repo-namewith the desired name for your image repository on Docker Hub (e.g.,my-spring-app).
- It runs
Test your understanding!
Imagine you want to tag each image with both the latest tag and a unique tag based on the Git commit SHA (a unique identifier for each commit). How might you modify the tags property?
Hint: GitHub Actions provides a context variable github.sha that holds the commit SHA.
Show answer
You would provide a multi-line string for the tags property, with each tag on a new line:
with:
context: .
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/your-repo-name:latest
${{ secrets.DOCKERHUB_USERNAME }}/your-repo-name:${{ github.sha }}
This is a common and powerful pattern for versioning your images.
3. Execution and Verification
Now, it's time to see your enhanced pipeline in action.
-
Update your local
.github/workflows/main.ymlwith the complete code from the section above. Remember to replaceyour-repo-namein thetagsproperty. -
Commit the changes and push them to your
mainbranch.git add .github/workflows/main.yml git commit -m "ci: Add steps to build and publish Docker image" git push origin main -
Navigate to the Actions tab in your GitHub repository. You will see your new workflow run being triggered.
-
Click on the workflow run and watch the steps execute. You should see the new "Login to Docker Hub" and "Build and push Docker image" steps. If everything is configured correctly, they will complete with green checkmarks.
-
Once the workflow succeeds, go to your Docker Hub profile. In your repositories list, you should see the new repository (
your-repo-name) containing the image with thelatesttag.
Congratulations! Your CI pipeline is now a CI/CD-ready pipeline. It doesn't just validate your code; it produces a versioned, distributable artifact that is ready to be deployed anywhere.
Conclusion
Today you've made a significant leap forward in automating your development process. By extending your CI pipeline, you've connected your source code repository directly to a container registry, laying the groundwork for automated deployments.
Key Takeaways:
- Always use GitHub Secrets to store sensitive credentials like API tokens; never hardcode them.
- Docker Hub Access Tokens provide a more secure way to grant CI/CD systems access than using your primary password.
- Specialized GitHub Actions (
docker/login-action,docker/build-push-action) simplify complex tasks and make your workflows cleaner and more maintainable. - A complete CI pipeline produces a distributable artifact, which in modern development is often a version-tagged Docker image in a registry.
What's Next?
Our pipeline now automatically builds, tests, and publishes our application as a container image. The final piece of the puzzle is deploying it. In the next lesson, Implement a CD pipeline to automatically deploy the containerized application to a cloud platform, we will add a final job to our workflow. This job will be triggered upon a successful image push and will instruct a cloud service to pull our new image and run it, completing our end-to-end automation from code commit to a live application.