Containerize the NixOS way
How do you do, Cola Gangsters? It’s been a while! We’ll get back to the Nixification of our daily driver shortly because a lot of things have changed, and we need to update things for this modern age. Today, however, I want to talk about running containers in a NixOS way that is sure to thrill!
Let’s say you want to add n8n to your homelab. One way to go about that is to have Docker or Podman installed, grabbing a docker-compose.yml, changing what needs changing, and running sudo podman-compose up
or the Docker equivalent. We don't use docker here.
What happens when you reboot your system, though? Do we need to go around and bring our containers up one by one? Or do we need to write a systemd
service for every one of our containers and make sure we get all the dependencies right? Actually, we don't need either of those things. All we need is love compose2nix.
compose2nix
will take the compose YAML file and generate a proper Nix module for you. From there, you just call the nix module from you configuration.nix
or flake, sudo nixos-rebuild switch
, and... that's about it. Let's take a look at how that would go with our n8n example.
First, you go here and grab the docker-compose.yml
file. It looks like this:
version: '3.8'
volumes:
db_storage:
n8n_storage:
services:
postgres:
image: postgres:16
restart: always
environment:
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- POSTGRES_NON_ROOT_USER
- POSTGRES_NON_ROOT_PASSWORD
volumes:
- db_storage:/var/lib/postgresql/data
- ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
healthcheck:
test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 5s
timeout: 5s
retries: 10
n8n:
image: docker.n8n.io/n8nio/n8n
restart: always
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
ports:
- 5678:5678
links:
- postgres
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
After you edit the file to get the env variables right, you just need to pass that YAML to compose2nix
and watch the magic happens. You don't even need to install compose2nix
: a nix shell
or nix run
will do the trick:
On the directory containing your docker-compose.yml
, you can nix run github:aksiksi/compose2nix -project=n8n
where n8n
is just the name you want to give this project. Could be anything you want, and it will become part of the systemd
service name. The default output is a docker-compose.nix
file that you can call from your configuration.nix
or from a flake. My configuration.nix
looks like this, for example:
{ config, pkgs, lib, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
./podman.nix
./nvim.nix
./shell.nix
./postgres.nix
./gitea.nix
./traefik.nix
./redis.nix
./n8n.nix
./node_exporter.nix
./vpn.nix
./lldap.nix
];
# Bootloader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.efi.efiSysMountPoint = "/boot/efi";
In this config, I just copied the docker-compose.nix
output to /etc/nixos/
, renamed it n8n.nix
, and added it to the imports
section of /etc/nixos/configuration.nix
. You can also use the -inputs
argument to specify the compose file name if you have something not called docker-compose.yml
, and/or the -output
argument to specify the path for the output file:
nix run github:aksiksi/compose2nix -- -project=test -output=name_I_like.nix
Will generate a name_I_like.nix
file in the current directory.
From here, my fellow Cola Gangster, it's just a matter of sudo nixos-rebuild switch
, and enjoying the show.
Doing a sudo systemctl list-units *n8n*
should yield (\*n8n\* if you're using zsh):
UNIT LOAD ACTIVE SUB DESCRIPTION
podman-n8n-n8n.service loaded active running podman-n8n-n8n.service
podman-network-n8n_default.service loaded active exited podman-network-n8n_default.service
podman-compose-n8n-root.target loaded active active Root target generated by compose2nix.
Legend: LOAD → Reflects whether the unit definition was properly loaded.
ACTIVE → The high-level unit activation state, i.e. generalization of SUB.
SUB → The low-level unit activation state, values depend on unit type.
3 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.
Now you can do all the nifty systemctl
stuff: stop, restart, status, etc. Check out the project's Github page for info on how to update and auto-update your containers. See you on the next one, gangsters!