I recently added a docker-compose
definition of services to make it easier for people to contribute to the new CodeBuddies back-end. This addresses some of the pain new contributors were feeling with setting up a local development environment. The CodeBuddies backend is an API built using the Django REST Framework. PostgreSQL provides the data store. Everything is fronted by an Nginx reverse proxy. This is often referred to as a three-tier architecture.
In addition to these three core services (web, app, and db) we include two supporting applications to make local development easier; Adminer - a front-end to allow people to explore the database, and Mailhog - a dummy mail server for testing outbound email.
Assuming Docker is installed, contributors are able to start a local development environment by running the following command.
docker-compose up -d
The set-up works really well and we’ve had great feedback from contributors on Windows, Linux and Mac OS. For the curious, the docker-compose.yaml
file can be found at github.com/codebuddies/backend.
We use the same approach to stand up an environment for testing using GitHub Actions. This ensures consistency between tests run by contributors on their local machines and the tests that get run on all pull requests.
When running tests using GitHub Actions, one of the things we aim to do is to give fast feedback to contributors. We don’t want to leave them hanging around to find out if the tests have passed. One of the ways we can speed things up is to avoid downloading or starting unnecessary services (Mailhog and Adminer). Unfortunately, the default docker-compose
behaviour is to start all of the defined services. I wanted a way to only start the services I needed.
Optional Services
A Docker Compose proposal to make services optional was rejected and so this leaves us with a couple of options:
- Use two
docker-compose.yaml
files, one for essential the other for optional service definitions. - Start services individually with
docker-compose up [service]
.
Using a second docker-compose.yaml
file to start supporting services would detract from the simplicity of the initial set-up. There is something nice about having a single command to get everything up and running.
docker-compose -f core.yaml up
docker-compose -f optional.yaml up
The alternative suffers the same challenge as each service would need to be started separately.
docker-compose up db
docker-compose up app
docker-compose up web
Dependency Trees
The docker-compose
format allows us to specify dependencies between services. A logical definition of service dependencies has the added side effect of making it easier to start just the services we need.
/Untitled_Artwork 2.png
By starting the service at the top of a dependency tree, docker-compose
will also start all the required dependencies. In our scenario we have three required services: web
, app
, db
. We also have two supporting services. To bring up just the services required for testing we start the web
service.
docker-compose up web
All dependent services are started automatically. Local developers still get the advantages of having everything start with a single command.
docker-compose up
Summary
Docker Compose provides a succinct way to define services that make up an application. It doesn’t allow services to be marked as optional so everything is started by default. In the event that we only need a sub-set of services to be started, we can use the dependencies between services to start only a subset of the services defined in the docker-compose.yaml
.
If you have any questions or would like to suggest alternative approaches, you can find me on Mastodon or Twitter.