Using Docker

See also

Warning

Fedora 31 Docker Issue
Red Hat Bugzilla – Bug 1757078.
To recap, append systemd.unified_cgroup_hierarchy=0 to GRUB_CMDLINE_LINUX in /etc/default/grub, then reboot.
wink

Note

Installing Portainer

docker volume create portainer_data
docker run -d -p 9000:9000 --name portainer --restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data portainer/portainer

First Steps

The Basic Commands

Tip

docker run
-h, –hostname string Container host name
-i, –interactive Keep STDIN open even if not attached
–name string Assign a name to the container
-t, –tty Allocate a pseudo-TTY

Use awk to display options.

awk 'BEGIN{print "docker run"}$1 ~ /^(--name|-[ith])/{print $0}' <(docker run --help)

Run a command in a new container.

docker run -h CONTAINER -i -t debian /bin/bash
docker inspect quirky_jackson --format {{.NetworkSettings.IPAddress}}
docker diff quirky_jackson
docker logs quirky_jackson
docker exec -it 4006e491063e /bin/bash

docker rm -v $(docker ps -aq -f status=exited)

Create a new image from a container’s changes.

docker run -it --name cowsay --hostname cowsay debian bash
root@cowsay:/# history
    apt update
    apt install fortune cowsay -y
docker ps -a
docker diff cowsay
docker commit cowsay test/cowsayimage
docker images
docker run test/cowsayimage /usr/games/cowsay "Moo"

Building Images from Dockerfiles

Tip

docker build
–no-cache Do not use cache when building the image
–rm Remove intermediate containers after a successful build
-f, –file string Name of the Dockerfile (Default is ‘PATH/Dockerfile’)
-t, –tag list Name and optionally a tag in the ‘name:tag’ format
./cowsay
├── [-rw-r--r--]  Dockerfile
└── [-rwxr-xr-x]  entrypoint.sh

Create Dockerfile.

FROM debian:latest
RUN apt-get update && apt-get install -y cowsay fortune
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
VOLUME /data

Create entrypoint.sh.

#!/bin/bash
if [ $# -eq 0 ]; then
        /usr/games/fortune | /usr/games/cowsay
else
        /usr/games/cowsay "$@"
fi

Build an image from Dockerfile.

cd cowsay/
docker build -t test/cowsay-dockerfile .
docker run test/cowsay-dockerfile "Moo"
 _____
< Moo >
 -----
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Working with Registries

The official Docker registry is the Docker Hub.

registry/repository:tag

Registry a service responsible for hosting and distributing images
Repository a collection of related images
Tag an alphanumeric identifier attached to images

Log in to default Docker registry.

docker login -u guisam

Login Succeeded

Pull/push an image or a repository from a registry.

docker pull amouat/revealjs:latest
docker push guisam/cowsay

Using the Redis Official Image

Tip

docker run
-d, –detach run container in background and print container ID
–rm automatically remove the container when it exits
-v, –volume list bind mount a volume
–link list add link to another container

The –link myredis:redis argument connect the new container redis to the existing myredis container.

Pull redis image, run in background.

docker pull redis
docker run --name myredis -d redis

Link redis container, add/save redis data.

docker run --rm -it --link myredis:redis redis /bin/bash

root@bc6f6085859b:/data# redis-cli -h redis -p 6379
redis:6379> ping
PONG
redis:6379> set "abc" 123
OK
redis:6379> save
OK
redis:6379> get "abc"
"123"

Get redis backup.

docker run --rm --volumes-from myredis -v $(pwd)/backup:/backup \
   debian cp /data/dump.rdb /backup/
backup
└── dump.rdb

Docker Fundamentals

The Docker Architecture

../_images/major_Docker_components.png
  • Docker registries store and distribute images. The default registry is the Docker Hub.
  • Docker daemon, which is responsible for creating, running, and monitoring containers, as well as building and storing images
  • Docker client is on the left-hand side and is used to talk to the Docker daemon. By default, this happens over a Unix domain socket.

How Images Get Built

The Build Context

The docker build command requires a Dockerfile and a build context. The build command docker build -t test/cowsay-dockerfile . in “Building Images from Dockerfiles” sets the context to ‘.’ , the current working directory.

Tip

Use a .dockerignore File

.git
*/.git
*/*/.git

Image Layers

Dockerfile results in a new image layer. This means that while you can start long- lived processes, such as databases or SSH daemons in a RUN instruction, it must be launched from an ENTRYPOINT or CMD instruction.

Show the history of an image.

docker history test/cowsay-dockerfile:latest

Caching

Docker caches each layer in order to speed up the building of images.

--no-cache Do not use cache when building the image

You can also add or change an instruction to invalidate the cache.

ENV UPDATED_ON "14:12 17 February 2015"

Base Images

A base image has FROM scratch in its Dockerfile.

Create Dockerfile.

FROM scratch
ADD hello /
CMD ["/hello"]

Tip

cat hello.c
#include <stdio.h>

int main() {
    printf("hello from docker container\n");
    return 0;
}
gcc -o hello -static hello.c

Build and run hello.

docker build -t test/hello .
docker run --rm test/hello

A parent image is the image that your image is based on.

FROM debian:buster

Dockerfile Instructions

ADD copies files from the build context or remote URLs into the image
CMD runs the given instruction when the container is started
COPY used to copy files from the build context into the image
ENTRYPOINT sets an executable to be run when the container starts
ENV sets environment variables inside the image
EXPOSE indicates to Docker that the container will listen on the given port(s)
FROM sets the base image for the Dockerfile
MAINTAINER sets the “Author” metadata
ONBUILD specifies an instruction to be executed later, when the image is used
RUN runs the given instruction inside the container and commits the result
USER sets the user (by name or UID)
VOLUME declares the specified file or directory to be a volume
WORKDIR sets the working directory

Connecting Containers to the World

Tip

docker run
-p, –publish list Publish a container’s port(s) to the host
-P, –publish-all Publish all exposed ports to random ports
docker run --name mynginx -d -p 8080:80 nginx
docker port mynginx

ID=$(docker run -d -P nginx)
echo $ID
docker port $ID
curl -I localhost:32768

Linking Containers

Links are initialized by giving the argument –link CONTAINER:ALIAS to docker run , where CONTAINER is the name of the link container and ALIAS is a local name used inside the master container to refer to the link container.

docker run --name myredis -d -p 6379:6379 redis
docker run --link myredis:redis debian env
docker run --rm -it --link myredis:redis debian /bin/bash

Managing Data with Volumes and Data Containers

The three different ways to initialize volumes

  1. Declare a volume at runtime with the -v flag.
docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
docker inspect -f {{.Mounts}} container-test
sudo touch /var/lib/docker/volumes/be24.../_data/test-file

docker exec -it container-test /bin/bash
root@CONTAINER:/# ls /data/
test-file
  1. Use the VOLUME instruction in a Dockerfile.
FROM debian:buster
VOLUME /data
  1. Using the format -v HOST_DIR:CONTAINER_DIR.
mkdir data
touch data/test-file
docker run --rm -v $PWD/data:/data debian ls /data

Sharing Data

Share data between containers by using the –volumes-from CONTAINER argument with docker run.

--volumes-from list
 Mount volumes from the specified container(s)
docker run -it -h NEWCONTAINER --volumes-from container-test --rm debian /bin/bash

Data Containers

data containers are containers whose sole purpose is to share data between other containers.

docker run --name dbdata postgres echo "Data-only container for postgres"
docker run -d --volumes-from dbdata --name db1 postgres

Common Docker Commands

The run Command

-a, --attach list
 Attach to STDIN, STDOUT or STDERR
-d, --detach Run container in background and print container ID
-e, --env list Set environment variables
--expose list Expose a port or a range of ports
-h, --hostname string
 Container host name
-i, --interactive
 Keep STDIN open even if not attached
--link list Add link to another container
--name string Assign a name to the container
-p, --publish list
 Publish a container’s port(s) to the host
-P, --publish-all
 Publish all exposed ports to random ports
--restart string
 Restart policy to apply when a container exits (default “no”)
--rm Automatically remove the container when it exits
-t, --tty Allocate a pseudo-TTY
-v, --volume list
 Bind mount a volume
--volumes-from list
 Mount volumes from the specified container(s)

Tip

awk is always helpful.
smile
awk 'BEGIN { printf "%s\n", "The run Command"
printf "%s\n", "---------------" }
$1 ~ /^(-[aditehvpP]|--(expose|link|volumes-from|name|no-cache|restart|rm)$)/{print $0}
END { "date" | getline now
close("date")
printf "\nreported %s\n", now }' <(docker run --help)

Managing Containers

attach Attach local standard input, output, and error streams to a running container
cp Copy files/folders between a container and the local filesystem
create Create a new container
exec Run a command in a running container
kill Kill one or more running containers
pause Pause all processes within one or more containers
restart Restart one or more containers
rm Remove one or more containers
start Start one or more stopped containers
stop Stop one or more running containers
unpause Unpause all processes within one or more containers
ID=$(docker run -d debian sh -c "while true; do echo 'tick'; sleep 1; done;")
docker attach $ID

Docker Info

info Display system-wide information
help Prints usage and help information
version Show the Docker version information

Container Info

diff Inspect changes to files or directories on a container’s filesystem
events Get real time events from the server
inspect Return low-level information on Docker objects
logs Fetch the logs of a container
port List port mappings or a specific mapping for the container
ps List containers
top Display the running processes of a container

Dealing with Images

build Build an image from a Dockerfile
commit Create a new image from a container’s changes
export Export a container’s filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
load Load an image from a tar archive or STDIN
rmi Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE

Using the Registry

login Log in to a Docker registry
logout Log out from a Docker registry
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
search Search the Docker Hub for images

Using Docker in Development

Say “Hello World!”

identidock
├── [drwxr-xr-x]  app
│   └── [-rw-r--r--]  identidock.py
├── [-rwxr-xr-x]  cmd.sh
└── [-rw-r--r--]  Dockerfile

Create identidock.py.

from flask import Flask

app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello Docker!\n'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

Create cmd.sh.

#!/bin/bash
set -e
if [ "$ENV" = 'DEV' ]; then
    echo "Running Development Server"
    exec python "identidock.py"
else
    echo "Running Production Server"
    exec uwsgi --http 0.0.0.0:9090 --wsgi-file /app/identidock.py \
        --callable app --stats 0.0.0.0:9191
fi

Create Dockerfile.

FROM python:latest
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask uWSGI
WORKDIR /app
COPY app /app
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
CMD ["/cmd.sh"]

Build image and run.

docker build -t identidock .
docker run --name hello -d -p 9090-9091:9090-9091 -v "$(pwd)"/app:/app identidock:latest
docker port hello
docker inspect -f {{.Mounts}} hello

Run in dev mode.

docker run --rm -e "ENV=DEV" -p 5000:5000 identidock

Danger

Always Set a USER

It’s important to set the USER statement in all your Dockerfiles. If not set and an attacker break the container, he will have root access to the host machine.

Automating with Compose

identidock
├── [drwxr-xr-x]  app
│   └── [-rw-r--r--]  identidock.py
├── [-rwxr-xr-x]  cmd.sh
├── [-rw-r--r--]  docker-compose.yml
└── [-rw-r--r--]  Dockerfile

Create docker-compose.yml.

identidock:
    build: .
    ports:
        - "7373:5000"
    environment:
        ENV: DEV
    volumes:
        - ./app:/app

Create and start container.

docker-compose up

The Compose Workflow

build Build or rebuild services
logs View output from containers
ps List containers
rm Remove stopped containers
run Run a one-off command
stop Stop services
up Create and start containers
-f, --file FILE
 Specify an alternate compose file
-p, --project-name NAME
 Specify an alternate project name

Creating a Simple Web App

Creating a Basic Web Page

Update identidock/app/identidock.py.

 app = Flask(__name__)
+default_name = 'Gui Sam'
+
 @app.route('/')
-def hello_world():
-    return 'Hello Docker!\n'
+def get_identicon():
+    name = default_name
+    header = '<html><head><title>Identidock</title></head><body>'
+    body = '''
+    <form method="POST">
+    Hello <input type="text" name="name" value="{}">
+    <input type="submit" value="submit">
+    </form>
+    <p>You look like a:
+    <img src="/monster/monster.png"/>
+    '''.format(name)
+    footer = '</body></html>'
+
+    return header + body + footer

 if __name__ == '__main__':
../_images/webapp_01.png

Taking Advantage of Existing Images

  1. Update identidock/app/identidock.py.
-from flask import Flask
+from flask import Flask, Response, request
+import requests
+import hashlib

 app = Flask(__name__)
+salt = "UNIQUE_SALT"
 default_name = 'Gui Sam'

-@app.route('/')
-def get_identicon():
+@app.route('/', methods=['GET', 'POST'])
+def mainpage():
+
     name = default_name
+    if request.method == 'POST':
+        name = request.form['name']
+
+    salted_name = salt + name
+    name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
     header = '<html><head><title>Identidock</title></head><body>'
-    body = '''
-    <form method="POST">
-    Hello <input type="text" name="name" value="{}">
-    <input type="submit" value="submit">
-    </form>
-    <p>You look like a:
-    <img src="/monster/monster.png"/>
-    '''.format(name)
+    body = '''<form method="POST">
+        Hello <input type="text" name="name" value="{0}">
+        <input type="submit" value="submit">
+        </form>
+        <p>You look like a:
+        <img src="/monster/{1}"/>
+        '''.format(name, name_hash)
     footer = '</body></html>'

     return header + body + footer

+@app.route('/monster/<name>')
+def get_identicon(name):
+
+    r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
+    image = r.content
+
+    return Response(image, mimetype='image/png')
+
 if __name__ == '__main__':

Update Dockerfile.

 RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
-RUN pip install Flask uWSGI requests
+RUN pip install Flask uWSGI
 WORKDIR /app

Rebuild image and run identidock and dnmonster.

docker build -t identidock .
docker run -d --name dnmonster amouat/dnmonster:1.0
docker run -d -p 5000:5000 -e "ENV=DEV" --link dnmonster:dnmonster identidock
  1. Update docker-compose.yml.
     volumes:
         - ./app:/app
+    links:
+        - dnmonster
+
+dnmonster:
+    image: amouat/dnmonster:1.0

Tip

docker-compose up
-d, –detach Detached mode: Run containers in the background
–build Build images before starting containers

Launch webapp with compose.

docker-compose up --build -d

Add Some Caching

Update identidock/app/identidock.py.

 import hashlib
+import redis

 app = Flask(__name__)
+cache = redis.StrictRedis(host='redis', port=6379, db=0)
 salt = "UNIQUE_SALT"

 def get_identicon(name):

-    r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
-    image = r.content
+    image = cache.get(name)
+    if image is None:
+        print ("Cache miss", flush=True)
+        r = requests.get('http://dnmonster:8080/monster/' + name + '?size=80')
+        image = r.content
+        cache.set(name, image)

     return Response(image, mimetype='image/png')

Update Dockerfile.

 RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
-RUN pip install Flask uWSGI requests
+RUN pip install Flask uWSGI requests redis
 WORKDIR /app

Update docker-compose.yml.

         - dnmonster
+        - redis

 dnmonster:
     image: amouat/dnmonster:1.0

+redis:
+    image: redis:latest
docker-compse down
docker-compose up --build -d
docker-compose ps
         Name                       Command              State                    Ports
----------------------------------------------------------------------------------------------------------
identidock_dnmonster_1   npm start                       Up     8080/tcp
identidock_identidock_1  /cmd.sh                         Up     0.0.0.0:5000->5000/tcp, 9090/tcp, 9191/tcp
identidock_redis_1       docker-entrypoint.sh redis ...  Up     6379/tcp
../_images/webapp_02.png

source: tgz

Image Distribution

Image and Repository Naming

Image names and tags are set when building the image or by using the docker tag command.

docker build -t "identidock:0.1" .
docker tag "identidock:0.1" "guisam/identidock:0.1"

The Docker Hub

Tag and push on Docker Hub.

docker tag identidock:latest guisam/identidock:0.1
docker push guisam/identidock:0.1

Private Distribution

Running Your Own Registry

registry
├── certs
│   ├── guigui-002.cnf
│   ├── guigui-002.crt
│   └── guigui-002.key
└── registry_data

Create guigui-002.cnf.

[req]
default_bits = 2048
prompt = no
default_md = sha256
x509_extensions = v3_req
distinguished_name = dn

[dn]
C = FR
ST = Neuf-Quatre
L = Ivry-sur-Seine
O = Guisam
emailAddress = toto@guisam.xyz
CN = guigui-002

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = guigui-002.guisam.xyz
DNS.2 = guigui-002.kapinfra.net

Generate a TLS certificate.

openssl req -new -x509 -newkey rsa:4096 -sha512 -nodes \
-keyout guigui-002.key -days 3560 \
-out guigui-002.crt -config guigui-002.cnf

Copy the certificate to each Docker daemon that will access the registry to the file /etc/docker/certs.d/<registry_address>/<ca>.crt.

sudo mkdir -p /etc/docker/certs.d/guigui-002.guisam.xyz:443
sudo cp guigui-002.crt /etc/docker/certs.d/guigui-002.guisam.xyz:443
sudo systemctl restart docker.service

Note

Add the registry hostname in /etc/hosts if needed.

Run registry container.

docker run -d \
  --restart=always \
  --name registry \
  -v "$(pwd)"/certs:/certs \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/guigui-002.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/guigui-002.key \
  -p 443:443 \
  -v "$(pwd)"/registry_data:/var/lib/registry \
  registry:2

Create tag and push.

docker tag alpine:latest guigui-002.guisam.xyz:443/alpine:local
docker push guigui-002.guisam.xyz:443/alpine:local

Pull images from registry.

docker pull guigui-002.guisam.xyz:443/alpine:local

Manage registry with curl:

  • Check registry service.
curl -I -k https://guigui-002.guisam.xyz:443/v2/
HTTP/2 200
content-type: application/json; charset=utf-8
docker-distribution-api-version: registry/2.0
x-content-type-options: nosniff
content-length: 2
date: Fri, 13 Mar 2020 17:58:17 GMT
  • List registry catalog.
curl -k https://guigui-002.guisam.xyz:443/v2/_catalog
{"repositories":["alpine","identidock"]}
  • Check/get alpine:local image existence.
curl -I -k https://guigui-002.guisam.xyz:443/v2/alpine/manifests/local
HTTP/2 200
...
curl -k https://guigui-002.guisam.xyz:443/v2/alpine/manifests/local

Reducing Image Size

The total size of the image is the sum of all its layers.

.
├── Dockerfile1
└── Dockerfile2

Create Dockerfile1.

FROM debian:buster
RUN dd if=/dev/zero of=/bigfile count=1 bs=50MB
RUN rm /bigfile

Create Dockerfile2.

FROM debian:buster
RUN dd if=/dev/zero of=/bigfile count=1 bs=50MB \
    && rm /bigfile

Build the both containers.

docker build -f Dockerfile1 -t test/filetest1 .
docker build -f Dockerfile2 -t test/filetest2 .

Compare the containers images size.

docker images test/filetest1
REPOSITORY          TAG        IMAGE ID        CREATED             SIZE
test/filetest1      latest     77081a6d4e51    14 seconds ago      164MB
docker images test/filetest2
REPOSITORY          TAG        IMAGE ID        CREATED             SIZE
test/filetest2      latest     dcd9a0ad691d    17 minutes ago      114MB

Compare the containers images history.

docker history test/filetest1
IMAGE               CREATED          CREATED BY                                      SIZE
77081a6d4e51        2 minutes ago    /bin/sh -c rm /bigfile                          0B
98906ef412f3        2 minutes ago    /bin/sh -c dd if=/dev/zero of=/bigfile count…   50MB
971452c94376        2 weeks ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           2 weeks ago      /bin/sh -c #(nop) ADD file:e05e45c33042db4ec…   114MB
docker history test/filetest2
IMAGE               CREATED          CREATED BY                                      SIZE
dcd9a0ad691d        20 minutes ago   /bin/sh -c dd if=/dev/zero of=/bigfile count…   0B
971452c94376        2 weeks ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           2 weeks ago      /bin/sh -c #(nop) ADD file:e05e45c33042db4ec…   114MB

Image Provenance

See Content Trust.

Continuous Integration and Testing with Docker

Adding Unit Tests to Identidock

Create identidock/app/tests.py.

import unittest
import identidock

class TestCase(unittest.TestCase):

    def setUp(self):
        identidock.app.config["TESTING"] = True
        self.app = identidock.app.test_client()

    def test_get_mainpage(self):
        page = self.app.post("/", data=dict(name="Moby Dock"))
        assert page.status_code == 200
        assert 'Hello' in str(page.data)
        assert 'Moby Dock' in str(page.data)

    def test_html_escaping(self):
        page = self.app.post("/", data=dict(name='"><b>TEST</b><!--'))
        assert '<b>' not in str(page.data)

if __name__ == '__main__':
    unittest.main()

Rebuild and run unit tests.

docker build -t identidock .
docker run --rm identidock python tests.py
.F
======================================================================
FAIL: test_html_escaping (__main__.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 18, in test_html_escaping
    assert '<b>' not in str(page.data)
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.009s

FAILED (failures=1)

Update identidock.py to fix unit tests failure.

 import redis
+import html
...
 app = Flask(__name__)

     if request.method == 'POST':
-        name = request.form['name']
+        name = html.escape(request.form['name'], quote=True)

     salted_name = salt + name
...
 def get_identicon(name):

+    name = html.escape(name, quote=True)
     image = cache.get(name)

Update cmd.sh to support automatic execution.

 exec python "identidock.py"
+elif [ "$ENV" = 'UNIT' ]; then
+       echo "Running Unit Tests"
+       exec python "tests.py"
 else

Rebuild and run.

docker build -t identidock .
docker run --rm -e ENV="UNIT" identidock
Running Unit Tests
..
  ----------------------------------------------------------------------
Ran 2 tests in 0.008s

OK

Creating a Jenkins Container

Jenkins is a popular open source CI server. When we push changes to our identidock project, Jenkins will automatically check out the changes, build the new images, and run some tests against them (both our unit tests and some system tests).

Docker-In-Docker versus Socket Mounting

In order to allow Docker container to build images:

  • mount the Docker socket from the host into the container;
  • or use Docker-in-Docker (DinD), where the Docker container can create its own “child” containers.
../_images/socket_vs_dind.png

Note

Docker-in-Docker

docker run --rm --privileged -t -i -e LOG=file jpetazzo/dind
root@2144149fdb6a:/# docker run busybox echo "Hello New World!"

Create Jenkins image with Socket Mounting and Docker client installed

  1. Create identijenk directory.
.
├── identidock
│   └── ...
└── identijenk
    ├── Dockerfile
    └── plugins.txt
  1. Create identijenk files.
  • Dockerfile
FROM jenkins/jenkins:lts

USER root

RUN apt-get update \
    && apt-get install -y apt-transport-https; \
    echo "deb [arch=amd64] https://download.docker.com/linux/debian stretch stable" \
        > /etc/apt/sources.list.d/docker.list; \
    curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
    && apt-get update \
    && apt-get install -y docker-ce-cli sudo \
    && rm -rf /var/lib/apt/lists/*; \
    curl -L "https://github.com/docker/compose/releases/download/1.25.4/\
        docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose; \
    chmod +x /usr/local/bin/docker-compose; \
    echo "jenkins ALL=NOPASSWD: ALL" >> /etc/sudoers

COPY plugins.txt /usr/share/jenkins/plugins.txt
RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins.txt
USER jenkins
  • plugins.txt
credentials:2.3.3
workflow-scm-step:2.9
apache-httpcomponents-client-4-api:4.5.10-2.0
trilead-api:1.0.5
ssh-credentials:1.18.1
script-security:1.71
jsch:0.1.55.2
structs:1.20
scm-api:2.6.3
git-client:3.2.1
git:4.2.2
greenballs:latest
mailer:1.30
display-url-api:2.3.2

4. Build identijenk image and create jenkins-data container. Then run jenkins container with socket mount.

docker build -t identijenk .
docker run --name jenkins-data identijenk echo "Jenkins Data Container"
docker run -d --name jenkins -p 8080:8080 \
--volumes-from jenkins-data \
-v /var/run/docker.sock:/var/run/docker.sock \
identijenk

Tip

Check SSH tunnels existence.

awk '{ if ($5 == "ssh" && $6 == "-L")
{ $2 = $3 = $4 = ""
$8 = gensub(/(.*)(@guigui-002)(.+)/, "<user>\\2", "g", $8)
print $0 }}' <(ps ax)
891164    ssh -L 8080:localhost:8080 <user>@guigui-002 -f -N
101614    ssh -L 7373:localhost:7373 <user>@guigui-002 -f -N

Kill existing SSH tunnels.

awk '{ if ($5 == "ssh" && $6 == "-L")
{ print $1 }}' <(ps ax) | xargs kill

wink

  1. Check docker service in jenkins container and web interface.
docker exec -it jenkins /bin/bash
jenkins@92eb4708c136:/$ sudo docker ps -a
CONTAINER ID        IMAGE                 [...]  NAMES
92eb4708c136        identijenk            [...]  jenkins
d4c8bbf78fc4        identijenk            [...]  jenkins-data
917cbd9b014c        identidock_identidock [...]  identidock_identidock_1
1c1f1381620c        redis:latest          [...]  identidock_redis_1
d1a5398cd267        amouat/dnmonster:1.0  [...]  identidock_dnmonster_1
055dac63eda9        registry:2            [...]  registry
jenkins@92eb4708c136:/$ sudo docker run --rm test/hello
hello from docker container
../_images/jenkins_01.png

Jenkins web interface