falcon-nest/literal/virtualization/setting-up-podman-and-portainer.org
2026-06-21 01:36:08 -05:00

5.2 KiB

Setting up Podman and Portainer

Set up Podman   factcheck

{ pkgs, lib, config, ... }: {
  <<podman-utils>>
}

NixOS seems to jive better with Podman, probably due to its ability to run without a daemon, which usually needs root access.

I'll be looking to run several containers the NixOS way. What does that mean? Well, no yaml, just Nix. Nix to declare the containers and Nix to declare the systemd scripts that help launch and scrap containers. However, there are some configuration options that we need to set for the VM that will host them.

First, I'll enable podman and specify it as the backend…:

virtualisation.podman.enable = true;
virtualisation.oci-containers.backend = "podman";

…and enable some sane defaults, like ensuring compatibility with Docker (so 3rd-party images play nice) and enabling automatic pruning of old images:

virtualisation.podman.autoPrune.enable = true;
virtualisation.podman.dockerCompat = true;

Then I'll set up DNS for all Podman networks:

networking.firewall.interfaces = let
  matchAll =
    if !config.networking.nftables.enable then "podman+" else "podman*";
in { "${matchAll}".allowedUDPPorts = [ 53 ]; };

Here's the completed code:

virtualisation = {
  oci-containers.backend = "podman";
  podman = {
    enable = true;
    autoPrune = {
      enable = true;
      flags = [ "--all" "--filter 'until=24h'" "--force" ];
    };
    dockerCompat = true;
  };
};

networking.firewall.interfaces = let
  matchAll =
    if !config.networking.nftables.enable then "podman+" else "podman*";
in { "${matchAll}".allowedUDPPorts = [ 53 ]; };

# If you want to trigger this service manually, run sudo systemctl restart update-containers.service.
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
    '');
  };
};

Set up Portainer

{ pkgs, lib, config, ... }: {
  <<portainer-config>>
}

This virtual machine will feature a single Portainer instance to act as "mission control" for my Debian-based fileserver and a Raspberry Pi running some public-facing utilities that run a variety of Docker containers.

I want to be careful to run this instance properly and securely. I'll need to create a user for the Portainer container.

users.users.portainer = {
  isSystemUser = true;
  group = "podman";
  home = "/var/lib/portainer";
  createHome = true;
};

Now for the actual container configuration, and this is courtesy of a great tool called compose2nix. compose2nix takes your standard Docker Compose YAML file and generates a configuration for the container.

virtualisation.oci-containers.containers."portainer" = {
  podman.user = "portainer";
  image = "portainer/portainer-ce:lts";
  volumes = [
    "/var/run/docker.sock:/var/run/docker.sock:rw"
    "portainer_portainer_data:/data:rw"
  ];
  ports = [ "8000:8000/tcp" "9443:9443/tcp" "9000:9000/tcp" ];
  log-driver = "journald";
  extraOptions =
    [ "--network-alias=portainer" "--network=portainer_default" ];
};

compose2nix also takes care of setting up the systemd service that's responsible for restarting the container when necessary, as well as setting up the volumes and networks the container will use.

# Networks
systemd.services."podman-network-portainer_default" = {
  path = [ pkgs.podman ];
  serviceConfig = {
    Type = "oneshot";
    RemainAfterExit = true;
    ExecStop = "podman network rm -f portainer_default";
  };
  script = ''
    podman network inspect portainer_default || podman network create portainer_default
  '';
  partOf = [ "podman-compose-portainer-root.target" ];
  wantedBy = [ "podman-compose-portainer-root.target" ];
};

# Volumes
systemd.services."podman-volume-portainer_portainer_data" = {
  path = [ pkgs.podman ];
  serviceConfig = {
    Type = "oneshot";
    RemainAfterExit = true;
  };
  script = ''
    podman volume inspect portainer_portainer_data || podman volume create portainer_portainer_data
  '';
  partOf = [ "podman-compose-portainer-root.target" ];
  wantedBy = [ "podman-compose-portainer-root.target" ];
};

# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-portainer-root" = {
  unitConfig = { Description = "Root target generated by compose2nix."; };
  wantedBy = [ "multi-user.target" ];
};

All that's left is to import these into this VM's configuration:

imports = [
  ../virtualization/podman-utils.nix
  ../virtualization/portainer.nix
];