Everybody is talking about Docker nowadays. What it is about? Do you remember Solaris Zones or Containers? It is more or less the same although development of Docker during the last years made Linux Containers the de-facto standard for deploying applications in a standardized and isolated way. Docker is build in a classical client server model: There is the docker server (or daemon) which servers the requests of docker clients. The client is the one you’ll use to tell the server what you want to do. The main difference from the classical client/server model is that docker uses the same binary for the server as well as for the client. It is just a matter of how you invoke the docker binary that makes it a server or client application. In contrast to the Solaris Zones Docker containers are stateless by default, that means: When you shutdown a docker container you’ll lose everything that was done when the container started to what happened when container got destroyed (Although there are ways to avoid that). This is important to remember.

When you start a docker container on a host the host’s resources are shared with the container (Although you can limit that). It is not like when you fire up a virtual machine (which brings up an instance of a whole operating system) but more like a process that shares resources with the host it is running on. This might be as simple as running a “wget” command but it might be as complicated as bringing up a whole infrastructure that serves your service desk. Docker containers should be lightweight.

So what does make docker unique then? It is the concept of a layered filesystem. We’ll come to that soon. Lets start by installing everything we need to run a docker daemon. As always we’ll start with as CentOS 7 minimal installation:

[root@centos7 ~]$ cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core) 
[root@centos7 ~]$ 

The easiest way to get docker installed is to add the official docker yum repository (for CentOS in this case):

[root@centos7 ~]$ echo "[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg" > /etc/yum.repos.d/docker.repo

Working directly as root never is a good idea so lets create a user for that and let this user do everything via sudo ( not a good practice, I know 🙂 ):

[root@centos7 ~]$ groupadd docker
[root@centos7 ~]$ useradd -g docker docker
[root@centos7 ~]$ passwd docker
[root@centos7 ~]$ echo "docker ALL=(ALL)   NOPASSWD: ALL" >> /etc/sudoers
[root@centos7 ~]$ su - docker
[docker@centos7 ~]$ sudo ls

Ready to install:

[docker@centos7 ~]$ sudo yum install docker-engine

This will install the docker engine and these additional packages:

======================================================================================================================================
 Package                                Arch                   Version                               Repository                  Size
======================================================================================================================================
Installing:
 docker-engine                          x86_64                 1.12.3-1.el7.centos                   dockerrepo                  19 M
Installing for dependencies:
 audit-libs-python                      x86_64                 2.4.1-5.el7                           base                        69 k
 checkpolicy                            x86_64                 2.1.12-6.el7                          base                       247 k
 docker-engine-selinux                  noarch                 1.12.3-1.el7.centos                   dockerrepo                  28 k
 libcgroup                              x86_64                 0.41-8.el7                            base                        64 k
 libseccomp                             x86_64                 2.2.1-1.el7                           base                        49 k
 libsemanage-python                     x86_64                 2.1.10-18.el7                         base                        94 k
 libtool-ltdl                           x86_64                 2.4.2-21.el7_2                        updates                     49 k
 policycoreutils-python                 x86_64                 2.2.5-20.el7                          base                       435 k
 python-IPy                             noarch                 0.75-6.el7                            base                        32 k
 setools-libs                           x86_64                 3.3.7-46.el7                          base                       485 k

Transaction Summary
======================================================================================================================================

Enable the service:

[docker@centos7 ~]$ sudo systemctl enable docker.service
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

Start the service:

[docker@centos7 ~]$ sudo systemctl start docker
[docker@centos7 ~]$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2016-12-10 12:26:46 CET; 6s ago
     Docs: https://docs.docker.com
 Main PID: 2957 (dockerd)
   Memory: 12.9M
   CGroup: /system.slice/docker.service
           ├─2957 /usr/bin/dockerd
           └─2960 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --...

Dec 10 12:26:45 centos7.local dockerd[2957]: time="2016-12-10T12:26:45.481380483+01:00" level=info msg="Graph migration to co...conds"
Dec 10 12:26:45 centos7.local dockerd[2957]: time="2016-12-10T12:26:45.481751429+01:00" level=warning msg="mountpoint for pid...found"
Dec 10 12:26:45 centos7.local dockerd[2957]: time="2016-12-10T12:26:45.481751451+01:00" level=info msg="Loading containers: start."
Dec 10 12:26:45 centos7.local dockerd[2957]: time="2016-12-10T12:26:45.574330143+01:00" level=info msg="Firewalld running: false"
Dec 10 12:26:45 centos7.local dockerd[2957]: time="2016-12-10T12:26:45.822997195+01:00" level=info msg="Default bridge (docke...dress"
Dec 10 12:26:46 centos7.local dockerd[2957]: time="2016-12-10T12:26:46.201798804+01:00" level=info msg="Loading containers: done."
Dec 10 12:26:46 centos7.local dockerd[2957]: time="2016-12-10T12:26:46.201984648+01:00" level=info msg="Daemon has completed ...ation"
Dec 10 12:26:46 centos7.local dockerd[2957]: time="2016-12-10T12:26:46.202003760+01:00" level=info msg="Docker daemon" commit...1.12.3
Dec 10 12:26:46 centos7.local dockerd[2957]: time="2016-12-10T12:26:46.207416263+01:00" level=info msg="API listen on /var/ru....sock"
Dec 10 12:26:46 centos7.local systemd[1]: Started Docker Application Container Engine.
Hint: Some lines were ellipsized, use -l to show in full.

And we’re done. Lets check if docker is working as expected:

[docker@centos7 ~]$ sudo docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c04b14da8d14: Pull complete 
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
 https://hub.docker.com

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

What happened here is that we already executed our first docker image: “hello-world”. The “–rm” flag tells docker to automatically remove the image once it exits. As the image was not available on our host it was automatically downloaded from the docker hub:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c04b14da8d14: Pull complete 
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for hello-world:latest

You can browse the docker hub for many, many other images using your favorite browser or you can use the command line:

[docker@centos7 ~]$ docker search postgres
NAME                      DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
postgres                  The PostgreSQL object-relational database ...   2939                 [OK]       
kiasaki/alpine-postgres   PostgreSQL docker image based on Alpine Linux   28                   [OK]
abevoelker/postgres       Postgres 9.3 + WAL-E + PL/V8 and PL/Python...   10                   [OK]
onjin/alpine-postgres     PostgreSQL / v9.1 - v9.6 / size <  50MB      9                    [OK]
macadmins/postgres        Postgres that accepts remote connections b...   8                    [OK]
jamesbrink/postgres       Highly configurable PostgreSQL container.       5                    [OK]
eeacms/postgres           Docker image for PostgreSQL (RelStorage re...   4                    [OK]
cptactionhank/postgres                                                    4                    [OK]
azukiapp/postgres         Docker image to run PostgreSQL by Azuki - ...   2                    [OK]
kampka/postgres           A postgresql image build on top of an arch...   2                    [OK]
clkao/postgres-plv8       Docker image for running PLV8 1.4 on Postg...   2                    [OK]
2020ip/postgres           Docker image for PostgreSQL with PLV8           1                    [OK]
steenzout/postgres        Steenzout's docker image packaging for Pos.1                    [OK]
blacklabelops/postgres    Postgres Image for Atlassian Applications       1                    [OK]
buker/postgres            postgres                                        0                    [OK]
kobotoolbox/postgres      Postgres image for KoBo Toolbox.                0                    [OK]
vrtsystems/postgres       PostgreSQL image with added init hooks, bu...   0                    [OK]
timbira/postgres          Postgres  containers                            0                    [OK]
coreroller/postgres       official postgres:9.4 image but it adds 2 ...   0                    [OK]
livingdocs/postgres       Postgres v9.3 with the plv8 extension inst...   0                    [OK]
1maa/postgres             PostgreSQL base image                           0                    [OK]
opencog/postgres          This is a configured postgres database for...   0                    [OK]
khipu/postgres            postgres with custom uids                       0                    [OK]
travix/postgres           A container to run the PostgreSQL database.     0                    [OK]
beorc/postgres            Ubuntu-based PostgreSQL server                  0                    [OK]

The first one is the official PostgreSQL image. How do I run it?

[docker@centos7 ~]$ docker run -it postgres
Unable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
386a066cd84a: Pull complete 
e6dd80b38d38: Pull complete 
9cd706823821: Pull complete 
40c17ac202a9: Pull complete 
7380b383ba3d: Pull complete 
538e418b46ce: Pull complete 
c3b9d41b7758: Pull complete 
dd4f9522dd30: Pull complete 
920e548f9635: Pull complete 
628af7ef2ee5: Pull complete 
004275e6f5b5: Pull complete 
Digest: sha256:e761829c4b5ec27a0798a867e5929049f4cbf243a364c81cad07e4b7ac2df3f1
Status: Downloaded newer image for postgres:latest
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    pg_ctl -D /var/lib/postgresql/data -l logfile start

****************************************************
WARNING: No password has been set for the database.
         This will allow anyone with access to the
         Postgres port to access your database. In
         Docker's default configuration, this is
         effectively any other container on the same
         system.

         Use "-e POSTGRES_PASSWORD=password" to set
         it in "docker run".
****************************************************
waiting for server to start....LOG:  database system was shut down at 2016-12-10 11:42:01 UTC
LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started
 done
server started
ALTER ROLE


/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*

waiting for server to shut down....LOG:  received fast shutdown request
LOG:  aborting any active transactions
LOG:  autovacuum launcher shutting down
LOG:  shutting down
LOG:  database system is shut down
 done
server stopped

PostgreSQL init process complete; ready for start up.

LOG:  database system was shut down at 2016-12-10 11:42:04 UTC
LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started

And ready. As with the “hello:world” image docker had to download the image as it was not available locally. Once that was done the image was started and new PostgreSQL instance was created automatically. Here you can see what the layered filesystem is about:

386a066cd84a: Pull complete 
e6dd80b38d38: Pull complete 
9cd706823821: Pull complete 
40c17ac202a9: Pull complete 
7380b383ba3d: Pull complete 
538e418b46ce: Pull complete 
c3b9d41b7758: Pull complete 
dd4f9522dd30: Pull complete 
920e548f9635: Pull complete 
628af7ef2ee5: Pull complete 
004275e6f5b5: Pull complete 

Each of this lines represents a layered/stacked filesystem on top of the previous one. This is an important concept because when you change things only the layer that contains the change needs to be rebuild, but not the layers below. In other words you could build an image based on a CentOS 7 image and then deploy your changes on top of that. You deliver that image and some time later you need to make some modifications: The only thing you need to deliver are the modifications you did because the layers below did not change.

You will notice that you cannot type any command when the image was started. As soon as you enter “CRTL-C” the container will shutdown (this is because of the “-it” switch, which is “interactive” and “pseudo terminal”):

^CLOG:  received fast shutdown request
LOG:  aborting any active transactions
LOG:  autovacuum launcher shutting down
LOG:  shutting down
LOG:  database system is shut down

Everything what happened inside the container is now gone. The correct way to launch it is:

[docker@centos7 ~]$ docker run --name my-first-postgres -e POSTGRES_PASSWORD=postgres -d postgres
d51abc52108d3040817474fa8c85ab15020c12cb753515543c2d064143277155

The “-d” switch tells docker to detach, so we get back our shell. The magic string dockers returns is the container id:

[docker@centos7 ~]$ docker ps --no-trunc
CONTAINER ID                                                       IMAGE               COMMAND                            CREATED             STATUS              PORTS               NAMES
d51abc52108d3040817474fa8c85ab15020c12cb753515543c2d064143277155   postgres            "/docker-entrypoint.sh postgres"   3 minutes ago       Up 3 minutes        5432/tcp            my-first-postgres

When you want to know what images you have available locally you can ask docker for that:

[docker@centos7 ~]$ docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
postgres            latest              78e3985acac0        2 days ago          264.7 MB
hello-world         latest              c54a2cc56cbb        5 months ago        1.848 kB

How do you now connect to the PostgreSQL image?

[docker@centos7 ~]$ docker run -it --rm --link my-first-postgres:postgres postgres psql -h postgres -U postgres
Password for user postgres: 
psql (9.6.1)
Type "help" for help.

postgres=# l+
                                                                   List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   |  Size   | Tablespace |                Description                 
-----------+----------+----------+------------+------------+-----------------------+---------+------------+--------------------------------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 7063 kB | pg_default | default administrative connection database
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 6953 kB | pg_default | unmodifiable empty database
           |          |          |            |            | postgres=CTc/postgres |         |            | 
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 6953 kB | pg_default | default template for new databases
           |          |          |            |            | postgres=CTc/postgres |         |            | 
(3 rows)

Or to get bash:

[docker@centos7 ~]$ docker run -it --rm --link my-first-postgres:postgres postgres bash
root@f8c3b3738336:/$ cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
NAME="Debian GNU/Linux"
VERSION_ID="8"
VERSION="8 (jessie)"
ID=debian
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

Ok, this PostgreSQL image is based on Debian 8. Lets say this is not what I like because I want my PostgreSQL image based on CentOS. This is the topic for the next post: We’ll build our own CentOS image and get deeper in what the stacked filesystem is about. Once we’ll have that available we’ll use that image to build a PostgreSQL image on top of that.