Docker Compose, a UX layer on top of Docker
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.
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:
- netnetworks:
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
8080
and maps it on the hosts’s port80
- 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.