How Docker works ?
Go watch @jderusse's midi formation
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
{
blackfire/blackfire
=== blackfire/blackfire:latest
blackfire/blackfire:1.12.0
mykiwi/symfony-base:7.1-fpm-alpine
/
/
/
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
https://github.com/docker/docker-registry (v1 - DEPRECATED)
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
$ docker ps -a
CONTAINER ID ... STATUS
8993da501dff ... Exited (0) 3 seconds ago
0fa641af2a08 ... Up 3 hours
Tips:
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
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
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