If Jenkins is running inside a Docker container and the CI build is setup to create Docker images, you have to find a way how to use Docker inside Docker. Indeed, there exists such a way to run Docker-in-Docker as described here. However, the primary purpose of this mechanism was the help with development of Docker itself. Although this Docker-in-Docker mechanism generates many problems which are listed in this blog, it is often abused to run a CI inside a container that creates Docker images.
A better way to miles is to run Docker inside Docker by bind-mounting the Docker socket of the Docker host into the Docker Jenkins container. This can be achieved by installing Docker binaries into the Jenkins container and then mapping the Docker socket as volume from the Docker host to the Jenkins container.
First of all, we need to have a Jenkins Docker image. Therefore, I created the Dockerfile shown below. It comprises a couple of useful tools and Jenkins itself. Furthermore, it installs the Docker binaries, docker-compose and docker-machine. The latter two are not really needed here, but I added them for completeness.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM renewinkler/ubuntu-oraclejdk:8 | |
ENV DOCKER_VERSION 1.12.0 | |
ENV DOCKER_COMPOSE_VERSION 1.8.0 | |
ENV DOCKER_MACHINE_VERSION 0.8.0 | |
# tools | |
RUN apt-get update -qq && apt-get install -qq curl wget git subversion nano nodejs npm iputils-ping && apt-get clean | |
# Maven | |
RUN curl -sf -o /opt/apache-maven-bin.tar.gz http://archive.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz; \ | |
tar xzf /opt/apache-maven-bin.tar.gz -C /opt/; \ | |
rm /opt/apache-maven-bin.tar.gz; \ | |
ln -s /opt/apache-maven-3.3.9 /opt/maven | |
ENV MAVEN_HOME /opt/maven | |
#Docker bins | |
WORKDIR /home/toolbox/ | |
RUN curl -L -o /tmp/docker-latest.tgz https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz && \ | |
tar -xvzf /tmp/docker-latest.tgz && \ | |
mv docker/* /usr/bin/ | |
#Docker compose | |
RUN curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose && \ | |
chmod +x /usr/local/bin/docker-compose | |
#Docker machine | |
RUN curl -L https://github.com/docker/machine/releases/download/v${DOCKER_MACHINE_VERSION}/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine && \ | |
chmod +x /usr/local/bin/docker-machine | |
# Jenkins | |
ENV JENKINS_HOME /opt/jenkins | |
ENV JENKINS_MIRROR http://mirrors.jenkins-ci.org | |
RUN mkdir -p $JENKINS_HOME | |
RUN curl -sf -o /opt/jenkins/jenkins.war -L $JENKINS_MIRROR/war/latest/jenkins.war | |
RUN mkdir -p $JENKINS_HOME/plugins; for plugin in greenballs; \ | |
do curl -sf -o $JENKINS_HOME/plugins/${plugin}.hpi -L $JENKINS_MIRROR/plugins/${plugin}/latest/${plugin}.hpi; done | |
VOLUME $JENKINS_HOME/data | |
WORKDIR $JENKINS_HOME | |
EXPOSE 8080 | |
CMD [ "java", "-jar", "jenkins.war" ] |
Once the image is built, it can be started. In this step the Docker socket of the Docker host has to be bind-mounted by the -v flag via /var/run/docker.sock:/var/run/docker.sock. The volume instruction from within a Dockerfile doesn’t allow to do a host mount. We just can do this from a Docker run command. This is the reason why this volume mapping is not done in the Dockerfile directly. Simply put, when you start the container, start it as follows:
docker run -itd —-name jenkins -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock renewinkler/jenkins-oraclejdk
That’s basically all. If you enter the container and type e.g. ‚docker images‘ you should see all images of your Docker host.
Lastly, I want to demonstrate a way how to create a Docker image in a Jenkins build. For this purpose, there exist several maven plugins. One of the better is the maven-plugin-docker from Spotify. There also exist the maven Docker plugin of fabric8 which is great too. I configured the former plugin in my maven build as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<plugin> | |
<groupId>com.spotify</groupId> | |
<artifactId>docker-maven-plugin</artifactId> | |
<version>0.4.11</version> | |
<executions> | |
<execution> | |
<id>build-image</id> | |
<phase>package</phase> | |
<goals> | |
<goal>build</goal> | |
</goals> | |
</execution> | |
<execution> | |
<id>push-image</id> | |
<phase>deploy</phase> | |
<goals> | |
<goal>push</goal> | |
</goals> | |
<configuration> | |
<imageName>registry.example.com/app_name:${project.version}</imageName> | |
</configuration> | |
</execution> | |
</executions> | |
<configuration> | |
<imageName>app_name</imageName> | |
<imageTags> | |
<imageTag>${project.version}</imageTag> | |
<imageTag>latest</imageTag> | |
</imageTags> | |
<baseImage>java:8</baseImage> | |
<entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> | |
<!– copy the service's jar file from target into the root directory of the image –> | |
<resources> | |
<resource> | |
<targetPath>/</targetPath> | |
<directory>${project.build.directory}</directory> | |
<include>${project.build.finalName}.jar</include> | |
</resource> | |
</resources> | |
</configuration> | |
</plugin> |
The plugin is bind to the package phase which builds the image as well as to the deploy phase which pushes the image to the configured registry. The built images is based on the java:8 base image and two tags latest and current project version are created. The generated jar file of my application will be the entrypoint of the image. As soon as the build mvn clean install is finished, a newly generated Docker image should be visible in the local Docker host registry.