Docker under the hood ?

  • LXC
  • Linux Kernel
  • cgroups
  • kernel namespaces ...      

Not explained in this talk

Versions

06/2014 - Docker

1.0.0

1.1.0

1.2.0

...

1.13.1

03/2017 - Docker Enterprise/Community Edition

 

1.0.0

17.03

Versions

$ docker version                        
Client:
 Version:      17.07.0-ce
 API version:  1.31
 Go version:   go1.8.3
 Git commit:   87847530f7
 Built:        Wed Aug 30 21:31:46 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.07.0-ce
 API version:  1.31 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   87847530f7
 Built:        Wed Aug 30 23:32:01 2017
 OS/Arch:      linux/amd64
 Experimental: false

Basics

Docker images

Docker containers

Images

Name[:Tag]

ubuntu

Examples: 

===  ubuntu:latest

ubuntu:17.04

php

php:7.1-fpm-alpine

===  php:latest

{

Official

Images

blackfire/blackfire

===  blackfire/blackfire:latest

blackfire/blackfire:1.12.0

mykiwi/symfony-base:7.1-fpm-alpine

Public

Images

/

/

/

phpmyadmin/phpmyadmin:edge-4.8

/

{

Official Images

  • Provide essential base OS repositories (for example, ubuntu, centos) that serve as the starting point for the majority of users.

  • Provide drop-in solutions for popular programming language runtimes, data stores, and other services, similar to what a Platform-as-a-Service (PAAS) would offer.

  • Exemplify Dockerfile best practices and provide clear documentation to serve as a reference for other Dockerfileauthors.

  • ...

 

Docker, Inc. sponsors a dedicated team that is responsible for reviewing and publishing all Official Repositories content

primarily hosted on github.com/docker-library

Docker Registry

Stores and distribute

Docker images

Docker Hub

Free-to-use, hosted Registry, plus additional features (organization accounts, automated builds, and more)

Images from Registry

ubuntu

docker.io/ubuntu  ===

registry.gitlab.com/foo/bar/baz

docker.elastic.co/elasticsearch/elasticsearch:6.0.0

Play with Docker Images

Image

>

Container

Run a Container

$ docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

List your containers using

$ docker ps -a

CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS                                      NAMES
8993da501dff        ubuntu                "echo 'hello world'"     5 seconds ago       Exited (0) 3 seconds ago                                              goofy_hamilton
0fa641af2a08        jwilder/nginx-proxy   "/app/docker-entry..."   2 weeks ago         Up 3 hours                 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   proxy

Container Status

  • Up
  • Dead
  • Exited
$ docker ps -a

CONTAINER ID      ...  STATUS
8993da501dff      ...  Exited (0) 3 seconds ago
0fa641af2a08      ...  Up 3 hours

Tips:

  • Always display all containers using `docker ps -a|--all`
  • Keep an eye on this list
  • Remove unused container
  • Created
  • Restarting
  • Removal In Progress

Docker in action

Build a Docker Image

# Dockerfile

FROM php:7.1-fpm

ADD php.ini /usr/local/etc/php/conf.d/my.ini

RUN apt-get update

RUN apt-get install -y --no-install-recommends libicu-dev

RUN docker-php-ext-install intl

ADD script.php /

CMD php script.php

Build a Docker Image

$ ls
Dockerfile

$ docker build .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM scratch
 ---> 
Step 2/2 : ENV hello world
 ---> Running in 59d4ab6c9dc3
 ---> 4fbe4f3d2905
Removing intermediate container 59d4ab6c9dc3
Successfully built 4fbe4f3d2905

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
<none>              <none>              4fbe4f3d2905        About a minute ago   0B


$ docker build -t foo/bar .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM scratch
 ---> 
Step 2/2 : ENV hello world
 ---> Using cache
 ---> 4fbe4f3d2905
Successfully built 4fbe4f3d2905
Successfully tagged foo/bar:latest

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
foo/bar             latest              4fbe4f3d2905        3 minutes ago       0B

Optimize your Images

# Dockerfile

FROM php:7.1-fpm

ADD php.ini /usr/local/etc/php/conf.d/my.ini

RUN apt-get update

RUN apt-get install -y --no-install-recommends libicu-dev

RUN docker-php-ext-install intl

# Dockerfile

FROM php:7.1-fpm

RUN apt-get update

RUN apt-get install -y --no-install-recommends libicu-dev

RUN docker-php-ext-install intl

ADD php.ini /usr/local/etc/php/conf.d/my.ini

# Dockerfile

FROM php:7.1-fpm

RUN apt-get update && \
    apt-get install -y --no-install-recommends libicu-dev && \
    docker-php-ext-install intl

ADD php.ini /usr/local/etc/php/conf.d/my.ini

# Dockerfile

FROM php:7.1-fpm

RUN apt-get update && \
    apt-get install -y --no-install-recommends libicu-dev && \
    docker-php-ext-install intl && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

ADD php.ini /usr/local/etc/php/conf.d/my.ini

# Dockerfile

FROM php:7.1-fpm

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        libicu-dev && \
    docker-php-ext-install \
        intl && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

ADD php.ini /usr/local/etc/php/conf.d/my.ini

Best practices for Dockerfiles

  • use/inherit Official images, or images trusted with a Dockerfile (+ RTFM)

  • Avoid installing unnecessary packages

  • Minimize the number of layers

  • Sort multi-line arguments

  • Use Build cache

  • Use .dockerignore file

Full details on

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/

Build a Dockerfiles

$ cat Dockerfile                                                                                                                               
FROM ubuntu
CMD echo "hello world!"

$ docker build --tag name:tag .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu
 ---> ebcd9d4fca80
Step 2/2 : CMD echo "hello world!"
 ---> Running in 730a4d212a56
 ---> bae3108b648f
Removing intermediate container 730a4d212a56
Successfully built bae3108b648f
Successfully tagged name:tag

$ docker run name:tag
hello world!

Tips

Try, Fail, Learn, Repeat

 

Inspire yourself from official Dockerfiles & community Dockerfiles

 

Before create a Dockerfile,

Try to do it by hand:

$ docker run --rm -ti ubuntu bash

root@19e82731443e:/# apt-get install ...

Tips

$ cat Dockerfile
FROM ubuntu:trusty

CMD ping localhost

$ docker ps 
CONTAINER ID        IMAGE      COMMAND
17ddc535daf4        demo       "/bin/sh -c 'ping localhost'"
$ cat Dockerfile
FROM ubuntu:trusty

CMD ["ping", "localhost"]

$ docker ps
CONTAINER ID        IMAGE      COMMAND
5080c763b73e        demo       "ping localhost"

Volumes

Volumes are directories that are stored outside of the container’s filesystem and which hold reusable and shareable data that can persist even when containers are terminated.

Can see them as symlink between the host and a container

Volumes in action

Volumes with VM

Can see them as symlink between the host and a container

$ docker-machine create -d virtualbox docker-vm
....
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running
on this virtual machine, run: docker-machine env docker-vm

$ docker-machine env docker-vm   
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/mykiwi/.docker/machine/machines/docker-vm"
export DOCKER_MACHINE_NAME="docker-vm"
# Run this command to configure your shell: 
# eval $(docker-machine env docker-vm)

$ eval $(docker-machine env docker-vm)

Volumes with VM

$ ls -la
total 12
drwxr-xr-x  2 mykiwi  100 Oct 17 13:56 ./
drwxrwxrwt 24 root   1060 Oct 17 14:09 ../
-rw-r--r--  1 mykiwi    4 Oct 17 13:56 bar
-rw-r--r--  1 root      7 Oct 17 13:56 hi-from-a-container
-rw-r--r--  1 mykiwi    6 Oct 17 13:56 world

$ docker run --rm -v `pwd`:/srv alpine ls -la /srv
total 8
drwxr-xr-x    2 root     root          4096 Oct 17 12:12 .
drwxr-xr-x   24 root     root          4096 Oct 17 12:14 ..

}?

Host / docker client

Host's filesystem

VM / docker server

VM's filesystem

http

docker-machine-nfs

docker-machine-nfs

$ docker-machine-nfs docker-vm --shared-folder=/home/mykiwi
[INFO] Configuration:

- Machine Name: docker-vm
- Shared Folder: /home/mykiwi
- Mount Options: noacl,async
- Force: false

[INFO] machine presence ... 			OK
[INFO] machine running ... 			OK
[INFO] Lookup mandatory properties ...          OK

- Machine IP: 192.168.99.100
- Network ID: vboxnet0
- NFSHost IP: 192.168.99.1

[INFO] Configure NFS ... 
!!! Sudo will be necessary for editing /etc/exports !!!
[sudo] password for mykiwi: 

$ pwd
/home/mykiwi/foo
$ docker run --rm -v `pwd`:/srv alpine ls -la /srv
total 8
drwxr-xr-x    2 1000     1000          4096 Oct 17 12:12 .
drwxr-xr-x   24 1000     1000          4096 Oct 17 12:14 ..
-rw-r--r--    1 1000     1000             4 Oct 17 13:56 bar
-rw-r--r--    1 root     root             7 Oct 17 13:56 hi-from-a-container
-rw-r--r--    1 1000     1000             6 Oct 17 13:56 world

Real usecase

&

Symfony requirements

$ docker run --rm -ti php:5.6-fpm-alpine sh

/var/www/html # apk add --update --no-cache git unzip zlib-dev ca-certificates
/var/www/html # php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
/var/www/html # php composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet

/var/www/html # composer create-project symfony/requirements-checker
/var/www/html # php requirements-checker/bin/check.php 

Symfony Requirements Checker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> PHP is using the following php.ini file:
  WARNING: No configuration file (php.ini) used by PHP!

> Checking Symfony requirements:
  ...E.....................WWW.....

                                                  
 [ERROR]                                          
 Your system is not ready to run Symfony projects 
                                                  

Fix the following mandatory requirements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 * date.timezone setting must be set
   > Set the "date.timezone" setting in php.ini* (like Europe/Paris).


Optional recommendations to improve your setup
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 * intl extension should be available
   > Install and enable the intl extension (used for validators).

 * a PHP accelerator should be installed
   > Install and/or enable a PHP accelerator (highly recommended).

 * short_open_tag should be disabled in php.ini
   > Set short_open_tag to off in php.ini*.


Note  The command console could use a different php.ini file
~~~~  than the one used with your web server. To be on the
      safe side, please check the requirements from your web
      server using the web/check.php script.
$ docker run --rm -ti php:7.1-fpm-alpine sh

/var/www/html # apk add --update --no-cache git unzip zlib-dev ca-certificates
/var/www/html # php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
/var/www/html # php composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet

/var/www/html # composer create-project symfony/requirements-checker
/var/www/html # php requirements-checker/bin/check.php 

Symfony Requirements Checker
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> PHP is using the following php.ini file:
  WARNING: No configuration file (php.ini) used by PHP!

> Checking Symfony requirements:
  ........................WWW.....

                                              
 [OK]                                         
 Your system is ready to run Symfony projects 
                                              

Optional recommendations to improve your setup
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 * intl extension should be available
   > Install and enable the intl extension (used for validators).

 * a PHP accelerator should be installed
   > Install and/or enable a PHP accelerator (highly recommended).

 * short_open_tag should be disabled in php.ini
   > Set short_open_tag to off in php.ini*.


Note  The command console could use a different php.ini file
~~~~  than the one used with your web server. To be on the
      safe side, please check the requirements from your web
      server using the web/check.php script.

Run Symfony

  1. one php container, with Symfony using bin/console server:run
    • easy to run
  2. one php container, using apache
    • need a vhost configuration for apache
  3. one nginx container, one php container
    • need a vhost configuration for nginx
    • need a connection between this two containers

Run Symfony using php's web server

$ docker run --rm -d \
    --name symfony_app \
    --volume `pwd`:/srv \
    --workdir /srv \
    --publish 8000:8000 \
    php:7.1-fpm-alpine bin/console \
        server:run \
        0.0.0.0:8000

0483a74d54a071269d944ad9e2ccd03877f07282c9308f4f16edefa7b5ea922a

$ docker logs symfony_app

 [OK] Server listening on http://0.0.0.0:8000                                   

 // Quit the server with CONTROL-C.                                             

PHP 7.1.5 Development Server started at Wed May 31 10:11:51 2017

$ curl localhost:8000
# HTML response

$ docker logs symfony_app

 [OK] Server listening on http://0.0.0.0:8000                                   

 // Quit the server with CONTROL-C.                                             

PHP 7.1.5 Development Server started at Wed May 31 10:11:51 2017
[Wed May 31 10:14:20 2017] 172.17.0.1:58272 [200]: /

Run Symfony using php & apache

$ cat vhost
<VirtualHost *:80>
    DocumentRoot    /srv/web
    DirectoryIndex  app.php
    <Directory "/srv/web">
        AllowOverride None
        Require all granted
        <IfModule mod_rewrite.c>
            Options -MultiViews
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ app.php [QSA,L]
        </IfModule>
    </Directory>
</VirtualHost>

$ docker run --rm -d \
    --name symfony_app \
    --volume `pwd`:/srv \
    --volume `pwd`/vhost:/etc/apache2/sites-enabled/000-default.conf \
    --workdir /srv \
    --publish 8000:80 \
    mykiwi/symfony-base:7.1-apache

9d3964623e48180508d39ba350f4931f9368e90eb3e2e91d57c161cbbe0f0254

$ curl localhost:8000
# HTML response

$ docker logs symfony_app
# ...
172.17.0.3:80 172.17.0.1 - - [31/May/2017:10:33:43 +0000] "GET / HTTP/1.1" 200 4796 "-" "curl/7.54.0"
  

Run Symfony using php & nginx

1. php fpm container

$ docker run --rm -d \
    --name php \
    --volume `pwd`:/srv \
    php:7.1-fpm-alpine

9d3964623e48180508d39ba350f4931f9368e90eb3e2e91d57c161cbbe0f0254

$ # You may need to fix permissions
$ docker exec php chmod -R 777 /srv/var

Run Symfony using php & nginx

2. nginx container

$ docker run --rm -d \
    --name nginx \
    --volume `pwd`:/srv \
    --volume `pwd`/vhost:/etc/nginx/conf.d/default.conf \
    --link php \
    --publish 8000:80 \
    nginx:1-alpine

9d3964623e48180508d39ba350f4931f9368e90eb3e2e91d57c161cbbe0f0254

$ docker exec nginx cat /etc/hosts
127.0.0.1       localhost
172.17.0.2      php 9d3964623e48

$ cat vhost
upstream php {
    server php:9000;
}
server {
    listen 80;
    root /srv/web;
    location / {
        try_files $uri @rewriteapp;
    }
    location @rewriteapp {
        rewrite ^(.*)$ /app.php/$1 last;
    }
    location ~ ^/app.php(/|$) {
        fastcgi_pass php;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

Run Symfony using php & nginx

$ curl localhost:8000
# HTML response

$ docker logs nginx
172.17.0.1 - - [31/May/2017:12:03:03 +0000] "GET / HTTP/1.1" 200 4577 "-" "curl/7.54.0" "-"

$ docker logs php
172.17.0.4 -  31/May/2017:12:03:03 +0000 "GET /app.php" 200

Network : Link successor

$ docker network

Usage:	docker network COMMAND

Manage networks

Options:
      --help   Print usage

Commands:
  connect     Connect a container to a network
  create      Create a network
  disconnect  Disconnect a container from a network
  inspect     Display detailed information on one or more networks
  ls          List networks
  prune       Remove all unused networks
  rm          Remove one or more networks

Run 'docker network COMMAND --help' for more information on a command.
 



$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
63cd022391f8        bridge              bridge              local
02b313991b83        host                host                local
da6ae97c76f7        none                null                local

Network demo

$ docker network create demo
ef9352bc0f7000c83f4164b8f615b929efad44a2cc641ee8612012eaa9ebf317


$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
63cd022391f8        bridge              bridge              local
02b313991b83        host                host                local
da6ae97c76f7        none                null                local
ef9352bc0f70        demo                bridge              local


$ docker run --rm -d -ti --name hostA              php
$ docker run --rm -d -ti --name hostB  --net demo  php
$ docker run --rm -d -ti --name hostC  --net demo  php

$ docker exec hostA ping -c 1 hostB
ping: unknown host

$ docker exec hostB ping -c 1 hostC
PING hostC (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: icmp_seq=0 ttl=64 time=1.341 ms
--- hostC ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 1.341/1.341/1.341/0.000 ms

$ docker exec hostC ping -c 1 hostB
PING hostB (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: icmp_seq=0 ttl=64 time=0.340 ms
--- hostB ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.340/0.340/0.340/0.000 ms

Next ?

$ docker run -d --net demo --name web nginx:1-alpine

$ docker run -d --net demo --name php php:7.1-fpm-alpine

$ docker run -d --net demo --name es elasticsearch:5-alpine

$ docker run -d --net demo --name redis redis:4-alpine

$ docker run -d --net demo --name db mysql:8

$ docker run -d --net demo --name bf blackfire/blackfire

$ docker run -d --net demo --name mq rabbitmq:3-alpine

$ # ...

Docker Compose

Define and run

multi-container Docker

applications

Quick History

fig

Versions

nginx:
    image: nginx:1.13-alpine
    links:
        - symfony

symfony:
    build: ./docker
    volumes:
        - ./:/srv
    links:
        - pgsql

pgsql:
    image: postgres:10-alpine

v1

version: '2'

services:
    nginx:
        image: nginx:1.13-alpine

    symfony:
        build: ./docker
        volumes:
            - ./:/srv

    pgsql:
        image: postgres:10-alpine

v2

 

Docker Compose      Docker Engine              

3.3   17.06.0+
3.2   17.04.0+
3.1   1.13.1+
3.0   1.13.0+
2.3   17.06.0+
2.2   1.13.0+
2.1   1.12.0+
2.0   1.10.0+
1.0   1.9.1.+
version: '3.3'

services:
    nginx:
        image: nginx:1.13-alpine

    symfony:
        build: ./docker
        volumes:
            - ./:/srv

    pgsql:
        image: postgres:10-alpine

Real usecase

Base

$ composer create-project symfony/skeleton .
$ composer require orm-pack profiler-pack


$ tree -L 1 .
├── bin/
├── composer.json
├── composer.lock
├── config/
├── Makefile
├── public/
├── src/
├── templates/
├── var/
└── vendor/

7 directories, 3 files

docker-compose.yml

$ cat docker-compose.yml
version: '3'

services:

  web:
    image: nginx:1-alpine
    volumes:
      - ./:/srv:ro
      - ./docker/nginx/vhost.conf:/etc/nginx/conf.d/default.conf:ro

  php:
    image: mykiwi/symfony-db:7.1-fpm
    volumes:
      - ./:/srv

  db:
    image: mysql:5.7
    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD=true

nginx config

$ cat ./docker/nginx/vhost.conf
upstream phpfcgi {
    server php:9000;
}

server {
    listen 80;
    server_name localhost;
    root /srv/public;

    location / {
        try_files $uri @rewriteapp;
    }

    location @rewriteapp {
        rewrite ^(.*)$ /index.php/$1 last;
    }

    location ~ ^/index.php(/|$) {
        include fastcgi_params;
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

database config

$ git diff
diff --git a/.env.dist b/.env.dist
index 0d763d7..c08ace1 100644
--- a/.env.dist
+++ b/.env.dist
-DATABASE_URL="mysql://root@127.0.0.1:3306/symfony?charset=utf8mb4&serverVersion=5.7"
+DATABASE_URL="mysql://root@db:3306/symfony?charset=utf8mb4&serverVersion=5.7"


$ composer require console
$ docker-compose exec php bin/console doctrine:database:create
Created database `symfony` for connection named default

Demo

Recommandations

- Use official images in docker-compose

- Or build a new one using an official image

diff --git a/docker-compose.yml b/docker-compose.yml
index af3c122..4003e7c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ services:
       - php

   php:
-    image: mykiwi/symfony-db:7.1-fpm-alpine
+    build: ./docker/php
     working_dir: /srv
     volumes:
       - .:/srv
diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
new file mode 100644
index 0000000..ffb0859
--- /dev/null
+++ b/docker/php/Dockerfile
@@ -0,0 +1,34 @@
+FROM php:7.1-fpm-alpine
+
+ENV APCU_VERSION 5.1.8
+
+RUN apk add --update --no-cache \
+        icu-libs && \
+
+    apk add --no-cache --virtual .build-deps \
+        $PHPIZE_DEPS \
+        icu-dev && \
+
+    docker-php-ext-install \
+        intl \
+        pdo_mysql && \
+
+    pecl install apcu-${APCU_VERSION} && \
+    docker-php-ext-enable apcu && \
+
+    docker-php-ext-enable opcache && \
+
+    echo "short_open_tag = off" >> /usr/local/etc/php/php.ini && \
+    echo "date.timezone = Europe/Paris" >> /usr/local/etc/php/conf.d/symfony.ini && \
+    echo "opcache.max_accelerated_files = 20000" >> /usr/local/etc/php/conf.d/symfony.ini && \
+    echo "realpath_cache_size=4096K" >> /usr/local/etc/php/conf.d/symfony.ini && \
+    echo "realpath_cache_ttl=600" >> /usr/local/etc/php/conf.d/symfony.ini && \
+
+    apk del .build-deps
+
+ADD ./bin/* /usr/local/bin/
+
+ENV GOSU_VERSION 1.10
+RUN install-gosu.sh
+
+ENTRYPOINT ["entrypoint.sh"]

http:// ???

- container ip

- host:port

- domain name

http://172.19.0.4 (container ip)

$ # Nothing to do !
$ # Or a least, find the container ip

$ docker inspect \
    -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
    $(docker-compose ps -q web)
172.19.0.4

out of the box

 

 

ip ... not easy to type/remember

can change

will not works with VMs

http://localhost:8042

$ cat docker-compose.yml

version: '3'

services:

  web:
    image: nginx:1-alpine
    volumes:
      - .:/srv:ro
      - ./docker/nginx/vhost.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php
    ports:
      - "8042:80"

  php:
    build: ./docker/php
    working_dir: /srv
    volumes:
      - .:/srv

  db:
    image: mysql:5.7
    environment:
      - MYSQL_ALLOW_EMPTY_PASSWORD=true

http://localhost:8042

must be specified

out of the box

 

 

will not work if the port is already used

$ docker-compose up -d
tutodockercomposeexampleflex_db_1 is up-to-date
tutodockercomposeexampleflex_php_1 is up-to-date
tutodockercomposeexampleflex_web_1 is up-to-date

$ docker-compose -p project2 up -d
...
ERROR: for projet2_web_1 ...
Bind for 0.0.0.0:8042 failed: port is already allocated

http://my-app.docker (domain name)

- Server DNS

simple

container configuration optional

 

 

 

not out of the box

will not works with VMs

my-app.docker

foo.docker

bar.docker

container docker

172.42.0.2

container docker

172.42.2.4

container docker

172.42.6.2

- Reverse proxy

work everywhere

 

 

 

not out of the box

will use port 80/443

can require container configuration

 

my-app.docker

foo.docker

bar.docker

*.docker

}

Reverse Proxy

127.0.0.1

container docker

172.42.0.2

container docker

172.42.2.4

container docker

172.42.6.2

http://my-app.docker (domain name)

Reverse Proxy 1 jwilder/nginx-proxy

nginx-proxy sets up a container running nginx and docker-gen. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.

How to use it

# Setup (do it only once, then forget about it)

$ docker run -d \
    -p 80:80 \
    -v /var/run/docker.sock:/tmp/docker.sock:ro \
    --restart=always \
    --name proxy \
    jwilder/nginx-proxy

$ docker network create proxy
$ docker network connect proxy proxy

# Play with it

$ docker run -d -e VIRTUAL_HOST=my-app.docker jwilder/whoami
75001121bcc1b236a11c289805a154f85240f074959176f9c114e4296aa...

$ curl -H "Host:my-app.docker" localhost
I'm 75001121bcc1

How to use it

# Play with it with my docker-compose version 1

$ cat docker-compose.yml
whoami:
  image: jwilder/whoami
  environment:
    - VIRTUAL_HOST=my-app.docker

$ docker-compose up -d
Creating root_whoami_1 ... done

$ curl -H "Host:my-app.docker" localhost
I'm a8129256d434

How to use it

# Play with it with my docker-compose version >= 2
$ cat docker-compose.yml
version: '3'

services:
  whoami:
    image: jwilder/whoami
    environment:
      - VIRTUAL_HOST=my-app.docker

$ docker-compose up -d
Creating root_whoami_1 ... done

$ curl -H "Host:my-app.docker" localhost
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body bgcolor="white">
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.13.5</center>
</body>
</html>

How to use it with networks

# Debug the nginx proxy

$ docker exec proxy cat /etc/nginx/conf.d/default.conf
...

upstream my-app.docker {
}

...

How to use it

$ cat docker-compose.yml
version: '3'

services:
  whoami:
    image: jwilder/whoami
    environment:
      - VIRTUAL_HOST=my-app.docker
    networks:
      - default
      - proxy

networks:
  proxy:
    external:
      name: proxy

How to use it

# Debug the nginx proxy

$ docker exec proxy cat /etc/nginx/conf.d/default.conf
...
upstream my-app.docker {
                                ## Can be connect with "proxy" network
                        # root_whoami_1
                        server 172.19.0.3:8000;
}


$ curl -H "Host:my-app.docker" localhost
I'm d76e533d0e19

Best practices

$ cat docker-compose.yml
version: '3'

services:
  whoami:
    image: jwilder/whoami

$ cat docker-compose.override.yml
version: '3'

services:
  whoami
    environment:
      - VIRTUAL_HOST=my-app.docker
    networks:
      - default
      - proxy

networks:
  proxy:
    external:
      name: proxy

don't force user by using/having this network

 

propose a docker-compose.override.yml.dist file as example and let the user adapt it for its usage

$ alias debug-proxy='docker exec proxy cat /etc/nginx/conf.d/default.conf'

It's just nginx so it's easy to debug

Reverse Proxy 2 Traefik

How to start Traefik

# From binary

$ sudo traefik \
    --web \
    --docker \
    --docker.domain=docker \
    --configfile=/dev/null
# From docker

$ cat docker-compose.yml
version: '3'

networks:
  default:
    external:
      name: proxy

services:
  traefik:
    image: traefik
    command: --web --docker --docker.domain=docker
    container_name: traefik
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /dev/null:/traefik.toml
    labels:
      - "traefik.frontend.rule=Host:dashboard.docker"
      - "traefik.port=8080"
    restart: always

How to use it

# From binary

$ cat docker-compose.yml
version: '3'

services:
  whoami:
    image: jwilder/whoami

# From docker

$ cat docker-compose.yml
version: '3'

services:
  whoami:
    image: jwilder/whoami

networks:
  default:
    external:
      name: proxy

Traefik's cool features

Let's encrypt integration (https for production)

Powerful rules (sub domain, path, ...)

Web Dashboard (RESTful API, monitoring)

http://my-app.docker/

dnsmasq

Permissions

$ docker run --rm -ti \
    --volume=`pwd`:/srv \
    --workdir=/srv \
    alpine \
    sh

/srv # touch test
/srv # ls -la
total 4
drwxr-xr-x    2 1000     1000            60 Nov  6 12:55 .
drwxr-xr-x   25 root     root          4096 Nov  6 12:55 ..
-rw-r--r--    1 root     root             0 Nov  6 12:55 test

/srv # exit
$ ls -la
total 0
drwxr-xr-x  2 mykiwi  60 Nov  6 13:55 ./
drwxrwxrwt 19 root   640 Nov  6 13:55 ../
-rw-r--r--  1 root     0 Nov  6 13:55 test

Fix volume permissions

$ docker run --help
Usage:	docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

Run a command in a new container

Options:
  ...

  -u, --user string      Username or UID (format: <name|uid>[:<group|gid>])

  ...


$ docker run --rm -ti \
    --volume=`pwd`:/srv \
    --workdir=/srv \
    --user=`id --user`:`id --group` \ # 1000:1000
    alpine \
    touch test

$ ls -la
total 0
drwxr-xr-x  2 mykiwi  60 Nov  7 13:41 ./
drwxrwxrwt 23 root   840 Nov  7 13:41 ../
-rw-r--r--  1 mykiwi   0 Nov  7 13:41 test

Fix volume permissions

$ cat docker-compose.yml
version: '3'
services:
  app:
    image: alpine
    volumes:
      - .:/srv
    working_dir: /srv
    user: "1000:1000"
    command: touch test
$ docker-compose up
Creating network "testdocker_default" with the default driver
Creating testdocker_app_1 ... 
Creating testdocker_app_1 ... done
Attaching to testdocker_app_1
testdocker_app_1 exited with code 0
$ ls -la
total 4
drwxr-xr-x  2 mykiwi  80 Nov  7 13:50 ./
drwxrwxrwt 23 root   840 Nov  7 13:41 ../
-rw-r--r--  1 mykiwi 124 Nov  7 13:50 docker-compose.yml
-rw-r--r--  1 mykiwi   0 Nov  7 13:50 test

Fix volume permissions

out of the box (on all images)

 

 

 

can't set dynamic uid/gid with docker-compose.yml easily

gosu

This is a simple tool grown out of the simple fact that su and sudo have very strange and often annoying TTY and signal-forwarding behavior. They're also somewhat complex to setup and use (especially in the case of sudo), which allows for a great deal of expressivity, but falls flat if all you need is "run this specific application as this specific user and get out of the pipeline".

$ gosu

Usage: ./gosu user-spec command [args]
   ie: ./gosu tianon bash
       ./gosu nobody:root bash -c 'whoami && id'
       ./gosu 1000:1 id

Why ?

Simple Go-based setuid+setgid+setgroups+exec

Dockerfile with gosu

FROM debian

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        wget && \
    rm -rf /var/lib/apt/lists/* && \
    wget -O /bin/gosu "https://github.com/tianon/gosu/releases/download/1.10/gosu-$(dpkg --print-architecture)" && \
    chmod +x /bin/gosu && \
    apt-get purge -y --auto-remove ca-certificates wget && \
    addgroup bar && \
    adduser --home=/home --shell=/bin/bash --ingroup=bar --disabled-password --quiet foo

ADD entrypoint.sh /bin/entrypoint
ENTRYPOINT ["entrypoint"]

Dockerfile with su-exec

FROM alpine

RUN apk add --no-cache su-exec && \
    addgroup bar && \
    adduser -D -h /home -s /bin/sh -G bar foo

ADD entrypoint.sh /bin/entrypoint
ENTRYPOINT ["entrypoint"]

Entrypoint

1. Detect permissions of the main volume

2. Create/adapt a user with the good uid/gid

3. Play with it

Entrypoint for gosu + debian

#!/bin/bash
set -e

uid=$(stat -c %u /srv)
gid=$(stat -c %g /srv)

if [ $uid == 0 ] && [ $gid == 0 ]; then
    if [ $# -eq 0 ]; then
        sh
    else
        exec "$@"
    fi
fi

sed -i -r "s/foo:x:\d+:\d+:/foo:x:$uid:$gid:/g" /etc/passwd
sed -i -r "s/bar:x:\d+:/bar:x:$gid:/g" /etc/group
chown $uid:$gid /home

user=$(grep ":x:$uid:" /etc/passwd | cut -d: -f1)
if [ $# -eq 0 ]; then
    exec gosu $user bash
else
    exec gosu $user "$@"
fi

Entrypoint for su-exec + alpine

#!/bin/sh
set -e

uid=$(stat -c %u /srv)
gid=$(stat -c %g /srv)

if [ $uid == 0 ] && [ $gid == 0 ]; then
    if [ $# -eq 0 ]; then
        sh
    else
        exec "$@"
    fi
fi

sed -i -r "s/foo:x:\d+:\d+:/foo:x:$uid:$gid:/g" /etc/passwd
sed -i -r "s/bar:x:\d+:/bar:x:$gid:/g" /etc/group

user=$(grep ":x:$uid:" /etc/passwd | cut -d: -f1)
if [ $# -eq 0 ]; then
    exec su-exec $user sh
else
    exec su-exec $user "$@"
fi

Demo

Keep practicing and learning

Docker

By MyKiwi

Docker

Updated version

  • 215