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