07/2015
Michaël Garrez
Docker is an open-source project that automates the deployment of applications inside software containers, by providing an additional layer of abstraction and automation of operating-system-level virtualization on Linux.
Docker is based on LXC (Linux Containers) which allows
it to isolate containers from each other.
LXC use mainly two Linux Kernel features to acheave it :
Docker can be split in multiple different parts :
Docker can be used natively on Linux (and only Linux) by the simple bash command :
wget -qO- https://get.docker.com/ | sh
Docker installation requires at least 3.13 Linux Kernel.
Docker uses boot2docker to create a VM with a Tiny Core Linux version including Docker.
Docker now offers Docker Toolbox which contains
Docker-machine (which still uses boot2docker).
It allows to create multiple docker machines (multiple VMs).
Image
An image is an inert snapshot of a container.
When runned (with a run command) it becomes a container.
As images can be quite heavy they are designed to be composed of layers of other images which allows to minimize an image weight.
Images are stored on Docker Hub.
Image
An image is built from a Dockerfile. A file describing how the image is supposed to behave, what it extends from, ...
docker images command lists all local images :
Container
Programmatically speaking, if an image what a class, then a container would be an instance of this class.
You may launch multiple containers for the same image.
docker ps command lists all running containers :
$ docker run -itd ubuntu:14.04
To create a container from the official ubuntu image :
To enter into a container with your terminal :
$ docker exec -it CONTAINERID bash
To check your running containers and their ids :
$ docker ps
As said before :
An image is a snapshot of a container
Let's take a snapshot of our container :
$ docker commit -m "First commit" CONTAINERID babacooll/myfirstimage
You can now create as many container from this image as you want :
$ docker run -dit babacooll/myfirstimage
-d : Daemonized
-it : Allocate TTY
Image name
This approach is nice but as developers
we highly prefer working in files than terminals.
That's why Docker uses Dockerfiles.
A Dockerfile translation of our current image would be :
FROM ubuntu
MAINTAINER Michael Garrez <michael.garrez@gmail.com>
ENV REFRESHED_AT 2015-05-11
RUN mkdir -p /var/www
To build an image from this Dockerfile :
$ docker build -t babacooll/mysecondimage .
To create a container from our new image :
$ docker run -dit babacooll/mysecondimage
-d : Daemonized
-it : Allocate TTY
Image name
# use the latest Ubuntu base image
# adapted from https://registry.hub.docker.com/u/coderstephen/php7 for additional needs
FROM ubuntu:14.04
MAINTAINER Michael Garrez <michael.garrez@gmail.com>
# install packages for Apache and for compiling PHP
RUN apt-get update && apt-get install -y \
apache2-mpm-prefork \
apache2-prefork-dev \
aufs-tools \
automake \
bison \
btrfs-tools \
build-essential \
curl \
git \
libbz2-dev \
libcurl4-openssl-dev \
libmcrypt-dev \
libxml2-dev \
re2c
# get the latest PHP source from master branch
RUN git clone --depth=1 https://github.com/php/php-src.git /usr/local/src/php
# we're going to be working out of the PHP src directory for the compile steps
WORKDIR /usr/local/src/php
ENV PHP_DIR /usr/local/php
# configure the build
RUN ./buildconf && ./configure \
--prefix=$PHP_DIR \
--with-config-file-path=$PHP_DIR \
--with-config-file-scan-dir=$PHP_DIR/conf.d \
--with-apxs2=/usr/bin/apxs2 \
--with-libdir=/lib/x86_64-linux-gnu \
--enable-bcmath \
--with-bz2 \
--enable-calendar \
--with-curl \
--enable-exif \
--enable-ftp \
--with-ldap \
--enable-mbstring \
--enable-mbregex \
--with-mcrypt \
--with-mysqli=mysqlnd \
--with-openssl \
--enable-pcntl \
--without-pear \
--enable-pdo \
--with-pdo-mysql=mysqlnd \
--enable-sockets \
--with-zip \
--with-zlib
# compile and install
RUN make && make install
ENV PATH=$PATH:/usr/local/php/bin
# set up Apache environment variables
ENV APACHE_RUN_USER=www-data \
APACHE_RUN_GROUP=www-data \
APACHE_LOG_DIR=/var/log/apache2 \
APACHE_LOCK_DIR=/var/lock/apache2 \
APACHE_PID_FILE=/var/run/apache2.pid
# Remove default site
RUN rm -f sites-enabled/000-default.conf
# Enable additional configs and mods
ENV HTTPD_PREFIX /etc/apache2
RUN a2dismod mpm_event && a2enmod mpm_prefork
RUN ln -s $HTTPD_PREFIX/mods-available/expires.load $HTTPD_PREFIX/mods-enabled/expires.load \
&& ln -s $HTTPD_PREFIX/mods-available/headers.load $HTTPD_PREFIX/mods-enabled/headers.load \
&& ln -s $HTTPD_PREFIX/mods-available/rewrite.load $HTTPD_PREFIX/mods-enabled/rewrite.load
RUN usermod -u 1000 www-data
ENV TERM xterm
EXPOSE 80
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND
$ docker build -t babacooll/apachephp7 .
Image name
Dockerfile directory
$ docker run -dit babacooll/apachephp7
-d : Daemonized
-it : Allocate TTY
Image name
To know the IP of Docker Host :
D.M. Name
D.M. IP
To open a port on the Container :
$ docker run -dit -p 80:80 babacooll/apachephp7
Solutions ?
Let's build a new Image babacooll/apachephp7project
extending babacooll/apachephp7
Dockerfile
FROM babacooll/apachephp7
MAINTAINER LFT <lft@quanta.lu>
ENV REFRESHED_AT 2015-05-11
COPY vhost.conf /etc/apache2/sites-enabled/
RUN mkdir -p /var/www/babacooll
VOLUME ["/var/www/babacooll"]
COPY application/ /var/www/babacooll
WORKDIR /var/www/babacooll
EXPOSE 80
CMD ["apache2ctl", "-D", "FOREGROUND"]
Vhost
<VirtualHost *:80>
ServerName babacooll.dev
DocumentRoot /var/www/babacooll
#RewriteEngine On
DirectoryIndex index.php
<Directory /var/www/babacooll>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
LogLevel info
ErrorLog /var/log/apache2/myapp-error.log
CustomLog /var/log/apache2/myapp-access.log combined
</VirtualHost>
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
Let's build our image :
$ docker build -t babacooll/apachephp7project .
Let's run a container from this image :
$ docker run -dit -p 80:80 babacooll/apachephp7project
Our image contains now :
Now let's change our index.php file
to display phpinfo() :
<?php
phpinfo();
As babacooll/apachephp7project is an inert image of Apache + PHP7 + Our project, a modification in my local files CAN'T affect this image.
As our container is a living babacooll/apachephp7project,
a modification in my local files CAN'T affect this container.
A solution would be to update files directly in the container but it would only update the current container, not all containers related to
babacooll/apachephp7project.
Another (better) solution is to mount dynamically
a volume in the container :
$ docker run \
-dit \
-p 80:80 \
-v //c/Users/Workspace/DockerPres/ApachePHP7Project/application:/var/www/babacooll \
babacooll/apachephp7project
Local folder to mount from
Container folder to mount on
Let's use the official mariadb repository
$ docker pull mariadb
To run a Container with MariaDB :
$ docker run \
--name mymariadb \
-e MYSQL_ROOT_PASSWORD=mypassword \
-e MYSQL_DATABASE=babacooll \
-d \
mariadb
That's all for the MariaDB container !
Let's link our application to MariaDB !
<?php
try {
$dbh = new PDO('mysql:host=mariadbcontainer;dbname=babacooll', 'root', 'mypassword');
} catch (PDOException $e) {
print "Erreur !: " . $e->getMessage() . "<br/>";
die();
}
Our PHP container has no knowledge of the MariaDB container.
Let's fix that !
$ docker run \
-dit \
-p 80:80 \
--link mymariadb:mariadbcontainer \
-v //c/Users/Workspace/DockerPres/ApachePHP7Project/application:/var/www/babacooll \
babacooll/apachephp7project
Docker Hub contains a lot of images for various purposes.
Docker Hub provides a free storage facility for your public images and a paying storage facility for your private images (GitHub style).
$ docker push babacooll/apachephp7project
$ docker pull babacooll/apachephp7project
Docker Hub allows you to link a GitHub repository to a Docker Hub repository.
Your GitHub repository should contain a Dockerfile on the root directory.
Each time you push on GitHub, Docker Hub (through webhooks) will pull your repository and build an image into your Docker Hub repository.
Running all containers by hand can
be really messy and complicated.
But Docker has a solution for us :
Docker Compose
For the moment our simple application requires us to do those commands :
$ docker run \
--name mymariadb \
-e MYSQL_ROOT_PASSWORD=mypassword \
-e MYSQL_DATABASE=babacooll \
-d \
mariadb
$ docker run \
-dit \
-p 80:80 \
--link mymariadb:mariadbcontainer \
-v //c/Users/Workspace/DockerPres/ApachePHP7Project/application:/var/www/babacooll \
babacooll/apachephp7project
Solutions ?
No.
The solution is Docker compose !
Docker compose works by putting a single yaml file in your project.
For example this is the corresponding docker-compose.yml file for our project :
web:
image: babacooll/apachephp7project
ports:
- "80:80"
volumes:
- //c/Users/Workspace/DockerPres/ApachePHP7Project/application:/var/www/babacooll
links:
- mariadb:mariadbcontainer
mariadb:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: mypassword
MYSQL_DATABASE: babacooll
Our project can now be started by a simple command :
$ docker-compose up -d
Docker compose allows you to multiply a container by using the scale command :
$ docker-compose scale mariadb=5
To allow links to properly function you have to stop and remove your web container and relaunch it :
$ docker-compose stop web
$ docker-compose rm web
$ docker-compose run -d web
How do you organize a stack composed of multiple projects implementing docker?
Example
We decided to split our future application into multiple micro-services. Docker seems a good way to run those micro-services in a "iso"-prod way.
But we faced a problem: how do you connect all those micro-services together ?
At this point (just beginning) we have 7 repositories providing 25 containers.
Links between containers in the same repository is easily defined (through a docker-compose.yml file) but what about trans-repositories links ?
Docker-compose allows you to connect containers together. Example for our member zone project :
memberzoneweb:
image: babacooll/apache-php7
environment:
SERVICE_HOST: memberzone.local
ports:
- "80:80"
links:
- memberzonemariadb
volumes_from:
- memberzonedatacontainer
working_dir: /var/www/lancastersMemberZone
memberzonemariadb:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: memberzone
memberzonedatacontainer:
image: debian:jessie
volumes:
- ./:/var/www/lancastersMemberZone
- ./vhost:/etc/apache2/sites-enabled
command: tail -f /dev/null
memberzonephpmyadmin:
image: nazarpc/phpmyadmin
ports:
- "1234:80"
links:
- memberzonemariadb:mysql
We decided to use the concept of data-only containers pattern to mount our data.
This patterns respects the single responsibility principle and eases the scalability of your applications.
All our projects has the same organization :
How to provide an easy way to launch our stack without having to understand all its projects ?
We decided to create a Stack Assembler PHP project which following goals :
The Stack Assembler contains two config files :
blinddrawer:
ssh: git@github.com:quantafuture/lancastersBlindDrawer.git
branch: v2.0.0
tag: ~
event:
ssh: git@github.com:quantafuture/lancastersEvent.git
branch: master
tag: ~
guard:
ssh: git@github.com:quantafuture/lancastersGuard.git
branch: master
tag: ~
redis:
ssh: git@github.com:quantafuture/lancastersRedis.git
branch: master
tag: ~
memberzone:
ssh: git@github.com:quantafuture/lancastersMemberZone.git
branch: master
tag: ~
backoffice:
ssh: git@github.com:quantafuture/lancastersBackOffice.git
branch: master
tag: ~
payment:
ssh: git@github.com:quantafuture/lancastersPayment.git
branch: master
tag: ~
Contains all the projects of the stack with a specific branch or tag defined.
The Stack Assembler contains two config files :
blinddrawerweb:
guardweb: guard.local
globalredis: redis.local
eventweb:
guardweb: guard.local
globalredis: redis.local
guardweb:
globalredis: redis.local
memberzoneweb:
guardweb: guard.local
globalredis: redis.local
paymentweb: payment.local
backofficeweb:
memberzoneweb: memberzone.local
globalredis: redis.local
guardweb: guard.local
paymentweb: payment.local
paymentweb:
blinddrawerweb: blinddrawer.local
guardweb: guard.local
eventweb: event.local
Adds specific links between containers from different projects.
The Stack Assembler uses multiple tools to interact with the Docker galaxy :
Due to a bug with OpenSSL occurring on Yosemite we use also sequenceiq/docker-socat to allow http connection to a docker-machine.
blinddrawerweb:
image: babacooll/apache-php7
ports:
- '80:80'
volumes_from:
- blinddrawerdatacontainer
environment:
SERVICE_HOST: blinddrawer.local
links:
- blinddrawermariadb
- 'guardweb:guard.local'
- 'globalredis:redis.local'
working_dir: /var/www/lancastersBlindDrawer
blinddrawermariadb:
image: 'mariadb:10'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: blinddrawer
blinddrawerdatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersBlindDrawer/:/var/www/lancastersBlindDrawer:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersBlindDrawer/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
blinddrawerphpmyadmin:
image: nazarpc/phpmyadmin
ports:
- '1234:80'
links:
- 'blinddrawermariadb:mysql'
eventweb:
image: babacooll/apache-php7
ports:
- '81:80'
volumes_from:
- eventdatacontainer
environment:
SERVICE_HOST: event.local
links:
- eventbeanstalkd
- 'guardweb:guard.local'
- 'globalredis:redis.local'
eventbeanstalkd:
image: schickling/beanstalkd
eventdatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersEvent/:/var/www/lancastersEvent:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersEvent/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
guardweb:
image: babacooll/apache-php7
ports:
- '82:80'
volumes_from:
- guarddatacontainer
environment:
SERVICE_HOST: guard.local
links:
- guardmariadb
- 'globalredis:redis.local'
working_dir: /var/www/lancastersGuard
guardmariadb:
image: 'mariadb:10'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: guard
guarddatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersGuard/:/var/www/lancastersGuard:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersGuard/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
guardphpmyadmin:
image: nazarpc/phpmyadmin
ports:
- '1235:80'
links:
- 'guardmariadb:mysql'
globalredis:
image: redis
ports:
- '6379:6379'
volumes_from:
- globaldatacontainer
command: 'redis-server /usr/local/etc/redis/redis.conf'
globaldatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersRedis/conf:/usr/local/etc/redis:rw'
command: 'tail -f /dev/null'
memberzoneweb:
image: babacooll/apache-php7
ports:
- '83:80'
volumes_from:
- memberzonedatacontainer
environment:
SERVICE_HOST: memberzone.local
links:
- memberzonemariadb
- 'guardweb:guard.local'
- 'globalredis:redis.local'
- 'paymentweb:payment.local'
working_dir: /var/www/lancastersMemberZone
memberzonemariadb:
image: 'mariadb:10'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: memberzone
memberzonedatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersMemberZone/:/var/www/lancastersMemberZone:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersMemberZone/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
memberzonephpmyadmin:
image: nazarpc/phpmyadmin
ports:
- '1236:80'
links:
- 'memberzonemariadb:mysql'
backofficeweb:
image: babacooll/apache-php7
ports:
- '84:80'
volumes_from:
- backofficedatacontainer
environment:
SERVICE_HOST: backoffice.local
links:
- backofficemariadb
- 'memberzoneweb:memberzone.local'
- 'globalredis:redis.local'
- 'guardweb:guard.local'
- 'paymentweb:payment.local'
working_dir: /var/www/lancastersBackOffice
backofficemariadb:
image: 'mariadb:10'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: backoffice
backofficedatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersBackOffice/:/var/www/lancastersBackOffice:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersBackOffice/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
backofficephpmyadmin:
image: nazarpc/phpmyadmin
ports:
- '1237:80'
links:
- 'backofficemariadb:mysql'
paymentweb:
image: babacooll/apache-php7
ports:
- '85:80'
volumes_from:
- paymentdatacontainer
environment:
SERVICE_HOST: payment.local
links:
- paymentmariadb
- 'blinddrawerweb:blinddrawer.local'
- 'guardweb:guard.local'
- 'eventweb:event.local'
working_dir: /var/www/lancastersPayment
paymentmariadb:
image: 'mariadb:10'
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: payment
paymentdatacontainer:
image: 'debian:jessie'
volumes:
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersPayment/:/var/www/lancastersPayment:rw'
- '/Users/michaelgarrez/Docker/Projects/V2Stack/lancastersStackAssembler/app/../../lancastersPayment/vhost:/etc/apache2/sites-enabled:rw'
command: 'tail -f /dev/null'
paymentphpmyadmin:
image: nazarpc/phpmyadmin
ports:
- '1238:80'
links:
- 'paymentmariadb:mysql'