The “why” part
I am sure that if you’ve not been using docker so far, you’ve heard about docker and most probably you’ve read about it. It’s been a very popular topic for at least few years now. So I assume you have at least some knowledge about it.

I don’t want to describe the architecture of docker. There have been thousands of articles, webinars and screen casts about it.

I heard and read about it but wasn’t actually so much in love until I bought my first VPS and had to start installing all the services, tools and packages from scratch and then, obviously, configuring them. What one usually needs when starting a project is a LAMP. Depending on it’s size some load balancing and efficient caching could be required. Additionally one would like to have a mail server plus some apps should be able to send emails. And more than that, these days SSL is a must, so let’s assume we want that too.

When you prepare a list like this and you start thinking about how much work is that you may ask yourself: what if I need to move all these things to another VPS? For any reason…
This is exactly the reason I’ve started considering docker and containerized setup, but there are really many different advantages of this approach.

Ok – let’s start. And please remember – we don’t want any single thing (except docker) installed on the host (in my case it’s ubuntu server 14.04.1 LTS)

So the complete list of our toys is the following:

  • mysql (percona)
  • nginx
  • php
  • mail server (postfix, curier)
  • memcached
  • let’s encrypt ssl certificates
  • haproxy

Now it’s up to you to decide if you want to have some of the tools coupled or all of them independent. I’ve decided to have them independent all but the nginx together with php fpm.

The searching part
Depending on the approach you may look for compound images containing many of the above together or single ones. You may also build your own image – I am gonna describe one of my own images later on, but as it’s not the scope of this article it will be rather basic explanation. Do not forget to have a look at the references in the end of the article where I’ve put all those used.

The gluing part 
We need something that will build, create, start and maintain all containers, and keep most of the configuration and also linking between separate services. The tool for this is docker-compose and we are going to have everything in a yaml based file.

MySQL (Percona)

image: 'percona:latest'
  environment:
    - 'MYSQL_ROOT_PASSWORD=some-strong-pass'
  volumes:
    - /srv/mysql/data:/var/lib/mysql
    - /srv/mysql/log:/var/log/mysql
  ports:
    - '18123:3306'
  restart: always
image: 'percona:latest'

One has to be careful when using the “latest” tag and it’s definitely worth checking the image maintainer policy for upgrades.

ports:
- '18123:3306'

The other thing worth noticing here is the “port forwarding” – I love MySQL workbench to manage my databases, recently also configured most of them in PhpStorm, but having the port for MySQL open, especially when using the default 3306 is asking for trouble (and the log with failed attempts growing soooo quickly). In this case considering a different, unusual port for connecting from outside could be some option – in my case it drastically reduced the number of attempts.

volumes:
- /srv/mysql/data:/var/lib/mysql
- /srv/mysql/log:/var/log/mysql

“Mapping” the host dirs to container dirs.

Mail server

image: tvial/docker-mailserver:v1
hostname: mail
domainname: my-domain.com
# your FQDN will be 'mail.my-domain.com'
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
volumes:
- /srv/kuba/mail/:/tmp/postfix/
- /srv/kuba/mail/all:/var/mail
- /srv/kuba/mail/spamassassin:/tmp/spamassassin/
environment:
- 'SA_TAG=0.01'
- 'SA_TAG2=4.5'
- 'SA_KILL=5.5'
- 'ENABLE_FAIL2BAN=1'
restart: always

Pretty straight forward configuration. SA prefixed ones are Spam Assassin directives.

Memcached

image: 'memcached:1.4.36-alpine'
ports:
- "11211:11211"
mem_limit: 3g
restart: always

If you need/want to save size of your images, always look for those based on alpine linux.

Nginx and php

image: 'million12/nginx-php'
environment:
- 'VIRTUAL_HOST=*,https://*'
- 'EXCLUDE_PORTS=443'
links:
- percona:mysql
- mail
- memcached:memcached
volumes:
- /srv/kuba/web/:/data
- /srv/storage/:/srv/storage
ports:
- '80'
restart: always

And finally some linking! This web container will use links to percona, mail server and memcached!

SSL certificates

image: 'levino/letsencrypt-cert-service'
environment:
- 'CERT_DOMAINS=my-domain.com,www.my-domain.com,my-another-domain.com,www.my-another-domain.com'
- 'CERT_EMAIL=your@email.address'
- 'CERT_SERVICE_PRIVATE=true'
- 'CERT_SERVICE_PASSWORD=some-strong-pass'
- 'CERT_SERVICE_USERNAME=some-username'
- 'VIRTUAL_HOST=http://*/.well-known/*,http://cert.my-domain.com,http://cert.my-another-domain.com'
- 'VIRTUAL_HOST_WEIGHT=1'
- 'EXCLUDE_PORTS=443'
- 'TERM=xterm'
volumes:
- /srv/kuba/web/cert/:/etc/letsencrypt
ports:
- '4443:443'
restart: always

Why let’s encrypt? Because it’s a free, trusted CA. It issues certificates for 3 months only, but with the above image you could have them renewed.

Load balancing/routing

image: 'tutum/haproxy:latest'
environment:
- 'BACKEND_PORTS=80'
- 'DEFAULT_SSL_CERT=-----BEGIN PRIVATE KEY-----\n-YOUR-PK-\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n-MY-DOMAIN-CERT\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMY-ANOTHER-DOMAIN-CERT\n-----\nEND CERTIFICATE-----\n'
links:
- cert
- web
ports:
- '80:80'
- '443:443'
- '1936:1936'
restart: always

The running part
So let’s spin things up with docker compose. Use -f to specify the setup file and remember about using -d flag to run as deamon (that will let you continue working on the host or terminating the connection)

$ docker-compose -f my-whole-setup.yml up -d
Creating kuba_memcached_1
Creating kuba_cert_1
Creating kuba_percona_1
Creating kuba_mail_1
Creating kuba_web_1
Creating kuba_router_1

Checking if all are properly created could be done with docker ps command:

$ docker ps
IMAGE                             CREATED            STATUS               NAMES
tutum/haproxy:latest              2 minutes ago      Up 2 minutes         kuba_router_1
million12/nginx-php               2 minutes ago      Up 2 minutes         kuba_web_1
tvial/docker-mailserver:v1        2 minutes ago      Up 2 minutes         kuba_mail_1
percona:latest                    2 minutes ago      Up 2 minutes         kuba_percona_1
levino/letsencrypt-cert-service   2 minutes ago      Up 2 minutes         kuba_cert_1
memcached:1.4.36-alpine           2 minutes ago      Up 2 minutes         kuba_memcached_1

And voilà, all services are up and running.

Noticed all the services prefixed with kuba_ ? It’s because the path I’ve run the command was /home/kuba but it would change depending on your path. Also, worth to be noticed is that if you want to scale in the future, do not “hardcode” the container name, allow adding the unique identifiers in the end, like at the above example all are suffixed with _1.

In the next part I will try to dig into the details, show how we could scale, but also show how and why to build one’s own image. Feel free to leave some feedback or contact me.

References: