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.
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:
browse to http://localhost:3000
sign in with username courselabs
and password student
click the plus icon +
in the My Repositories section to create a new repo
call the repo devsecops
and click Create Repository
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:
docker
http://gogs:3000/courselabs/devsecops.git
refs/heads/main
labs/pipeline/docker/Jenkinsfile
📋 Open the Jenkins UI and create the new pipeline job.
courselabs
and password student
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?
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.
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:
multi-stage/Dockerfile - builds the same Java app from source, so we no longer need a separate compilation step
multi-stage/Jenkinsfile - builds the image using environment variables to generate the tag, then runs a test 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
.
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?
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.
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:
compose/Jenkinsfile - builds the app in the labs/compose-build/rng
folder; the Push stage is commented out so only the Build stage will run
the API Dockerfile - is a multi-stage build using the .NET SDK and runtime images
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
.
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?
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.
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:
docker-hub
in the ID fieldNext edit the Jenkinsfile:
REPOSITORY="courselabs"
with your own Docker Hub IDsixeyed
so my updated setting will read REPOSITORY="sixeyed"
/*
and end */
commentsPush 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.
Check all the build stages. If the Push stage fails, check the logs. The issue will be a problem authenticating to Docker Hub:
docker-id
credentials in Jenkins are correctREPOSITORY
environment variable matches the Docker Hub ID in the credentialsIf 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.
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.
courselabs/rng-api:21.12-3
- is build 3 of the 21.12 releasecourselabs/rng-api:21.12
- is the latest build of the 21.12 releasecourselabs/rng-api:latest
- is the latest build of the latest releaselatest
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?
Cleanup by removing all containers:
docker rm -f $(docker ps -aq)
And remove the Gogs remote:
git remote rm gogs