Project Goal: Automate the build process of a Java-based registration app using Maven and Jenkins, with GitHub for source control and an Ubuntu server for hosting Jenkins.
- Jenkins β CI/CD automation server
- Maven β Java build and dependency management tool
- Git & GitHub β Version control and source code repository
- Ubuntu EC2 β Server environment
- Java 21 β Runtime and compilation environment
- Developer pushes code to GitHub.
- Jenkins pulls code from GitHub.
- Maven compiles and builds the application.
- Jenkins stores the artifact in a target directory.
# Set a password for the default Ubuntu user
sudo passwd ubuntu
# Enable password authentication
cd /etc/ssh
sudo vi sshd_config
# Uncomment: PasswordAuthentication yes
# Restart SSH daemon
sudo systemctl restart sshd /
systemctl daemon-reloadsudo apt update && sudo apt upgrade -y
sudo apt install openjdk-21-jdk -y
# Check Java path
readlink -f $(which java)
# Set JAVA_HOME
cd
vi .profile
# Add below lines to the end:
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
export PATH="$PATH:$JAVA_HOME/bin"
# Apply changes
source .profile# Download Maven
sudo wget https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.zip
# Install unzip
sudo apt install unzip -y
# Unzip Maven
sudo unzip apache-maven-3.9.9-bin.zip -d /opt
# Clean up
sudo rm apache-maven-3.9.9-bin.zip
# Set environment variables
vi ~/.profile
# Add:
export MAVEN_HOME="/opt/apache-maven-3.9.9"
export M2_HOME="/opt/apache-maven-3.9.9"
export PATH="$PATH:$MAVEN_HOME/bin:$M2_HOME/bin"
# Apply changes
source ~/.profile
# Check version
mvn --version# Set identity
git config --global user.name "<your-name>"
git config --global user.email "<your-email>"
# Git basics
git add .
git commit -m "message"
git push
git status
# Branching
git branch sprint1
git checkout sprint1
git switch sprint1
git merge sprint1
git push --all origin
git remote add origin <repo-url>
git push --set-upstream origin main
git checkout -b fixit1
git diff
git revert HEAD
git reset --hard <commit-id># Create jenkins.sh file
vi jenkins.sh
# Add the following:
#!/bin/bash
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/" | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update -y
sudo apt-get install jenkins -y
# Make script executable
chmod u+x jenkins.sh
# Run the script
bash jenkins.sh# Ensure Jenkins is running
sudo systemctl status jenkins
# Check port
sudo netstat -tnlp | grep :8080
# Get server IP
ifconfig
# Access: http://<server-ip>:8080
# Retrieve admin password
sudo cat /var/lib/jenkins/secrets/initialAdminPassword- Install suggested plugins
- Configure JDK: Add JAVA_HOME
/usr/lib/jvm/java-21-openjdk-amd64 - Configure Maven: Add MAVEN_HOME
/opt/apache-maven-3.9.9
- Name:
Project_Henry - Source Code Management: Git β
https://github.com/olat95/registration-app-devops.git - Build Trigger: (Optional) GitHub webhook
- Build Step:
Invoke top-level Maven targets- Maven Version:
MAVEN_HOME - Goals:
test install
- Maven Version:
- Click Build Now
- Jenkins pulls source β runs Maven β stores artifact in
target/
- Understood how to automate Java project builds with Maven.
- Gained hands-on with Git commands for local/remote branch management.
- Learned how to configure and automate Jenkins with custom build steps.
- Understood the interaction between Jenkins, GitHub, and Maven.
π§ Week 1 Complete! Stay tuned for the next project in this DevOps journey.
--======--
Project Goal: Deploy a Java web application to a Tomcat staging server using Jenkins post-build automation over SSH. Set up Docker for future containerized deployments.
- Jenkins β Automates CI/CD process
- Apache Tomcat β Hosts Java web app in staging
- SSH β Secure file transfer between VMs
- Docker β Containerization setup
- Java 21, Ubuntu, Maven, Git, GitHub
- Developer pushes code to GitHub.
- Jenkins (on the workstation server) pulls the source code.
- Jenkins uses Maven to build the project and generate a
.warfile. - Jenkins transfers the
.warfile via SSH to the Tomcat webapps directory on the staging server. - Jenkins triggers Tomcat to deploy the application.
We began by creating two separate Ubuntu VMs:
- Workstation VM: For Jenkins, Git, Maven, and Java.
- Staging VM: For Tomcat and Docker (future container hosting).
This separation allows us to simulate a more realistic DevOps setup.
We logged into the staging VM and installed Java 21. Then, to prepare a runtime environment for our .war artifact, we installed Tomcat:
cd /opt
sudo wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.42/bin/apache-tomcat-10.1.42.tar.gz
sudo tar -xvf apache-tomcat-10.1.42.tar.gz
sudo rm apache-tomcat-10.1.42.tar.gz
sudo chown -R ubuntu:ubuntu apache-tomcat-10.1.42/Next, we configured environment variables for Tomcat:
vi ~/.profileAppend:
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export CATALINA_HOME=/opt/apache-tomcat-10.1.42
export CATALINA_BASE=/opt/apache-tomcat-10.1.42
export PATH=${PATH}:${JAVA_HOME}/bin:${CATALINA_HOME}/bin:${CATALINA_BASE}/binApply changes:
source ~/.profileThen navigate into the webapps directory where our .war artifact will live:
cd /opt/apache-tomcat-10.1.42/webappsWe needed Jenkins on the workstation server to securely access the staging server via SSH. SSH is user-to-user, so we needed to set this up manually.
On Workstation VM (as jenkins):
sudo passwd jenkins
su jenkins
cd ~
mkdir -p .ssh && cd .ssh
ssh-keygen -t rsa
cat id_rsa.pubOn Staging VM:
cd ~/.ssh
vi authorized_keys # Paste the public keyTest connection:
ssh ubuntu@<staging-server-ip>In Jenkins:
- Navigate to Manage Jenkins > Configure System > SSH Servers
- Add new SSH server:
- Name: Mo-staging
- Hostname:
<staging-server-ip> - Username:
ubuntu - Remote Directory:
. - Path to private key:
/var/lib/jenkins/.ssh/id_rsa
Test configuration β Save.
In Jenkins:
- Navigate to Manage Jenkins > Configure System > SSH Servers
- Add new SSH server:
- Name: Mo-staging
- Hostname:
<staging-server-ip> - Username:
ubuntu - Remote Directory:
. - Path to private key:
/var/lib/jenkins/.ssh/id_rsa
Test configuration β Save.
Inside Jenkins Job:
- Go to Configure > Post-build Actions > Send build artifacts over SSH
Settings:
- Source files:
webapp/target/webapp.war - Remove prefix:
webapp/target - Remote directory:
. - Exec command:
sudo cp ./webapp.war /opt/apache-tomcat-10.1.42/webappsInside Jenkins Job:
- Go to Configure > Post-build Actions > Send build artifacts over SSH
Settings:
- Source files:
webapp/target/webapp.war - Remove prefix:
webapp/target - Remote directory:
. - **Exec command:*sudo cp ./webapp.war /opt/apache-tomcat-10.1.42/webapps*
sudo cp ./webapp.war /opt/apache-tomcat-10.1.42/webappsTo prepare for future containerized deployments:
sudo apt install docker.io -y
sudo usermod -aG docker ubuntu
getent group | grep docker
sudo init 6After reboot:
docker psTo test our setup:
To test our setup:
- Remove any previously deployed artifacts:
rm /opt/apache-tomcat-10.1.42/webapps/webapp.war- Trigger build in Jenkins.
- Verify automatic deployment via Tomcat:
http://<staging-ip>:8080/webapp/During this step, I encountered a common blocker: the Jenkins build was marked "unstable" due to a failed SSH transfer. On troubleshooting, I realized port 22 was not open. Once I added the correct NSG rule to allow SSH on both VMs, the pipeline completed successfully.
This project was both challenging and fulfilling. It involved many moving parts β and I hit roadblocks. The biggest lesson came from a failed build. In the past, I mightβve given up. But this time, I dug deeper. I discovered that Jenkins-to-staging SSH failed because port 22 wasnβt enabled. Once fixed, everything worked seamlessly.
π Key takeaways:
- Jenkins post-build SSH is powerful when done right
- Port 22 must be open for SSH transfers
- Manual Tomcat deployment is foundational before Docker
- Passwordless SSH setups are critical for automation
- This time, I saw it through β and completed the project successfully
β
Week 2 Complete!
We now deploy .war files automatically to Tomcat from Jenkins. Docker is installed and ready for container-based deployments in Week 3!
--======--
Project Goal: Package the Java web application into a Docker container and automate its deployment via Jenkins post-build actions. This step introduces containerization to make our deployments portable, lightweight, and environment-independent.
- Docker β Containerization platform
- Jenkins β Automates CI/CD process
- Tomcat (in container) β Hosts Java web app
- Ubuntu, Java, Maven, Git, GitHub
- Code is pushed to GitHub.
- Jenkins builds the
.warfile using Maven. - A Docker container is created using a custom Dockerfile.
- Jenkins automates the Docker build and container run.
- The app becomes accessible on specified container ports.
Install Docker on both staging and QA servers:
sudo apt update -y && sudo apt upgrade -y
sudo apt install docker.io -y
sudo systemctl start docker
sudo systemctl enable dockerConfirm Docker version:
docker --versionAdd current user to the Docker group for permission:
sudo usermod -aG docker ${USER} # ${USER} resolves to 'azureuser'
sudo init 6 # Reboot the machineWe tested Docker image pulls from DockerHub:
docker pull tomcat:latest
docker pull nginx:latest
docker pull httpd:latest
docker images # List downloaded imagesRun a container exposing Tomcat:
docker run -d --name artford-con -p 8080:8080 tomcat:latestValidate port availability:
sudo apt install net-tools -y
sudo netstat -tnlp | grep :8080Stop, remove containers, and clean up:
docker stop artford-con
docker rm artford-con
docker ps -aRemove images:
docker rmi <image-id>Run multiple containers on different ports:
docker run -d --name henry-con -p 8085:8080 tomcat:latest
docker run -d --name bliss-con -p 8090:8080 tomcat:latest
docker run -d --name dann-con -p 8095:8080 tomcat:latestAccess the container shell:
docker exec -it henry-con /bin/bashInside the container:
cd /usr/local/tomcat/webapps # Destination for .war file
cp -r /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webappsFrom host machine:
docker cp webapp.war henry-con:/usr/local/tomcat/webappsCheck browser:
http://<server-ip>:8085/webapp/We automated container provisioning by writing a Dockerfile:
FROM tomcat:latest
COPY ./webapp.war /usr/local/tomcat/webapps
RUN cp -r /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webappsBuild and run custom image:
docker build -t bliss-im .
docker run -d --name bliss-con -p 8090:8080 bliss-imInside Jenkins Job:
Dashboard > Project Name > Configure > Post-build Actions > Exec Command
Add:
sudo docker build -t austin-im .
sudo docker run -d --name austin-con -p 8088:8080 austin-imClick Build Now to automate:
- Docker image build from Dockerfile
- Container launch on port 8088
π‘ Common Error: Jenkins may show unstable build due to namespace issues.
Clean up and rebuild:
docker stop austin-con
docker rm austin-con
docker rmi austin-im
sudo docker build -t austin-im .
sudo docker run -d --name austin-con -p 8088:8080 austin-imContainerizing the app took us one step closer to cloud-native DevOps. It was refreshing to see how quickly containers spin up and deploy artifacts without worrying about host environments.
π Key Takeaways:
- Docker simplifies and standardizes deployments
- Jenkins post-build automation saves time
- Dockerfiles eliminate manual container configuration
- Jenkins has namespace limits β for more complex flows, consider using Ansible or custom scripts
β Week 3 Complete! Weβve automated deployments inside Docker containers directly from Jenkins. Next up: pushing custom Docker images to DockerHub and pulling them into K8s clusters!
Project Goal: Deploy containerized applications to multiple production servers using Ansible automation from a staging (QA) server. DockerHub is used as the image registry, with Ansible and Jenkins orchestrating the deployment pipeline.
- Ansible β Automated deployments via SSH
- Docker β Containerization engine
- Jenkins β Continuous deployment trigger
- DockerHub β Public container registry
- Amazon EC2 / Azure VMs β Target production servers
- Developers push code to GitHub.
- Jenkins builds the application and creates a
.warartifact. - Docker image is built and container tested on staging.
- Image is pushed to DockerHub.
- Ansible playbook runs from QA server to production VMs over SSH.
- Production servers pull the image and run containers.
Once satisfied with the container in QA (e.g. austin-con), we commit it into an image and push to DockerHub.
$ docker ps -a # View containers
$ docker start pelumhi-container # Start container to commit
$ docker login -u <dockerhub-username> # Authenticate with DockerHub
$ docker commit <container-id> <dockerhub-username/repo-name:tagname> # Commit container as image
$ docker images # Confirm image creation
$ docker push <dockerhub-username/repo-name:tagname> # Push to DockerHub$ sudo apt update && sudo apt upgrade -y # Update OS
$ sudo apt install ansible -y # Install Ansible
$ sudo apt install python3-pip -y # Python for pip
$ pip install docker or pip install --break-system-packages docker # Install Docker SDK for Ansible
$ ansible --version # Confirm installation# List of Production Servers
[email protected]
[email protected]---
- hosts: all
tasks:
- name: copy dockerfile into Remote machine
copy:
src: dockerfile
dest: .
- name: copy .war file into Remote machine
copy:
src: webapp.war
dest: .
- name: stop the running container
command: docker stop pelumhi-container
ignore_errors: True
- name: remove the running container
command: docker rm pelumhi-container
ignore_errors: True
- name: remove the running image
command: docker rmi pelumhi-image
ignore_errors: True
- name: create customimage from dockerfile
command: docker build -t pelumhi-image .
- name: create and run container
command: docker run -d --name pelumhi-container -p 8081:8080 pelumhi-imageRun the playbook:
$ ansible-playbook -i hosts pelumhi-app.yamlWhen I first ran the Ansible playbook, I encountered the following errors:
docker: command not foundpermission denied: cannot access docker daemon
This happened because I had created new production servers without installing Docker or adding azureuser to the Docker group.
β Lesson learned: The tutorial assumed reusing the staging serverβs AMI (with Docker pre-installed). In my case, skipping this step led to deployment failure. After installing Docker and reconfiguring permissions, the issue was resolved.
ansible-playbook -i hosts pelumhi-app.yamlNavigate to Jenkins Job β Configure β Add as Exec Command.
We set up a Jenkins build trigger so every time a developer pushes code to GitHub, Jenkins pulls and triggers the full deployment.
- Navigate to Jenkins Job β Configure > Build Triggers
- Check "GitHub hook trigger for GITScm polling"
- Ensure GitHub webhook is added to trigger Jenkins build
π‘ Screenshot suggestion: Jenkins build trigger config β
assets/jenkins-webhook.png
This week, I achieved automated multi-server deployment using Ansible.
π Key Takeaways:
- Docker image pushes decouple deployment from infrastructure
- Ansible playbooks scale deployment across any number of VMs
- Jenkins can act as the bridge between build and deployment with just one exec command
- Manual provisioning is error-prone β automation boosts repeatability and confidence
β Week 4 Complete!
- DockerHub connected
- Ansible deployed across production
- Jenkins now fully controls deployment via build triggers














