Building an End-to-End CI/CD Pipeline with GitLab, Docker, and AWS EKS


Introduction
In the world of software development, speed and reliability are everything. Companies need to deliver new features quickly, without compromising on stability or security. This is where CI/CD (Continuous Integration and Continuous Delivery) pipelines come in.
As part of my learning journey in DevOps and Cloud Computing, I designed and implemented a complete CI/CD pipeline for a Java-based application. This project integrates tools like GitLab CI/CD, Maven, SonarQube, Docker, and AWS EKS (Kubernetes) to automate the entire software delivery process—from writing code to running it in the cloud.
In this article, I’ll share:
The problem I wanted to solve
The solution architecture
How I built the pipeline step by step
The challenges I faced and how I solved them
My key learnings
Problem Statement
When working on software projects, teams usually face these challenges:
Manual deployments take a lot of time and are error-prone.
Inconsistent environments make it hard to reproduce bugs ("works on my machine" issue).
No quality checks before merging code increases risks of bugs and vulnerabilities.
Scaling issues when the application gets more traffic than expected.
I wanted to build a solution where:
Every code change is automatically tested and verified.
Code quality is checked before deployment.
Applications are packaged in containers for consistency.
Deployments are automated and scalable using cloud infrastructure.
Solution Overview
To solve the above problems, I built an end-to-end CI/CD pipeline with the following tools:
Maven – to build and test the Java application.
SonarQube – to check code quality and security issues.
Docker – to containerize the application.
GitLab Package & Container Registry – to store build artifacts and Docker images.
AWS EKS (Elastic Kubernetes Service) – to deploy and scale the application.
CI/CD Pipeline Design
The pipeline was created in GitLab CI/CD and divided into clear stages:
Build & Test – Run unit tests using Maven.
Deploy Artifact – Package the application and store it in GitLab Package Registry.
Code Quality Check – Use SonarQube to scan the code.
Docker Build – Build the Docker image of the application.
Docker Push – Push the image to GitLab Container Registry.
AWS Configure – Authenticate the GitLab runner with AWS.
Kubernetes Deploy – Deploy the application to AWS EKS using Kubernetes manifests.
.gitlab-ci.yml:
stages:
- maven-test
- maven-deploy
- sonarqube-stage
- docker-build
- docker-push
- docker-run
- aws-configure
- kubernetes-stage
maven_test_job:
stage: maven-test
tags:
- saurav
script:
- mvn clean package
- mvn test
artifacts:
name: capestone-project
paths:
- target/my-webapp.war
reports:
junit:
- target/surefire-reports/TEST-com.example.MyServletTest-junit.xml
- target/surefire-reports/TEST-com.example.AppTest-junit.xml
untracked: false
when: on_success
access: all
expire_in: 30 days
maven_deploy_job:
stage: maven-deploy
tags:
- saurav
script:
- mvn clean package
- ls -l target/my-webapp.war # Corrected to check for the WAR file
- mvn deploy -s settings.xml
artifacts:
paths:
- target/my-webapp.war # Pass the WAR file to next stages
sonarqube-check:
stage: sonarqube-stage
tags:
- saurav
image: maven:3.6.3-jdk-11
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- mvn verify sonar:sonar -Dsonar.projectKey=udemy2631411_capstone-project-cicd_AZWLlQvd4T47fbOoKwXE
allow_failure: true
only:
- main
docker_build_job:
stage: docker-build
tags:
- saurav
script:
- docker build -t registry.gitlab.com/udemy2631411/capstone-project-cicd:$CI_PIPELINE_IID .
after_script:
- docker images
docker_push_job:
stage: docker-push
tags:
- saurav
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD registry.gitlab.com
- docker push registry.gitlab.com/udemy2631411/capstone-project-cicd:$CI_PIPELINE_IID
.docker_run_job:
stage: docker-run
tags:
- saurav
script:
- docker run -d -p 83:8080 registry.gitlab.com/udemy2631411/capstone-project-cicd:$CI_PIPELINE_IID
aws_configuration_job:
stage: aws-configure
tags:
- saurav
before_script:
- aws configure set aws_access_key_id "$AWS_ACCESS_KEY"
- aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
- aws configure set default_region "$AWS_DEFAULT_REGION"
script:
- aws s3 ls
kubernetes_job:
stage: kubernetes-stage
tags:
- saurav
script:
- aws eks update-kubeconfig --region us-east-1 --name capstone
# Update the image tag in the manifest
- 'sed -i "s|image: registry.gitlab.com/devopsagents/java-project:latest|image: registry.gitlab.com/udemy2631411/capstone-project-cicd:$CI_PIPELINE_IID|g" Application.yml'
# Apply the updated manifest
- kubectl apply -f Application.yml
Infrastructure on AWS
For this project, I used AWS services to host and deploy the application:
EC2 instances – to run GitLab runners and supporting services.
Amazon EKS – to manage Kubernetes clusters for deployment.
IAM Roles – to securely give GitLab access to AWS resources.
VPC & Security Groups – to control networking and security.
Challenges and How I Solved Them
Like any real-world project, I faced a few challenges:
SonarQube Authentication
Problem: GitLab couldn’t connect to SonarQube.
Solution: Configured an authentication token in GitLab CI/CD variables and updated the Maven SonarQube plugin.
Docker Push to Registry
Problem: Pipeline failed when pushing Docker images.
Solution: Used GitLab’s built-in variables (
CI_JOB_TOKEN
,CI_REGISTRY_USER
) for secure login.
Deploying to AWS EKS
Problem: The GitLab runner couldn’t authenticate with EKS.
Solution: Installed AWS CLI and
kubectl
inside the runner and configured it with IAM credentials.
Key Learnings
Working on this project gave me hands-on experience with:
Automation – How to completely automate builds, tests, and deployments.
Code Quality – Using SonarQube to enforce clean and secure code.
Containerization – Packaging applications with Docker for consistency.
Scalability – Deploying applications on Kubernetes to handle dynamic traffic.
CI/CD Best Practices – Structuring pipelines into modular jobs for easier debugging and maintenance.
Conclusion
This project showed me how powerful DevOps practices can be when applied properly. By integrating GitLab CI/CD, Docker, SonarQube, and AWS EKS, I built a secure, automated, and scalable pipeline that takes code from commit to deployment in the cloud.
This experience not only improved my technical skills but also gave me confidence to tackle more advanced topics such as monitoring, logging, and advanced deployment strategies like blue-green or canary deployments.
I’m excited to continue learning and applying these practices to real-world systems!
Appendix: Screenshots
##
Subscribe to my newsletter
Read articles from Saurav Karki directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Saurav Karki
Saurav Karki
I’m a DevOps and Cloud enthusiast passionate about Kubernetes, AWS, and automation. Currently working as a "Devops Engineer Trainee", working with CI/CD pipelines, GitOps, and scalable architectures. I share projects, tutorials, and lessons from my DevOps journey to help others learn and grow."