In this hands-on DevOps tutorial, we’ll walk through building a full-fledged Jenkins CI/CD pipeline to build, test, store, and deploy a Java application to Google Cloud Run using industry-standard tools and best practices.
- Java: Language & Framework
- Spring Boot: Creates Web Applications in Java
- Maven: Java Application Build Tool
- Shell: Commands to Build, Test, and Deploy
- Jenkins: CI/CD Pipeline Tool
- Docker: Containerization Tool
- GCP Account & Google Cloud SDK
We want to build a docker image later with our Jenkins Pipeline, so we need a Jenkins Container or VM with Docker in it (i.e., Docker-in-Docker or DinD)
docker network lsdocker network create jenkins-javadocker run -d --name jenkins-dind \
-p 8080:8080 \
-p 50000:50000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/usr/bin/docker \
-u root \
-e DOCKER_GID=$(getent group docker | cut -d: -f3) \
--network jenkins-java \
jenkins/jenkins:ltsdocker psGet Jenkins Container IP Address
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jenkins-dindOn your browser, open
ContainerIP:8080Note: If you see the error "It appears that your reverse proxy set up is broken.", go to Manage Jenkins > System and change the Jenkins URL from localhost:8080 to ContainerIP:8080.
docker exec -it jenkins-dind bashInside the container, run the following commands:
docker --versiongroupadd -for -g $DOCKER_GID docker
usermod -aG docker jenkins
exitdocker psdocker restart jenkins-dindThis will also provide you the Jenkins Initial Password. Use the password to login to Jenkins & Install suggested plugins
docker logs -f jenkins-dindCtrl + C to exit logs
In your GitHub Account,
- Create a PAT with scope (repo & admin:repo_hook), and
- Add it as a Credential (Kind: Username with Psswd) on Jenkins UI
- Username: GitHub Username here
- Password: PAT here
- Create Pipeline Job with Definition: "Pipeline script from SCM", and complete the fields SCM, Repository URL, Credentials, Branch Specifier, and Script Path.
- Apply and Save when done.
docker exec -it jenkins-dind bashCheck Java version (Usually installed with Jenkins since Java is one of Jenkins' Dependencies) & JAVA_HOME
java -versionmvn -versionInstall Maven & get MAVEN_HOME as well (usually: /usr/share/maven)
apt update -y
apt install maven -y
mvn -version
exitIn the Jenkins GUI
- Install Maven Integration Plugin
- Set up Maven installation in Manage Jenkins > Tools > Maven installations - Uncheck "Install Automatically"
- Name: maven387
- MAVEN_HOME: /usr/share/maven
- Set up JDK installation in Manage Jenkins > Tools > JDK installations - Uncheck "Install Automatically"
- Name: java17015
- JAVA_HOME: /opt/java/openjdk
- Add these tools in the Jenkinsfile for 'jdk' and 'maven'.
Run the command below to create the SonarQube Container on the same Docker network as Jenkins:
docker run -d --name sonarqube-dind \
-e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true \
-p 9000:9000 \
--network jenkins-java \
sonarqube:latestCheck the logs of the SonarQube Container (Wait till SonarQube is Operational):
docker logs -f sonarqube-dinddocker psGet SonarQube Container IP Address
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sonarqube-dindOn your browser, open
ContainerIP:9000Login to SonarQube GUI with
Username: admin
Password: adminand Change the password.
docker exec -it jenkins-dind bashUpdate & Install Ping in Jenkins Container once you've logged in:
apt-get update -y
apt-get install iputils-ping -y
exitUse Jenkins Container bash to Ping SonarQube Container
docker exec -it jenkins-dind ping sonarqube-dindYou will see bytes of data coming in showing established connection between the 2 containers.
- Click on the User Account, and click on "My Account"
- Go to Security, create a token of type "Global Analysis Token", expiry date, Name: jmsonar, and Generate.
- Copy and Save the token somewhere safe.
- Go to Jenkins Credentials, select Kind "Secret text", Paste sonar token as the secret and provide ID and description.
- Install the "SonarQube Scanner" and "Sonar Quality Gates" Plugins.
- Go to Jenkins > Manage Jenkins > Systems > SonarQube Installation, Name: sonar, SonarQube URL (http://ContainerIP:9000), and Server auth token: select the credential. Apply and Save.
- Go to Jenkins > Manage Jenkins > Tools > Add Name: sonar7, Install Automatically. Save and Apply
Create a Local Project in SonarQube and provide the ff:
- Project display name,
- Project key,
- GitHub branch (main),
- Use the global setting, and
- Create project.
- Click on "Locally", Use existing token and enter name of your sonar token as value "jmsonar" and continue
- Run analysis on your project: Select Other & Linux. Copy the execution script and add to your pipeline script. We will modify it a bit later.**
- SonarQube Installation Name or SonarQubeEnv: sonar (the setup we did @ Manage Jenkins > System), add to pipeline
- SonarQube Tool Name: sonar7 (the setup we did @ Manage Jenkins > Tools), add to pipeline as an environment variable.
- Create a sonar-project.properties file in root of your GitHub Repository and add the SonarQube project key and organization (GitHub Username).
- Since the application is Java-based, you must specify the location of the Java binaries which is usually stored in a "target/classes" directory. Add it to the SonarScanner block as: "-Dsonar.java.binaries=target/classes".
To Generate Jenkins Pipeline Script
- In Jenkins UI, go to configure the Jenkins Pipeline
- Click on Pipeline Syntax
- Sample Step: Select withCredentials, Add Bindings, Select Secret text, Variable: sonarToken, Credentials: select the credentials
- Generate Pipeline Script.
Login to the Jenkins Container
docker exec -it jenkins-dind bashCheck if Trivy is installed
trivy --versionIf not, install Trivy in the Jenkins Container
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.17.2trivy --version
exitAdd Trivy FS Scan stage to the Jenkinsfile
Make sure Docker is installed in the Jenkins Container and there is a Dockerfile in the root of the GitHub Repo
docker --versionIn Jenkins UI,
- Install Docker & Docker Pipeline plugins in Jenkins UI
- Manage Jenkins > Tools > Add Docker Installations: Name: Docker, Install Automatically from docker.com
- Apply and Save.
Trivy is already set up. Now, scan the docker image with Trivy.
Go to your GCP Console home,
- Click on "APIs & Services
- Search and Enable Artifact Registry API
- Create a GCP Service Account:- Go to IAM & Admin > Service Accounts > Create service account >
- Permissions:- Search and Add Roles: Owner, continue
- Principals with access:- Done
- Generate a Json Key:- After creating the service account, click on the service account to open it and Click on "Keys" tab, click "Add Key", "Create new key", "JSON", and the key is downloaded onto your system (save if securely)
- Go to "Artifact Registry" > Create repository: format=Docker, mode=standard, location type=region = us, desc=Docker repository, project=focal-dock-**
On Jenkins > Manage Jenkins > Credentials > Kind: Secret file, ID: gcp-jmsa, upload the .json key file
Login to the Jenkins Container
docker exec -it jenkins-dind bashsudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates gnupg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | \
sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
sudo apt-get update && sudo apt-get install -y google-cloud-sdkCheck that installation was successful:
gcloud version
exitIn Jenkins > Pipeline Syntax > withCredentials > Bindings: Secret file, Variable: gcpCred, Credentials: select the gcp credential from earlier > Generate Pipeline Script. Copy script to Jenkinsfile & edit.
Make sure that Cloud Run API is enabled. Run the Jenkins pipeline to deploy
Automate Jenkins pipeline build on "git push"
- On Jenkins, Go to the Jenkins job > Configure > Select the "GitHub hook trigger for GITScm polling" trigger > Apply & Save
- On GitHub project repo > Settings > Webhooks > Add webhook
- But our Jenkins container is running locally thus GitHub can't locate its endpoint on the cloud.
- Since, I am not using a cloud VM, I will expose my Jenkins container to the cloud using NGROK bcuz Payload URL: the jenkins url (http://ip:8080/github-webhook/) will NOT work.
- Install NGROK on your linux system (Ngrok creates a secure tunnel)
sudo snap install ngrok- Go to https://dashboard.ngrok.com/signup and sign up. Save your ngrok recovery files safe & setup MFA as well.
- On your linux terminal, run the command that's given to add authtoken to the default ngrok.yml config file. The command looks like this:
ngrok config add-authtoken someRandomStringHere- Now, Deploy your app online with the command
ngrok http http://ip:8080- You will get a public URL (forwarding link) that looks like
https://582f-2603-8080-91f0-4ef0-e8ad-7ee-7ee8-4a4f.ngrok-free.app -> http://ip:8080Now your payload URL should be:
Payload URL: the jenkins url (https://582f-2603-8080-91f0-4ef0-e8ad-7ee-7ee8-4a4f.ngrok-free.app/github-webhook/)
Content type: application/json
Secret: leave blank
SSL verification: Disable
Even: Just the push event
Active
Add webhook- Delete the Cloud Run service
- Delete the Artifact Registry Repo and Image
- Delete the GCP Service Account
- Stop and Delete Jenkins & SonarQube containers