Docker Compose Best Practice

Docker Compose is very useful for building the environment of multiple docker containers. Some examples are in official document, but we’re often confusing to configure docker-compose.yml and the other configuration of docker conteiners.

I’d like to talk about the best practice of Docker Compose. It has 3 phases, I think. As the phase number is increased, the configuration becomes more complex. We should avoid the complication as much as possible.

Phase 1: Only docker-compose.yml

First, we should use the version 3 of docker-compose.yml to write the configuration simply. In phase 1, we use just one configuration, docker-compose.yml. It’s easy to understand and manage the environment.

version: '3'
services:
mysql:
image: mysql:5.7.18
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
postgres:
image: postgres:9.6.2
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: mysecretpassword

Even when the enviroment is small, I recommend to use docker-compose instead of docker run because the sub commands of Docker Compose are very convenient. For example, if you login to the MySQL container, you can use the following command which has no options. docker-compose exec mysql /bin/bash

Phase 2: With data volume

If you check the docker image has no environment variable, you want to write a custom Dockerfile in accordance with your use-case… Wait! Actually we can make it by using data volume instead of Dockerfile.

version: '3'
services:
nginx:
image: nginx:1.11.13-alpine
ports:
- "80:80"
volumes:
- ./nginx/index.html:/usr/share/nginx/html/index.html
logging:
driver: fluentd
options:
fluentd-address: "127.0.0.1:24224"
tag: "docker.{{.Name}}"
depends_on:
- fluentd
fluentd:
image: fluent/fluentd:v0.14.14
ports:
- "24224:24224"
volumes:
- ./fluentd/etc/fluent.conf:/fluentd/etc/fluent.conf

As you can see, we’re able to replace the files in a container by data volume. In the above example, nginx access logs will be output to stdout by fluentd after we access to localhost via http.

In phase 2, it became more complex than phase 1, but don’t worry. We don’t use a custom Dockerfile, so it’s not bad.

Phase 3: Custom Dockerfile

Alright, we have to write a custom Dockerfile… I think we don’t like the custom Dockerfile because it’s too much freedom and no maintenability, but there is no other way.

version: '3'
services:
nginx:
image: nginx:1.11.13-alpine
ports:
- "80:80"
logging:
driver: fluentd
options:
fluentd-address: "127.0.0.1:24224"
tag: "docker.{{.Name}}"
depends_on:
- fluentd
fluentd:
build: ./fluentd
ports:
- "24224:24224"
volumes:
- ./fluentd/etc/fluent.conf:/fluentd/etc/fluent.conf
mysql:
image: mysql:5.7.18
ports:
- "3306:3306"
- "11211:11211"
volumes:
- ./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw

It’s an example of nginx access count aggregation in near realtime. Even if a custom Dockerfile is needed, it would be better to be simple. I think we shouldn’t make a copy of the base Dockerfile which you want to use and edit the copy. You just write only your customization part on the custom Dockerfile, like this:

FROM fluent/fluentd:v0.14.14

RUN gem install fluent-plugin-memcached --no-rdoc --no-ri

Plus, if you want to change only ENTRYPOINT or CMD, you don’t need a custom Dockerfile. You can use entrypoint in docker-compose.yml.

Anyway, we should make the configuration more simple and reduce custom Dockerfile.

Docker Compose Best Practice

  1. Only docker-compose.yml as much as possible
  2. Replacement of files in container without Dockerfile
  3. Simple Dockerfile if it’s needed

http://innossh.hatenablog.com/entry/2017/04/22/192743 (ja)