How I manage 30+ docker containers at home

How I manage 30+ docker containers at home

When I first started self hosting services at home, my docker-compose file had a single container definition: Plex. Fast forward 5 years and today my compose file has over 30 containers defined in just over 500 lines of yaml. While it's incredibly easy to drop a few more lines into the compose file to try out a new service in just a couple minutes, keeping things up to date starts to become a burden.

Let's talk about the latest tag

It's tempting to define every container using the latest image, but as I found out the hard way, it's not a smart way to run containers you rely on. If you're not paying attention to what you're updating, you could find yourself with broken services (or even corrupted data).

The problem with the latest tag is that it doesn't act as a pointer to the latest release of an image, it's basically just another tag. When you pull the latest tag, that's not a pointer to version-x.x.x, it's whatever's tagged as latest. This means that the image you download isn't tagged with the version, so in 2 months when you need to know which image version you have running, all you have to go on is latest.

Moral of the story: don't use the latest tag.

So far, I haven't found a better way of safely updating containers than manually updating each image definition. For a compose setup with a few containers this isn't a big deal, but at 30+ containers it starts to get cumbersome. To ease this a bit, I use the .env file

Docker Compose environment file

Docker Compose supports environment files for making environment variables available to docker-compose. By default, this is a file called .env placed in the project directory. Aside from storing some commonly used variables like Time Zone and the root Appdata directory, I set variables for each image definition including the tag.

Example:

# Volumes
VOL_APPDATA=/opt/appdata/

# Images
IMG_HASS=homeassistant/home-assistant:2021.11.0
IMG_NODERED=nodered/node-red:2.1.3-14

# Misc
TZ=America/New_York
.env
hass:
    container_name: hass
    image: ${IMG_HASS}
    volumes:
        - ${VOL_APPDATA}/homeassistant:/config
    environment:
        - TZ=${TZ}
    restart: always
    ports:
        - 8123:8123

nodered:
    container_name: nodered
    image: ${IMG_NODERED}
    volumes:
        - ${VOL_APPDATA}/nodered:/data
    environment:
        - TZ=${TZ}
    restart: always
    ports:
        - 1880:1880
docker-compose.yml

Trying to keep that many services up to date is time-consuming regardless, but putting all of the image definitions in the same place makes the process a little bit easier. If nothing else, it's easier to read.