DevSecOps Course Labs

Building Docker Images with Jenkins

You can run containers which have access to the Docker engine - so the app inside the container can use the Docker CLI to run other containers or build images. That makes for a nice clean CI/CD setup, where you run Jenkins in a container using pipelines which build your applications with multi-stage Dockerfiles. The pipelines can also run containers from the new images to smoke test the functionality.

With this approach your whole build infrastructure is portable. You can run a Jenkins server on any Docker engine, load in your pipelines and build any type of software without installing any other tools. You can also save credentials in Jenkins so you can authenticate with other services - then you can push images to Docker Hub or deploy to your production environment from your pipeline.

References

Build a Simple Java Image

We'll start by building this Dockerfile, using this Jenkinsfile. That will package a pre-built Java app into a Docker image and run a container from the image. If the build or the test stages fail then the pipeline fails.

Start by running Jenkins and Gogs using this Docker Compose spec:

docker-compose -f labs/pipeline/infra/docker-compose.yml up -d

The Jenkins service is configured to mount the Docker socket, which is how the container can talk to the Docker Engine where it's running.

This is a new Gogs server, so we'll need to set it up for our repo:

Add your Gogs server as a remote and push the repo to it:

# you may already have this set up from the Jenkins lab - ignore any errors you see:
git remote add gogs http://localhost:3000/courselabs/devsecops.git

git push gogs main

Now we can create a pipeline in Jenkins with:

📋 Open the Jenkins UI and create the new pipeline job.

Not sure how?

Save and run the build.

📋 Check the build output. What is the name of the Docker image that it built? And what is the output from the test container?

Not sure?

Open the logs from the Build stage and you'll see the image tag is docker.io/courselabs/hello-world:1.

In the Test stage logs you'll see the container output is the string Hello, World.

This is a simple example, but we don't want to just package binaries into images - we want to build them from source code, which we can do with a multi-stage build.

Multi-stage Builds in Jenkins

There's nothing special about multi-stage builds - any Docker builds we can run on the laptop work just the same in the Jenkins container:

Those files are already in your Gogs server, so you can create a new pipeline in Jenkins to run the script in labs/pipeline/multi-stage/Jenkinsfile.

📋 Create and run a new pipeline called multi-stage.

Not sure how?

Create a new item in the Jenkins UI at http://localhost:8080/view/all/newJob, call it multi-stage, set the type to be Pipeline and enter docker in the box to Copy from an existing pipeline.

Change the script path to labs/pipeline/multi-stage/Jenkinsfile.

Click Save and then Build Now.

The build runs with the familiar stages we've been using. Remember that Jenkins is sharing your Docker Engine, so when it builds images they're available for you to use in docker commands.

📋 What is the size of the new image from the build? How does it compare to the SDK?

Not sure?

You can list images on your machine to see the details:

# image built from pipeline:
docker image ls courselabs/multi-stage:1

# java sdk image from Docker Hub:
docker pull openjdk:11-jdk-slim
docker image ls openjdk:11-jdk-slim 

You should see your application image is about half the size of the SDK image.

Building with Docker Compose

This Jenkins container image also has the docker compose command installed, so we can build multiple images from one command.

We'll be building the random number app from source:

So this is a .NET app. To be sure we're not cheating, run this command inside the Jenkins container to confirm that .NET is not installed:

# this will fail
docker exec infra_jenkins_1 dotnet --version

You'll see a not found in $PATH error because there is no .NET in the container. That's OK though because it's a multi-stage build and the compilation will run inside containers.

📋 Create and run a new pipeline called compose from the script path labs/pipeline/compose/Jenkinsfile.

Not sure how?

Create a new item in the Jenkins UI at http://localhost:8080/view/all/newJob, call it compose, set the type to be Pipeline and enter docker in the box to Copy from an existing pipeline.

Change the script path to labs/pipeline/compose/Jenkinsfile.

Click Save and then Build Now.

Check the output - the build should run successfully and generate images.

📋 How many images are built? Where does the 21.12 part of the image tag come from?

Not sure?

The logs from the Build stage will show two images being built: courselabs/rng-api:21.12-1 and courselabs/rng-web:21.12-1.

21.12 is the release cycle, which is set in an environment variable in the Jenkinsfile.

Environment variables are used for the registry and repository names in the image tag, so they can be easily edited to change where the images get pushed.

Pushing to Docker Hub

The publish part of our pipeline will push images to Docker Hub. For that you'll need three things:

When you have a Docker Hub account, you can browse to your settings and generate an access token. Copy the token to your clipboard - you can use it to log in so you don't store your actual password in Jenkins.

Now create a credential in Jenkins:

Next edit the Jenkinsfile:

Push your changes:

git add labs/pipeline/compose/Jenkinsfile  

git commit -m 'Added push'

git push gogs main

📋 Build the compose job again and verify the images are built and pushed to Docker Hub under your username.

Not sure how?

Check all the build stages. If the Push stage fails, check the logs. The issue will be a problem authenticating to Docker Hub:

If you get an error message it should be clear what the issue is. When you get the build working you'll see images being pushed to Docker Hub, like mine at https://hub.docker.com/r/sixeyed/rng-api/tags

You probably don't have Jenkins, Gogs or .NET installed on your machine. But now you can build and push Docker images for a .NET app with a fully automated pipeline, which brings in all the dependencies it needs.

Lab

Docker image tags are typically used for versioning. Our images contain a release cycle and a build number as the image version. We also want to provide less specific versions of the image, e.g.

latest is the default tag, so users can run a container from courselabs/rng-api to get the latest version, or courselabs/rng-api:21.12 to get the latest build for the release, or courselabs/rng-api:21.12-3 to run a specific build.

There are some additional Compose overrides we can use to build images with the extra tags:

Extend the Jenkinsfile to add those tags and push them to Docker Hub as part of the build. The goal is for each build to push the specific image tag and update the other tags, so they work as aliases for the same image digest:

You'll need to push your Jenkinsfile changes to Gogs and run a new build in Jenkins. When you have it all working, pull the latest image using Docker on your machine. Can you find the build information and Git hash from the metadata?

Stuck? Try hints or check the solution.


Cleanup

Cleanup by removing all containers:

docker rm -f $(docker ps -aq)

And remove the Gogs remote:

git remote rm gogs