In my last blog post, I detailed how I built myself a beefy file and application server. In this post, I wanted to share how I organize and run Docker containers on Unraid.
Orchestrating Services with Docker Compose
I’m a huge fan of docker compose
. Although the docker run
command is fine when you’re just getting started, it becomes unwieldy when managing more than a couple of Docker containers.
Although Unraid includes Docker, you’ll need to install docker compose
separately via this plugin. You can ignore the plugin’s settings page – there’s nothing to configure via Unraid’s interface after installing. The plugin includes the CLI tool, which is all you need.
With docker compose
, you can use formatted text files – yaml
files – to store exactly how you want your containers to run. If you need to change something, just edit the file, run docker compose
with the path to your yaml
file, and your changes will be applied.
Because Docker containers are ephemeral, you’ll need a place to store the stuff you want to keep as you start/stop/delete/recreate your containers. Unraid really wants you to keep that at /mnt/user/appdata
, and I suggest it’s easiest not to fight that (though if you can, keep this on an SSD). Here’s the hierarchy I’ve set up in there:
/mnt/user/appdata:
__docker-compose.yml
example-app/
config.env
data/
secrets.env
Let’s break down the components:
__docker-compose.yml
: This is the file where I keep all of the information about my Docker containers. I add a couple of underscores to the front of this, so it sorts at the top.example-app
: I have a folder set up for each container, named after the container. The app must be compliant with DNS formatting, so no underscores.config.env
: This is the file with most of the environment variables.data
: This contains all files that I want to persist when a container is rebuilt.secrets.env
: This contains the environment variables that might be sensitive – passwords, for example. Long-term, I plan to move this to Docker Secrets, but the first step was to separate them into their own files.
__docker-compose.yml
might contain something like this:
networks:
reverseproxy:
name: reverseproxy
driver: bridge
services:
example-app:
container_name: example-app
image: hello-world:latest
# https://hub.docker.com/_/hello-world
env_file:
- /mnt/user/appdata/example-app/config.env
- /mnt/user/appdata/example-app/secrets.env
volumes:
- /mnt/user/appdata/example-app/data:/config:rw
ports:
- 80:80
networks:
- reverseproxy
restart: always
config.env
and secrets.env
, if populated, might contain some environment variables:
FOO=BAR
ROOT_PASSWORD=BAZ
From there, it’s only a few commands to have docker compose
spin up or tear down services based on these files:
#!/bin/bash
# This updates your Docker images
/usr/bin/docker compose -f "/mnt/user/appdata/__docker-compose.yml" pull
# This rebuilds your Docker containers to match the yaml file. Be careful, as it deletes any containers not mentioned in your yaml file!
/usr/bin/docker compose -f "/mnt/user/appdata/__docker-compose.yml" up -d --remove-orphans
# This deletes any Docker images that are old/unused.
/usr/bin/docker image prune -a -f
exit
To keep things up to date, I run a variation of this script every night via the User Scripts plugin (which uses cron
).
I’ve found this method of organization scales very well, while providing the most flexibility. When I want to add a new app, I start with the YAML, then create the directory and config files. Heck, I’ve even managed to automate that through OliveTin.
Further Expansion
To securely expose services outside of my home network, I use a reverse proxy / load balancer called SWAG – there’s a great setup guide available. I recommend Cloudflare for both their at-cost domain registration services, as well as free wildcard DNS. Thanks to that guide, as soon as I spin up a new service, it’s immediately available at https://example-app.mydomain.com
with a free wildcard Let’s Encrypt certificate that automatically renews itself. Also, rather than rely on built-in authentication, I’ve put most services behind Authentik, which provides SSO and MFA (including passkey support!).
Once you accumulate enough services, you can really tie everything together with a dashboard. I’ve gone through several over the years, but my current favorite is called Homepage. You can add an arbitrary amount of bookmarks, arrange them however you’d like, and even display live data in some of them (they’re called “widgets”). Homepage is clean, lightweight, and is actively getting better all of the time.