Docker Compose, quickly
This is a quick, practical guide to get you started. A common issue I notice in projects that use Dockerfile
’s is that they don’t easily run on developer machines and don’t work across operating systems and IDEs (Visual Studio Code, vs. PyCharm, vs. Command line).
Why Docker? 😋
- Consistent environment:
- Some binaries and applications might not be able to run your platform natively. Fortunately, Apple ARM macs (e.g. M1/M2) already allow you to run
amd64
/x86_64
binaries thanks to Rosetta 2. However, you still can’t runlinux/arm64
orlinux/amd64
on macOS. You still can’t run ARM binaries on older,x86_64
macs. With Docker, you can run binaries from other architectures more easily, thanks to QEMU emulation. - Environment variables are not automatically passed to the docker image or container.
- Some binaries and applications might not be able to run your platform natively. Fortunately, Apple ARM macs (e.g. M1/M2) already allow you to run
- Clean environment:
- whenever I want a clean environment to test in, I find myself spawning a docker container and entering it’s shell:
docker run -it python:3.9-bullseye bash
. This makes me more confident when helping colleagues or writing articles.
- whenever I want a clean environment to test in, I find myself spawning a docker container and entering it’s shell:
- Powerful build: you can compile docker images for other platforms on your machine. Without Docker, how can you build applications or binaries for Linux?
- Many more benefits. This is a quick guide. 😉
Steps 🚶🏻
- Do all the steps in your project directory. See https://github.com/ben-xD/docker-compose-guide for an example.
Create a Dockerfile
for your application
- Writing a
Dockerfile
is very specific to the application you want to deploy, so I won’t include that here. It can also get quite complex. In the example, we use a basic `Dockerfile. - You’ll need to copy over your application.
Create a docker-compose.yml
file
- This should contain:
services:
development:
build:
context: "./"
dockerfile: Dockerfile
args:
# Optional credentials used during image build.
- USERNAME
- TOKEN
env_file:
- .env
command: "tail -f /dev/null"
# Specify a platform if some of your binaries only work on certain platforms. e.g. linux/amd64, linux/arm64
# platform: linux/amd64
volumes:
- .:/workspace
If you need credentials when building your image:
Create a .env
- The
.env
file should contain all the credentials in yourargs
Add .env
to .gitignore
- Ignore this file, because it will contain your credentials.
Create a .env.example
- Copy your
.env
and remove the credentials. - Keep this up to date whenever your
.env changes
Create VSCode devcontainer configuration
- Even if you don’t use VSCode, another developer might want in the future. Create one now so you configure it correctly 😆.
- We don’t want developer environment to drift or become inconsistent between Visual Studio Code users and others. Unfortunately,
devcontainers
defaults to configuring things insidedevcontainers.json
this can easily happen.
- We don’t want developer environment to drift or become inconsistent between Visual Studio Code users and others. Unfortunately,
- install “Remote - Containers” extension from Microsoft.
- Open Command Palette, run “Remote-Container: Add Development Container Configuration Files…”
- Delete
devcontainer/docker-compose.yml
- Inside
.devcontainer/devcontainer.json
,- use only 1 docker-compose file:
{
...
"dockerComposeFile": ["../docker-compose.yml"],
}
- Add a warning to the top of
.devcontainer/devcontainer.json
:
// WARNING: developers should put most configuration in docker-compose.yml instead of VSCode specific configuration to ensure all developer environments work, not just VSCode.
Try launching the application
- From the command line, run
docker-compose up -d --build
- Enter the shell inside the container:
docker exec -it docker-compose-guide_development_1 bash
- This command won’t need to change everything you build the image or restart your computer.
- That name should be consistent to the folder name and the service inside
docker-compose.yml
. If you change those, you’ll need to update this command.
- Run a command, you’re in Linux: e.g. run
uname -a
- This should return
Linux eed70de56bc9 5.10.104-linuxkit #1 SMP PREEMPT aarch64 GNU/Linux
- This should return
Write the documentation
- Describe what you need to do to launch the application locally. Take a look at the example Contributing guide.
More reading 👀
- Docker images don’t have the Linux kernel in them, they share the one from the Host. Running Docker on macOS is more resource intensive than on a Linux host, because it needs to run a Linux kernel. More information on this Stack Overflow answer.
- Every Docker image eventually extends from
scratch
. For example, the Debianbullseye
image is actually pretty simple. It does 1 thing: copy and extract the root file system (33.4MB) into the container:- The root file system provides a lot, you can download it from Github, extract it and take a look.
- More information on this Stack Overflow answer.
FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]
- Useful images: If you need more functionality in your Docker image, you could write
Dockerfiles
. Try to find existing Docker images that do most of your needs, and extend from there. - Lightweight images: If you need to deploy a lightweight version of your image, you could build multi-stage docker files. These are slower, but can be better for deployment because they smaller and more locked down. For this reason, I like to provide 2
Dockerfile
’s, one for release, and one for local development. Backstage, by Spotify does something similar in Building a Docker image. - Deployment: If you need to deploy your application to a Kubernetes cluster, you could look at Helm. If you want even more advanced deployments, take a look at ArgoCD.
- Warning: Please don’t go into the wormhole of using all the tools available to you today, as it will add the complexity of your project. Use it when you need it.
Conclusion 🏁
That was a quick guide to using Docker compose locally. I’ll be happy to clarify things and point to more resources. Some tricky things for me were to:
- setup Gitlab authenticate with a private Container Registry so that it would use the image when running CI commands. You need to generate a
DOCKER_AUTH_CONFIG
Gitlab environment variable. - Building complex existing tools together with my application (e.g. Ardupilot simulator/SITL) in the same Docker image. This is useful for running integration tests of your application communicating with the drone simulator.