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)
- mail server (postfix, curier)
- let’s encrypt ssl certificates
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.
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.
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.
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!
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_EMAILfirstname.lastname@example.org' - '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.
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.