Deploy your code on a Docker Container using Jenkins on AWS

Ashutosh ojhaAshutosh ojha
9 min read

Description: We are going to deploy a Java Web app on a Docker Container built on an EC2 Instance using Jenkins.

Agenda:

  1. Setup Jenkins (Jenkins-EC2 instance)

  2. Setup & Configure Maven and Git (Jenkins-EC2 instance)

  3. Integrating GitHub and Maven with Jenkins (Jenkins-EC2 instance)

  4. Setup Docker Host (Docker EC2 instance)

  5. Integrate Docker with Jenkins

  6. Automate the Build and Deploy process using Jenkins

  7. Test the deployment

Step 1: Setup Jenkins Server on AWS EC2 Instance

  1. Setup a Linux EC2 Instance

  2. Install Java

  3. Install Jenkins

  4. Start Jenkins

  5. Access Web UI on port 8080

Step 1.1: Setup a Linux EC2 instance with mentioned configurations

Step 1.2.3.4: For installation of Java, Jenkins use below user data scrip

#!/bin/bash
set -e
# Update the OS
sudo yum update -y
# Add Jenkins repository and import the key
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
# Upgrade all packages
sudo yum upgrade -y
# Install Java 17 (required for Jenkins 2.404+)
sudo yum install -y java-17-amazon-corretto
# Install Jenkins
sudo yum install -y jenkins
# Enable and start Jenkins service
sudo systemctl enable jenkins
sudo systemctl start jenkins
# Install additional tools: Git, Maven
sudo yum install -y git maven
# Install Amazon SSM Agent (recommended yum/rpm method for Amazon Linux)
sudo yum install -y https://s3.amazonaws.com/amazon-ssm-${AWS_REGION:-us-east-1}/latest/linux_amd64/amazon-ssm-agent.rpm
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
# Set environment variables for Java and Maven for all users
JAVA_HOME_PATH="/usr/lib/jvm/java-17-amazon-corretto.x86_64"
M2_HOME_PATH="/usr/share/maven"
M2_BIN_PATH="/usr/share/maven/bin"
cat <<EOF | sudo tee /etc/profile.d/dev_env.sh
export JAVA_HOME=$JAVA_HOME_PATH
export M2_HOME=$M2_HOME_PATH
export M2=$M2_BIN_PATH
export PATH=\$PATH:\$JAVA_HOME/bin:\$M2
EOF
source /etc/profile.d/dev_env.sh
# Optionally check Jenkins service status and disk usage
sudo systemctl status jenkins
df -h
echo "Jenkins, Java 17, Git, Maven, and SSM Agent installed and configured successfully!"
Note: Run this script to check everything is installed or not:
echo "==== Jenkins ===="
sudo systemctl status jenkins | grep Active
echo "==== Java ===="
java -version
echo "==== Git ===="
git --version
echo "==== Maven ===="
mvn -version
echo "==== SSM Agent ===="
sudo systemctl status amazon-ssm-agent | grep Active
echo "==== Environment Variables ===="
echo "JAVA_HOME: $JAVA_HOME"
echo "M2_HOME: $M2_HOME"
echo "M2: $M2"

Step: 1.5 Access Jenkins UI on http://publicip:8080

Step 2: Integrate GitHub with Jenkins

  1. Install Git on Jenkins Instance

  2. Install Github Plugin on Jenkins GUI

  3. Configure Git on Jenkins GUI

    Step 2.1: Git already installed using user-data scrip

    Step 2.2: Install Github plugin on Jenkins GUs

    Step 3: Integrate Maven with Jenkins

    1. Setup Maven on Jenkins Server (Done through User data script)

    2. Setup Environment Variables JAVA_HOME,M2,M2_HOME ((Done through User data script)

    3. Install Maven Plugin (GUI)

    4. Configure Maven and Java (GUI)

Step 3.3: Now we need to update the paths where Java and Maven have been installed in the Jenkins UI. We will first install the Maven Integration Plugin as shown below

Step 3.4: After clicking on Install without restart, go again to manage Jenkins and select Global Tool configuration to set the paths for Java and Maven

Step 4: Setup Docker Host

  1. Setup a Linux EC2 Instance

  2. Install Docker

  3. Start Docker Services

  4. Run Basic Docker Commands

Step 4.1: Let's first launch an EC2 Instance. We will skip the steps here as we have already shown earlier how to create an EC2 Instance.

Use user data script to run Docker:

    #!/bin/bash
    # This script installs Docker, starts Docker service, and runs basic Docker commands on Amazon Linux 2
    # Update the system
    echo "Updating system packages..."
    sudo yum update -y
    # Install SSM Agent (for SSM login capability)
    # Install Amazon SSM Agent (recommended yum/rpm method for Amazon Linux)
    sudo yum install -y https://s3.amazonaws.com/amazon-ssm-${AWS_REGION:-us-east-1}/latest/linux_amd64/amazon-ssm-agent.rpm
    sudo systemctl enable amazon-ssm-agent
    sudo systemctl start amazon-ssm-agent
    # Install Docker
    echo "Installing Docker..."
    sudo amazon-linux-extras install docker -y
    # Start Docker service
    echo "Starting Docker service..."
    sudo systemctl enable docker
    sudo systemctl start docker
    # Add ec2-user to docker group to run docker without sudo
    sudo usermod -a -G docker ec2-user
    # Run basic Docker commands
    echo "Running basic Docker commands..."
    # Display Docker version
    echo "Docker version:"
    docker version
    # Pull hello-world image
    echo "Pulling hello-world image..."
    sudo docker pull hello-world
    # Run hello-world container
    echo "Running hello-world container..."
    sudo docker run hello-world
    echo "Docker installation and basic setup completed!"

Step 4.4: Create Tomcat Docker Container

docker pull tomcat

docker images

    docker run -d --name tomcat-container -p 8081:8080 tomcat
    docker ps

Note: Before accessing our container from the browser we need to allow port 8081 in the Security Group of our EC2 docker-host machine.

Create security group rule, select Custom TCP as type, and in port range put 8081–9000

Now let’s take the public IP of our Docker-host EC2 machine and with port 8081 access it from our browser

From the above screenshot, you can see that although there is a 404 error, it also displays Apache Tomcat at the bottom which means the installation is successful however this is a known issue with the Tomcat docker image that we will fix in the next steps.

The above issue occurs because whenever we try to access the Tomcat server from the browser it will look for the files in /webapps directory which is empty and the actual files are being stored in /webapps.dist.

So in order to fix this issue we will copy all the content from webapps.dist to webapps directory and that will resolve the issue.

Let's access our tomcat container and perform the steps as shown below:

    docker exec -it tomcat-container /bin/bash
    cd webapps.dist/
    cp -R * ../webapps/
    cd ../webapps/
    ls -lrth

After that we should be able to access our tomcat docker container

Note: Somehow if we stop this container and start another container with the same Image on a different port we will face the same issue of 404 error. This happens because every time we launch a new container we are using a new Image as the previous container gets deleted.

This issue can be solved by creating our own Docker Image with the appropriate changes required to run our container. This can be achieved by creating a Dockerfile on which we can mention the steps required to build the docker image and from that Image, we can run the container.

Create a Customized Dockerfile for Tomcat:

vim Dockerfile

    FROM tomcat:9.0-jdk11
    RUN cp -R /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps
    # Expose Tomcat port
    EXPOSE 8080
    # Start Tomcat
    CMD ["catalina.sh", "run"]
    docker build -t tomcat-latest .
    docker run -d -p 8085:8080 --name tomcat-latest tomcat-latest

Now you can access the tomcat page on port 8085

Step 5: Integrate Docker with Jenkins

  1. Create a dockeradmin user

  2. Install the “Publish Over SSH” plugin

  3. Add Dockerhost to Jenkins “configure systems”

Step 5.1: Let's first create a dockeradmin user and create a password for it as well.

    # sudo adduser dockeradmin
    # sudo passwd dockeradmin
    sudo mkdir /home/dockeradmin/.ssh
    sudo chown dockeradmin:dockeradmin /home/dockeradmin/.ssh
    sudo chmod 700 /home/dockeradmin/.ssh

    # Now let’s add this user to the Docker group with the below command:
    # usermod -aG docker dockeradmin
    #Now login to Jenkins server and create a ssh key public and private file
    sh-keygen -t rsa
    #Now copy files to Jenkins user and test connectivity:
    # Copy the private key to jenkins user
    sudo cp /home/ec2-user/.ssh/id_rsa /var/lib/jenkins/.ssh/
    sudo chown jenkins:jenkins /var/lib/jenkins/.ssh/id_rsa
    sudo chmod 600 /var/lib/jenkins/.ssh/id_rsa
    # Also copy the public key
    sudo cp /home/ec2-user/.ssh/id_rsa.pub /var/lib/jenkins/.ssh/
    sudo chown jenkins:jenkins /var/lib/jenkins/.ssh/id_rsa.pub
    sudo chmod 644 /var/lib/jenkins/.ssh/id_rsa.pub
    # Switch to jenkins user and test
    sudo su - jenkins
    ssh dockeradmin@172.31.86.2 "whoami"

Now in order to access the server using the newly created User we need to allow password-based authentication. For that, we need to do some changes in the /etc/ssh/sshd_config file

As you can see above we need to uncomment PasswordAuthentication yes and comment out PasswordAuthentication no.

Once we have updated the config file we need to restart the services with the command:

    service sshd reload
    systemctl reload sshd

Redirecting to /bin/systemctl reload sshd.service

Now we would be able to log in using the dockeradmin user credentials

Step 5.2: Now next step is to integrate Docker with Jenkins, for that we need to install the “Publish Over SSH” plugin:

Go to Manage Jenkins > Manage Plugins > Available plugins and search for publish over ssh plugin

Now we need to configure our dockerhost in Jenkins. For that go to Manage Jenkins > Configure System:

On the next page after scrolling down you would be able to see Publish over SSH section where you need to add a new SSH server with the info as shown below

Also copy the .pem file of dockerhost vm:

Note: It should be noted that it's best practice to use ssh keys however for this demo we are using password-based authentication. In the above screenshot, we have provided details of our Docker host which we created on EC2 Instance. Also, note the use of private IP under Hostname as both our Jenkins Server and Dockerhost are on the same subnet. We can also use Public IP here.

Click on Apply and Save to proceed. With this, our Docker integration with Jenkins is successfully accomplished.

Now have to add global configuration of ssh

Now run the pipeline to check the server connectivity from Jenkins server to dockerhost server.

    pipeline {
        agent any

        stages {
            stage('Test connectivity') {
                steps {
                    sh 'whoami'
                    sh 'ssh -o StrictHostKeyChecking=no dockeradmin@172.31.86.2 "whoami && pwd"'
                }
            }
        }
    }

Step 6: Now run the application using Jenkins pipeline

  1. Create deployment script file

  2. Create Dockerfile

  3. Create Pipeline

Step 6.1: Create deployment script to run tomcat applications:

#cd /home/dockeradmin/tomcatproject

#vim deploy.sh

    #!/bin/bash
    echo "Starting deployment..."
    # Stop and remove existing container
    docker stop my-tomcat-app || true
    docker rm my-tomcat-app || true
    # Remove old image
    docker rmi my-tomcat-app:latest || true
    # Build new image
    docker build -t my-tomcat-app:latest .
    # Run new container
    docker run -d --name my-tomcat-app -p 8081:8080 my-tomcat-app:latest
    echo "Deployment completed!"

echo "Application available at: http://localhost:8081"

Step 6.2: Create Dockerfile to run application

# cd /home/dockeradmin/tomcatproject

# vim Dockerfile

    FROM tomcat:latest
    RUN cp -R /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps
    EXPOSE 8080
    CMD ["catalina.sh", "run"]

Step 6.3: Create pipeline:

    pipeline {
        agent any
        stages {
            stage('Checking file present or not'){
                steps{
                    echo 'Cheking all files'
                    script {
                        sh '''
                        ssh dockeradmin@172.31.86.2 "
                        cd /home/dockeradmin/tomcat-project
                        ls -lrth
                        "
                        '''
                    }
                }
            }
            stage('Deploying tomcat-application'){
                steps{
                    echo 'Deploying Docker host'
                    script {
                        sh '''
                        ssh dockeradmin@172.31.86.2 "
                        cd /home/dockeradmin/tomcat-project
                        chmod +x deploy.sh
                        ./deploy.sh
                        "
                        '''
                    }
                }
            }
            stage('Veryfying deployemnt'){
                steps{
                    echo 'Check docker images and docker container'
                    script{
                        sh ''' 
                        ssh dockeradmin@172.31.86.2 "
                        docker images
                        docker ps -a 
                        "
                        '''
                    }
                }
            }
        }
        post {
            success {
                echo 'Deployment successful! Access your app at: http://172.31.86.2:8081'
            }
            failure {
                echo 'Deployment failed!'
            }
        }
    }

Note: Row check in the browser using http:publicip:8081

Conclusion

In conclusion, this tutorial demonstrated how to set up a seamless CI/CD pipeline using Jenkins and Tomcat for Java applications. By following the steps outlined, you can automate the build and deployment process, allowing developers to focus on writing code and improving application features. Integrating GitHub webhooks ensures that the pipeline is triggered automatically upon changes in the repository, further streamlining the development workflow.

1
Subscribe to my newsletter

Read articles from Ashutosh ojha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ashutosh ojha
Ashutosh ojha

DevOps Engineer with 4+ years of experience in automating, optimizing, and scaling AWS and on-prem infrastructures. I specialize in CI/CD pipeline design, Kubernetes (on-prem & EKS), and infrastructure as code with Ansible and Terraform. Successfully migrated 200+ servers to AWS with zero downtime and implemented robust observability stacks (Prometheus, Grafana). Driven by automation, I focus on building secure, scalable, and cost-efficient solutions, with a strong track record in monitoring, incident response, and reducing deployment times. Always open to connecting about DevOps, Cloud, and Automation!