Between its well-documented nature, beginner-friendly collection of tools, and thousands of first and third-party images, Docker has a large following in the self-hosting and home lab communities. Although I’ve started using Podman as of late, I still rely on Docker and its utilities for many containerization projects – and that’s largely because of Docker Compose.
Unlike its K8s and Podman counterparts, Docker Compose works well with practically every service I use – be it something as popular as Nextcloud or an obscure Ofelia container. And the fact that it doesn’t force me to jump through multiple hoops just to deploy a service is just icing on the cake. As someone who uses Docker Compose extensively, here’s a collection of tips and tricks that make my self-hosting workloads a lot easier to manage.
I use these 5 tools to turn my Raspberry Pi into a self-hosting behemoth
Even a Raspberry Pi can become a containerization war machine with the right set of apps
Store all compose.yml files in separate folders
Preferably in the same directory as the container’s volumes
Since Docker Compose doesn’t use anything besides the compose.yml (or docker-compose.yml, for folks who still follow the old convention) file when deploying a new container, attempting to reuse the same document name for different containers isn’t possible unless you keep switching directories when creating new config files. Of course, you could try overwriting the compose.yml file with the syntax for a different service, but doing so will cause Docker to identify the previous container as orphaned.
My solution to the compose.yml conundrum is to create new folders for each container I want to spin up and store the config file over there. The Paperless-ngx YAML file, for example, lies in its own folder within the /home/containers directory, alongside its config and data volumes, and the same holds true for my other containers. After all, most of my projects require persistent volumes anyway, and keeping the compose.yml file together with the rest of the container data makes it easy to spin a new one up if things go south later down the line.
Append the healthcheck attribute for essential containers
An easy way to check if the containers are nice and healthy
When a container shows up as running after executing docker ps, it’s not always guaranteed to be in an operational state – and that’s something I learned the hard way during my early Docker days. It’s possible that a certain dependency isn’t working, which results in the container getting stuck in an infinite loop and appearing in the "running" state, even though it’s technically inaccessible.
That’s where the healthcheck attribute comes in handy. Adding it to the compose.yml file allows Docker to confirm whether a container is actually “operational” and not just in a “running” state. If it’s constantly crashing or stuck in a dependency failure loop, Docker displays the container as unhealthy, and that’s my cue to don my troubleshooting hat. Or it may be that the container is just spinning up, which is when I typically modify the start-period, timeout, and retry-period instructions to avoid receiving false positives.
Configure restart policies
To keep the containers alive, provided they’re not broken beyond repair
While we’re on the subject of crashing containers, restart policies are a great way to ensure certain apps keep running even if they shut down abruptly. While they’re not all that useful if the container has a catastrophic failure and ends up getting stuck in a boot loop, restart policies can help bring essential services back to life if they went down because of some fatal error (that doesn’t need manual recovery) or by exhausting too many system resources.
The restart attribute supports a handful of restart policies for Compose files, though I tend to use the on-failure flag with a maximum of two retries, since further reboots would mean there’s something wrong with the container.
Arm coding-centric containers with Docker Compose Watch
Great for web projects
When I was active in the web development field, I’d use the Live Server extension on VS Code to check the results of my painstakingly-coded documents in real time. Docker Compose Watch operates on the same principle, except, rather than displaying a web page, it automatically syncs any changes made to the config file with the images – and even rebuilds them if necessary.
That way, I don’t have to manually build new container images after modifying the source code. While it does require more permissions and extra binaries within the service image, being able to see the code’s output, and even the container image, getting updated dynamically makes the watch attribute worth adding to coding-centric environments.
Use the devices attribute for intensive containers
Useful for projects that require USB and PCI passthrough
Whether it’s Immich requiring a GPU for machine-learning tasks or a mere Debian container needing access to a USB flash drive, there are plenty of services that may need access to PCI or USB devices. Of course, running Docker Desktop on Windows may make device passthrough difficult because of the container runtime’s reliance on a virtual machine. But if you’re on the Linux home turf like I am, adding the devices attribute to Compose configuration files makes it easy to leverage USB and PCI peripherals in hardcore container workloads.
The devices attribute is a lot easier to use with privileged containers, where I don’t have to worry about modifying user ID mappings, but I’ll always recommend going with their unprivileged counterparts.
The Docker Compose rabbit hole goes pretty deep
Still on the prowl for more Docker Compose tips? If you’ve run Wger, Romm, or other complex containers, you may have used an environment file to store credentials and other essential data for the compose.yml config. For folks who sync their docker-compose.yml files with online platforms like Git, it’s a good idea to relegate private credentials to an .env file by adding the env_file attribute to the YAML document. Likewise, you can even create a separate network for co-dependent containers that you want to keep isolated from the Internet via the networks attribute.
I automated my Docker container updates safely with Watchtower
Watchtower is a lifesaver when you've got an arsenal of Docker containers
