Smart Home Docker Setup

Previously I wrote about setting up Home Assistant running in Docker along with Portainer to provide a GUI for management. Since then I’ve spent a fair amount of time coming up with a setup that is something I’m comfortable with and feel is able to be easily maintained. I’m assuming that I’ve gone through a lot of steps that are similar to what others have.

I got up and running with simple docker run commands that were cobbled together from random posts and examples across the internet. I would run the container with its various command line options and tweaking until it worked. Once I got something solid I would copy that into some of my notes so I had it for later. This whole process felt disorganized and likely to end up in a sad failure when I tried to come back to any of it months later when I would have inevitably forgotten how and why I had done anything.

I then realized that there was docker-compose which initially was quite confusing. Reading the documentation made it seem like it was meant for managing multiple container docker applications, but I only needed one container and initially thought this was an issue. Turns out just because it can support multiple doesn’t mean you cant just run one container. Docker Compose files provided a so much nicer and organized approach to setting up the configuration and running a container.

Where I stored all of my Docker stuff

In /home/docker is what I settled on. Within that directory I created a new directory for each of my containers, and their associated mounts, configs, etc. This ended up looking similar to:

/home
   |__ /docker
         |__ /mosquitto
               |__ /config
               |__ /log
               |__ /data
         |__ /portainer
         |__ /samba  
Docker directory structure

This seemed easy to wrap my head around, find my data, and navigate.

docker run vs. docker-compose up

Heres and example of the docker run command to bring up my mosquitto container.

sudo docker run -it --name mosquitto -p 1883:1883 -p 9001:9001 -v /home/docker/mosquitto:/mosquitto/config -v /home/docker/mosquitto/data:/mosquitto/data -v /home/docker/mosquitto/log:/mosquitto/log eclipse-mosquitto

Now compare that to what it looks like in a docker compose file.

version: '3.7'
services:
  mosquitto:
    container_name: "mosquitto"
    image: "eclipse-mosquitto"
    restart: unless-stopped
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - "./config:/mosquitto/config"
      - "./log:/mosquitto/log"
      - "./data:/mosquitto/data"
docker-compose.yaml

Both of these are doing the same thing, but obviously the Docker Compose file is far easier to read.

Understanding Volumes

So now that I’ve settled on using Docker Compose and it appealing to my need for organization the next step was figuring out what all those -v or volume options actually meant. Since it was just copy paste to start I had no idea what was going on. Realistically it was creating those directories local to where I ran docker compose and persisting the data on the host’s file system. This seemed great at first but requires that that directory structure had to be the same if you were to move the containers to a different host, which is less than ideal.

It turns out if you don’t declare them Docker takes care of it for you and just creates its own. This led to me to start understanding what volumes were in Docker, which you can find in:

/var/lib/docker/volumes

Docker will just give them random names and create them. You also have the option of specifying a volume and giving it your own name, which can also be done in the Docker Compose file.

volumes:
   data_vol:      
      name: my-named-volume

Doing this will give you named volumes managed by Docker. This seemed like a solid solution, except that everything is then located in /var/lib/docker/volumes It was pretty annoying bouncing over to that directory to change config files, and then back to /home/docker/. This felt like the “right” thing to do, and the proper Docker pattern to follow, but I didn’t like it.

Introducing local-persist

I was talking with a friend and he told me he had found this Docker volume plugin called local-persist. This changed things up quite a bit. I was now able to persist data in a local directory but still have named volumes. The real kicker was that I felt like I still doing things the “right” way. I now had the ability to persist data locally and maintain named volumes. If I removed the volume my data lived on. If I wanted to point those volumes at an external source like my NAS, I now had that option. That original Mosquitto Docker Compose file now turned into this:

version: '3.7'
services:
  mosquitto:
    container_name: "mosquitto"
    environment:
      - TZ=America/Los_Angeles
    image: "eclipse-mosquitto"
    restart: unless-stopped
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - "config:/mosquitto/config"
      - "log:/mosquitto/log"
      - "data:/mosquitto/data"
volumes:
  config:
    name: mosquitto_config
    driver: local-persist
    driver_opts:
      mountpoint: ${LOCAL_PERSIST}/mosquitto/config
  log:
    name: mosquitto_log
    driver: local-persist
    driver_opts:
      mountpoint: ${LOCAL_PERSIST}/mosquitto/log
  data:
    name: mosquitto_data
    driver: local-persist
    driver_opts:
      mountpoint: ${LOCAL_PERSIST}/mosquitto/data
docker-compose.yaml

That $LOCAL_PERSIST environmental variable is set in /etc/environment to LOCAL_PERSIST=”/home/docker/.local-persist”. This made it easy for me to use it across multiple Docker Compose files.

In Conclusion

I now feel like I have a pattern of setting up Docker containers using Docker Compose using the local-persist plugin that makes me feel like I’m in control and have something I can easily maintain and move around if I chose to. Everything I have talked about here was learned over the course of a couple weeks and is just my opinion. It’d be great to hear from others what their approach is.