Run and Auto-Update Docker Containers on NixOS
Learn how to run Docker/Podman containers on NixOS while automatically updating and pruning old images. I use this on a Raspberry Pi 4, but the code in the article will run on any NixOS system.
A year ago, I set up Raspberry Pi 4 to run Home Assistant with a cheap USB Zigbee Gateway. Configuring Home Assistant has a little bit of a fiddly character. Still, it runs great with Zigbee products from all kinds of expensive and cheap Zigbree products like heating thermostats, switches, temperature and humidity sensors, light bulbs, etc.
This article is not about how to set up NixOS on a Raspberry Pi. If you would like to learn how to do that, please have a look at this very good official nix.dev tutorial. (I suggest using a fast USB3 stick instead of an SD card for the system disk.)
We are going to use the original Docker image instead of the native NixOS modules (browse the official Home Assistant NixOS module options here), because this is a nice example of how to run relevant services as a Docker image with less hassle than on other GNU/Linux distributions. NixOS modules and packages are generally well-maintained, but this is also an interesting alternative in case some module is not as fresh as we might need.
Running Home Assistant via Docker on NixOS
To add the Home Assistant Docker image to our system configuration to make it run as a service, add this new NixOS module file to your configuration folder:
# file: homeassistant.nix
{ ... }:
{
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
volumes = [ "home-assistant:/config" ];
environment.TZ = "Europe/Berlin";
image = "ghcr.io/home-assistant/home-assistant:stable";
extraOptions = [
"--network=host"
"--device=/dev/ttyACM0:/dev/ttyACM0"
];
};
};
networking.firewall.allowedTCPPorts = [ 8123 ];
}
The module adds a new container called homeassistant
to the system.
The extraOptions
are necessary to give it the capability to use the
host network.
The second line there passes through our Zigbee USB dongle into the
container.
Please note that your Zigbee dongle might pop up with a different
/dev/...
filesystem path - adapt it accordingly.
The last line about firewall settings is only necessary if your NixOS
configuration sets networking.firewall.enable = true;
.
Instead of Docker, we’re using the podman
backend.
Podman is interesting on NixOS because it
works without a central daemon
but instead starts images directly.
Add this new module file to the imports = [ ... ];
line of your
configuration.nix
(be that inside a flake or in /etc/nixos/
) and
rebuild your system with nixos-rebuild switch
.
You should now be able to open http://<raspberry-ip>:8123
in your browser
and access the configuration dialogue!
Automatically updating Home Assistant
Home Assistant has some self-update capabilities, but this is not the same as updating the Docker image and restarting the service.
We can automatically update all images on the system using
systemd timers and the podman pull
command:
Create another NixOS module file and add it to your configuration.nix
’s imports
clause
or put its content into the module file we created earlier.
# file: update-containers.nix
{ ... }:
{
systemd.timers.update-containers = {
timerConfig = {
Unit = "update-containers.service";
OnCalendar = "Mon 02:00";
};
wantedBy = [ "timers.target" ];
};
systemd.services.update-containers = {
serviceConfig = {
Type = "oneshot";
ExecStart = lib.getExe (pkgs.writeShellScriptBin "update-containers" ''
images=$(${pkgs.podman}/bin/podman ps -a --format="{{.Image}}" | sort -u)
for image in $images; do
${pkgs.podman}/bin/podman pull "$image"
done
'');
};
};
}
Every Monday morning at 2:00, this systemd timer triggers an update of all running Podman images.
If you want to trigger this service manually, run
sudo systemctl restart update-containers.service
.
Updating the images alone does not restart the Home Assistant service. To do this, we write another systemd timer:
# file: restart-homeassistant.nix
{ ... }:
{
systemd.timers.restart-homeassistant = {
timerConfig = {
Unit = "update-containers.service";
OnCalendar = "Tue 02:00";
};
wantedBy = [ "timers.target" ];
};
systemd.services.restart-homeassistant = {
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.systemd}/bin/systemctl try-restart podman-homeassistant.service";
};
};
}
This module restarts the podman-homeassistant.service
, which runs the
Docker image, every Tuesday at 2:00 in the morning.
Of course, you can set the OnCalendar
field to a point in time that is closer
to the updates.
Again, save this part of the configuration in another NixOS module
file and then add it to your system’s NixOS config imports
clause,
or add its content to an existing file.
Then, switch the system to the new configuration.
Automatically pruning old images
A few months later, we might have multiple versions of Home Assistant images on our system while we only actually use the latest. This wastes disk space.
The podman NixOS module supports periodic weekly pruning out of the box already, so we don’t need to write a third systemd timer:
# file: prune-containers.nix
{ ... }:
{
virtualisation.podman = {
enable = true;
autoPrune = {
enable = true;
flags = [ "--all" ];
};
};
}
Again, add this new file to your configuration folder and
the imports
clause of your NixOS configuration module,
or add its content to an existing NixOS module.
Then, rebuild the system using nixos-rebuild switch
.
Summary
This NixOS configuration has been running on my Raspberry Pi hidden behind a shelf for a year now without needing attendance. I log into the system every few months to fix configuration format deprecations if there are any.
The systemd service definitions seem repetitive and can be shortened in different ways. In the Nixcademy Nix & NixOS 101 training, participants learn how to write rock-solid NixOS configurations and also their own configurable NixOS modules with best practice guidance as well as practical exercises, done together with assistance by the teacher if things don’t work out as expected.