Docker Compose, a UX layer on top of Docker

Charalambos Paschalides
4 min readDec 21, 2018

In my previous article titled Dockerize your development environment, I aimed at explaining some of the most fundamental Docker concepts by presenting a concrete use-case for Docker. I hope that now concepts such as Docker files, volumes, ports and networking feel familiar. I am a strong advocate of getting one’s feet wet, and assuming that our feet are wet right now, it’s time to move on. We got the hang of the basics, so let’s build on them.

In this article I explain how we can use Docker Compose for the same use-case, namely dockerizing our development environment. Why another article on solving the same problem, I hear you say? Well, It’s a problem that has been explained and solved step by step in the previous article. It’s therefore an ideal playground to evolve and introduce new technologies that build upon the ones we’ve already used. I believe the clean mapping of the Docker and the Docker Compose solutions, makes the similaries, differences and evolution, well-understood.

Introduction

Docker Compose is a container orchestration tool, which is a fancy way of saying that it sets up and manages multiple containers with just one piece of configuration.

Photo by Aalok Atreya on Unsplash

Before we kick it off

In case you haven’t read my previous article perhaps it’s a good idea to scan it through quickly, to get an idea of what we’ll be doing.

Procedure

Now, let’s get right to it.

$ git clone git@github.com:stargazer/django-docker-compose.git
$ cd django-docker-compose

The only difference to the repo we used in the previous article, is the addition of the docker-compose.yml file. The Docker files and applications are the same. The file docker-compose.yml is basically the steering wheel for Django Compose. Below we can see the file’s contents.

version: '3'
services:
app:
build: ./app/.
stdin_open: true
tty: true
ports:
- "80:8080"
volumes:
- ./app/src:/src
networks:
- net
db:
build: ./db/.
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=project-db
- MYSQL_USER=user
- MYSQL_PASSWORD=secret
networks:
- net
networks:
net:

So what happens here? We define the two containers we’ll be using (Docker Compose calls them services) as well as the network. The two services app and db go on and define all their runtime characteristics, so there’s no need to define them using any long and scary sudo docker run commands.

Let’s take a closer look. We define the app contaner:

  • Built from the Dockerfile in ./app/.
  • Keeps the stdin open and attaches an interactive terminal
  • Exposes port 8080and maps it on the hosts’s port 80
  • Maps the host’s directory ./app/src to its own /src directory
  • Connects to network net

The db container:

  • Built from the Dockerfile in ./db/.
  • Supplied with a list of environment variables
  • Connects to network net

And we define the network net, which is simply a default Docker network.

The Docker files from which our containers are built, are the exact same ones used in the previous article. The parameters in the docker-compose.yml file are the exact equivalents we had used when running the containers in the previous article, with sudo docker run. It’s easy to see that the docker-compose.yml centralizes the container management. It links the Dockerfile to the corresponding container’s runtime parameters, and takes care of both building the Docker image and running the container. It’s like a one-stop-shop solution; When using Docker Compose, there is no reason to run sudo docker build or sudo docker run. It’s all done under the hood for free.

So, let’s run our containers and see what we get.

$ sudo docker-compose up -d

We’ve just built both our Docker images and ran the two containers in detached mode. So both the app and db containers are now up and running.

Let’s interact with our app container

$ sudo docker-compose exec app pip install -r requirements.txt
$ sudo docker-compose exec app ./manage.py migrate
$ sudo docker-compose exec app ./manage.py createsuperuser
$ sudo docker-compose exec app ./manage.py runserver 0.0.0.0:8080

Here we’ve ran a few Django management commands, and eventually ran the development server, listening to the app container’s port 8080. Remember this is mapped on the host’s port 80. Going now to our browser’s url bar and typing http://localhost/admin/ should be enough to access the application.

Conclusion

This article describes how you can quickly and succinctly manage a whole bunch of Docker containers using Docker Compose. Compared to running stock Docker, it’s a much easier approach since it defines both the containers themselves as well as their relationships in a single configuration file.

The previous article and this one have built exact functional equivalents, the former using stock Docker and the latter using Docker Compose. Orchestration tools are not a substitute for poor understanding, but rather a UX weapon in our arsenal.

As I mentioned earlier, Docker concepts should be well understood before using orchestration tools. And honestly, they are easier than one might think at the beginning.

--

--