Hello! Welcome back to our module on "CI/CD & System Design Interview Practice."
In our last lesson, we built a foundational Continuous Integration (CI) pipeline. That pipeline automatically takes your Spring Boot application's source code, compiles it, runs all the tests, and packages it into a distributable JAR file. This ensures that every code change is validated and a ready-to-run artifact is produced.
Today, we'll take the critical next step in preparing your application for a modern cloud-native environment. We will extend our pipeline to automate the process of building a Docker image and pushing it to a container registry. This directly addresses the learning outcome: Automate the building and pushing of a Docker image to a container registry within a CI/CD pipeline.
This skill is a cornerstone of DevOps and microservices deployment, making it a frequent topic in senior engineering interviews. Mastering this bridges the gap between local development and scalable, production-grade deployment.
1. From JAR to a Portable Docker Image
Why don't we just deploy the JAR file we created? While possible, containerizing the application offers significant advantages that are crucial for microservices and scalable systems:
- Consistency: A Docker image bundles your application (the JAR) and its entire runtime environment (like the specific Java version, OS libraries, and configurations). This eliminates the classic "it works on my machine" problem.
- Portability: The containerized application can run identically on a developer's laptop, a testing server, or a production Kubernetes cluster in any cloud (like AWS, which we'll focus on).
- Isolation: Containers run in isolated environments, preventing conflicts between different applications or their dependencies on the same host machine.
To achieve this, our CI pipeline needs two new components:
- A
Dockerfile: A script that defines how to build the image (we created an optimized multi-stage one in Module 11). - A Container Registry: A storage service for your Docker images. Think of it as "Maven Central for containers." Popular examples include Docker Hub (public/private) and Amazon Elastic Container Registry (ECR), an enterprise-grade service within AWS.
2. Automating the Docker Build and Push
Our goal is to add new steps to the GitHub Actions workflow we built previously. After the package job successfully creates the JAR file, a new job will take over to:
- Log in to a container registry.
- Build the Docker image using our
Dockerfile. - Tag the image with a unique and traceable identifier.
- Push the image to the registry, making it available for deployment.
Let's begin by implementing this with Docker Hub, as it's a straightforward starting point.
Demystifying Docker for Spring Boot: Automation with GitHub Actions
The article 'Demystifying Docker for Spring Boot: Automation with GitHub Actions' provides a complete guide for this process. We'll focus on the GitHub Actions workflow definition part.
Please read the section '4.3. Writing GitHub Actions Workflow'. Pay close attention to the full YAML file provided. Specifically, note the last three steps in the build job: 'Login to Docker Hub', 'Build Docker image', and 'Push Docker image'.
Dissecting the Workflow for Docker Hub
Based on the article, let's look at what a combined build-and-push job would look like. This job extends our previous CI steps.
jobs:
build-and-push-image:
runs-on: ubuntu-latest
steps:
# --- Previous CI steps ---
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build and Package with Maven
run: mvn -B package --file pom.xml
# --- New Docker steps ---
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/my-spring-app:latest
Key Additions:
- Secrets: Notice the use of
${{ secrets.DOCKER_USERNAME }}and${{ secrets.DOCKER_PASSWORD }}. You never hardcode credentials in your workflow files. Instead, you store them as encrypted "Secrets" in your GitHub repository's settings (Settings>Secrets and variables>Actions). For Docker Hub, you'd typically create a Personal Access Token and store it as theDOCKER_PASSWORD. docker/login-action@v2: A pre-built Action that securely handles the login to a Docker registry.docker/build-push-action@v4: This powerful Action combines several steps. It builds the image from yourDockerfile(context: .), tags it, and pushes it (push: true) to the registry.
3. The Enterprise Standard: Pushing to Amazon ECR
While Docker Hub is great, in a professional setting at a FAANG or Fintech company, you'll almost certainly use a private, managed registry like Amazon ECR. The principles are the same, but the authentication is different and more secure, relying on AWS IAM (Identity and Access Management) instead of a simple username/password.
This workflow is a common interview topic, testing your knowledge of cloud security and integration.

Let's walk through the steps to set this up.
Create and Push Docker image to Amazon ECR with GitHub ...
The article 'Create and Push Docker image to Amazon ECR with GitHub ...' provides a focused tutorial on this exact process. It uses official AWS actions to manage authentication and the image push.
Please read through the article from 'Step 1' to 'Step 5', focusing on the concepts: Step 1: How to create an ECR repository in the AWS console. Step 2: The need for an IAM user with AmazonEC2ContainerRegistryFullAccess permissions to get an Access Key ID and Secret Access Key. Step 4: Storing these AWS credentials as GitHub Secrets (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY). Step 5 & 6: The structure of the YAML workflow. Pay close attention to the aws-actions/configure-aws-credentials and aws-actions/amazon-ecr-login steps.
The GitHub Actions Workflow for AWS ECR
The workflow for ECR uses specialized AWS actions to handle authentication. It's more secure because it uses temporary credentials.
Here is a refined version of the workflow from the article:
name: Build and Push to AWS ECR
on:
push:
branches: [ main ]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
# 1. Configure AWS Credentials
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1 # Change to your region
# 2. Login to Amazon ECR
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# 3. Build, tag, and push image to Amazon ECR
- name: Build, tag, and push image
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-spring-app # Your ECR repository name
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
Key Differences and Interview Talking Points:
- Authentication: Instead of a username/password, we use
aws-actions/configure-aws-credentials. This action securely configures the runner to make authenticated calls to AWS services. It uses the IAM user credentials you stored in GitHub secrets. - ECR Login Action: The
aws-actions/amazon-ecr-login@v1action uses the configured AWS credentials to fetch a temporary login token for ECR and configures Docker to use it. This is more secure than a long-lived password or token. - Image Naming: The ECR image name has a specific format:
<registry-url>/<repository-name>:<tag>. Thelogin-ecrstep conveniently provides the registry URL as an output (steps.login-ecr.outputs.registry).
4. Interview Focus: Image Tagging Strategy
In both examples, the image tag is crucial. A tag like :latest is a poor practice in production as it's mutable and makes rollbacks difficult. An interviewer might ask, "What's your strategy for tagging container images in a CI/CD pipeline?"
A robust answer involves using immutable tags that provide traceability:
- Git Commit SHA: Using
${{ github.sha }}(the full commit hash) or a shortened version is excellent. It ties the image directly back to the exact version of the code that produced it. This is invaluable for debugging.- Example:
my-spring-app:f2b87b3
- Example:
- Semantic Versioning: For formal releases, use SemVer tags (e.g.,
v1.2.0). This is typically done in a separate release workflow triggered by creating a Git tag. - Combination: A great strategy is to include both the version and commit SHA, like
v1.2.0-f2b87b3.
Using an immutable and descriptive tag ensures that your deployments are predictable and reproducible.
Test your understanding! (Interview Question)
A team is using GitHub Actions to push Docker images to Amazon ECR. They have stored their AWS Access Key ID and Secret Access Key directly in the YAML workflow file. The pipeline works, but you've been asked to review it for security and best practices. What are the two most critical issues you see, and how would you fix them?
Show answer
-
Critical Issue 1: Hardcoded Credentials. Storing AWS keys directly in the YAML file is a major security vulnerability. Anyone with read access to the repository can see these credentials and gain access to the AWS account.
- Fix: The credentials must be removed from the file immediately. They should be stored as encrypted secrets in the GitHub repository settings (
Settings>Secrets and variables>Actions). The workflow should then reference them using thesecretscontext, like${{ secrets.AWS_ACCESS_KEY_ID }}.
- Fix: The credentials must be removed from the file immediately. They should be stored as encrypted secrets in the GitHub repository settings (
-
Critical Issue 2: Long-Lived IAM User Keys. While using IAM user keys is better than using root account keys, they are still long-lived credentials. If compromised, they remain valid until manually revoked.
- Fix (Advanced/Best Practice): For FAANG-level roles, a superior answer is to move towards short-lived credentials by using OpenID Connect (OIDC). GitHub Actions can be configured as a trusted identity provider in AWS IAM. This allows the workflow to request temporary credentials directly from AWS without needing to store any long-lived secret keys in GitHub at all. This is the most secure method as the credentials expire automatically after the job is complete. The workflow would use
aws-actions/configure-aws-credentialswith arole-to-assumeinstead of explicit keys.
- Fix (Advanced/Best Practice): For FAANG-level roles, a superior answer is to move towards short-lived credentials by using OpenID Connect (OIDC). GitHub Actions can be configured as a trusted identity provider in AWS IAM. This allows the workflow to request temporary credentials directly from AWS without needing to store any long-lived secret keys in GitHub at all. This is the most secure method as the credentials expire automatically after the job is complete. The workflow would use
Conclusion
Congratulations! You have now learned how to create a complete CI pipeline that not only validates your code but also packages it into a portable, versioned Docker image and stores it in a secure registry. This is the standard foundation for deploying microservices in any modern tech company.
Key Takeaways:
- Containerizing an application in a CI pipeline provides consistency and portability.
- A Container Registry (like Docker Hub or Amazon ECR) acts as a central repository for your Docker images.
- GitHub Secrets are essential for securely managing credentials like access tokens and API keys.
- For enterprise-grade pipelines, use AWS ECR with the official AWS actions for secure, IAM-based authentication.
- Employ immutable image tagging strategies (like using the Git commit SHA) to ensure traceability and enable reliable rollbacks.
Preview of the Next Lesson:
Our Docker image is now built and waiting in the Amazon ECR registry. The final piece of the puzzle is deploying it. In our next lesson, "Implement an automated deployment to a Kubernetes cluster as part of a CD pipeline," we will build the Continuous Deployment (CD) part of our pipeline. We'll create a new workflow job that takes our newly pushed image and triggers a deployment to the Kubernetes cluster we set up in the previous module.