NixOS config for docker containers using rootless podman

Here is my podman configuration. (Edit: removed link and included file).

# Podman services
{pkgs, ...}: let
  podman-py = p:
    with p; [
      (
        buildPythonPackage rec {
          pname = "podman";
          version = "4.5.1";
          src = fetchPypi {
            inherit pname version;
            sha256 = "sha256-znZnjuOFIy6YiwbtVXoCFNnNdtVJldWpDUNXC13RygQ=";
          };
          doCheck = false;
          propagatedBuildInputs = [
            # Specify dependencies
            pkgs.python310Packages.pyxdg
            pkgs.python310Packages.requests
            pkgs.python310Packages.setuptools
            pkgs.python310Packages.sphinx
            pkgs.python310Packages.tomli
            pkgs.python310Packages.urllib3
            pkgs.python310Packages.wheel
          ];
        }
      )
    ];
in {
  environment.systemPackages = with pkgs; [
    (python310.withPackages podman-py)
  ];
  environment.shellAliases = {
    pps = "podman ps --format 'table {{ .Names }}\t{{ .Status }}' --sort names";
    pclean = "podman ps -a | grep -v 'CONTAINER\|_config\|_data\|_run' | cut -c-12 | xargs podman rm 2>/dev/null";
    piclean = "podman images | grep '<none>' | grep -P '[1234567890abcdef]{12}' -o | xargs -L1 podman rmi 2>/dev/null";
  };

  systemd.services.pod-cloud = {
    description = "Start podman 'nextcloud' pod";
    wants = ["network-online.target"];
    after = ["network-online.target"];
    requiredBy = ["podman-mariadb.service" "podman-nextcloud.service" "podman-redis.service"];
    unitConfig = {
      RequiresMountsFor = "/run/containers";
    };
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "-${pkgs.podman}/bin/podman pod create cloud";
    };
    path = [pkgs.zfs pkgs.podman];
  };
  systemd.services.pod-download = {
    description = "Start podman 'download' pod";
    wants = ["network-online.target"];
    after = ["network-online.target"];
    requiredBy = ["podman-jackett.service" "podman-radarr.service" "podman-sabnzbd.service" "podman-sonarr.service"];
    unitConfig = {
      RequiresMountsFor = "/run/containers";
    };
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "-${pkgs.podman}/bin/podman pod create download";
    };
    path = [pkgs.zfs pkgs.podman];
  };
  systemd.services.pod-flux = {
    description = "Start podman 'flux' pod";
    wants = ["network-online.target"];
    after = ["network-online.target"];
    requiredBy = ["podman-miniflux.service" "podman-postgres.service"];
    unitConfig = {
      RequiresMountsFor = "/run/containers";
    };
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "-${pkgs.podman}/bin/podman pod create --dns=192.168.1.1 flux";
    };
    path = [pkgs.podman pkgs.zfs];
  };
  systemd.services.pod-wireguard = {
    description = "Start podman 'wg' pod";
    wants = ["network-online.target"];
    after = ["network-online.target"];
    requiredBy = ["podman-wireguard-client.service" "podman-qbitorrent.service" "podman-socks-proxy.service"];
    unitConfig = {
      RequiresMountsFor = "/run/containers";
    };
    serviceConfig = {
      Type = "oneshot";
      # 8081 - qbitorrent, 2222 - socks-proxy
      ExecStart = "-${pkgs.podman}/bin/podman pod create -p 8081:8081 -p 2222:22 wg";
    };
    path = [pkgs.zfs pkgs.podman];
  };
  systemd.timers."memories" = {
    wantedBy = ["timers.target"];
    timerConfig = {
      OnCalendar = "daily";
      OnBootSec = "5m";
      Unit = "memories.service";
    };
  };
  systemd.services."memories" = {
    script = ''
      ${pkgs.podman}/bin/podman run --rm \
      -v /home/firecat53/docs/family/scott/wiki/diary:/data/journal \
      -v /mnt/media/pictures/Family\ Pictures:/data/pictures \
      -v /srv/rss:/srv/rss \
      memories
    '';
    path = [pkgs.podman pkgs.zfs];
  };
  systemd.timers."nextcloud_cron" = {
    wantedBy = ["timers.target"];
    timerConfig = {
      OnBootSec = "2m";
      OnUnitActiveSec = "5m";
      Unit = "nextcloud_cron.service";
    };
  };
  systemd.services."nextcloud_cron" = {
    bindsTo = ["podman-nextcloud.service" "podman-mariadb.service" "podman-redis.service"];
    after = ["network-online.target" "podman-nextcloud.service" "podman-mariadb.service" "podman-redis.service"];
    script = "${pkgs.podman}/bin/podman exec -u www-data nextcloud php -f cron.php";
    path = [pkgs.podman pkgs.zfs];
  };
  systemd.timers."nextcloud_files_update" = {
    wantedBy = ["timers.target"];
    timerConfig = {
      OnBootSec = "2m";
      OnUnitActiveSec = "15m";
      Unit = "nextcloud_files_update.service";
    };
  };
  systemd.services."nextcloud_files_update" = {
    bindsTo = ["podman-nextcloud.service" "podman-mariadb.service" "podman-redis.service"];
    after = ["network-online.target" "podman-nextcloud.service" "podman-mariadb.service" "podman-redis.service"];
    script = ''
      ${pkgs.podman}/bin/podman exec -u www-data nextcloud ./occ files:scan -q --all
      ${pkgs.podman}/bin/podman exec -u www-data nextcloud ./occ memories:index -q
      ${pkgs.podman}/bin/podman exec -u www-data nextcloud ./occ preview:pre-generate
    '';
    path = [pkgs.podman pkgs.zfs];
  };
  systemd.timers."picture_copy" = {
    enable = true;
    wantedBy = ["timers.target"];
    timerConfig = {
      OnBootSec = "5m";
      OnCalendar = "daily";
      Unit = "picture_copy.service";
    };
  };
  systemd.services."picture_copy" = {
    script = "${pkgs.python3}/bin/python3 /home/firecat53/docs/family/scott/src/scripts.git/bin/pix.py";
    path = [pkgs.python3 pkgs.rsync pkgs.exiftool];
    serviceConfig = {
      User = "firecat53";
    };
  };
  systemd.timers."podman_db_backup" = {
    wantedBy = ["timers.target"];
    timerConfig = {
      OnBootSec = "5m";
      OnCalendar = "daily";
      RandomizedDelaySec = "15m";
      FixedRandomDelay = true;
      Unit = "podman_db_backup.service";
    };
  };
  systemd.services."podman_db_backup" = {
    script = let
      python3 = pkgs.python3.withPackages podman-py;
    in "${python3.interpreter} /home/firecat53/docs/family/scott/src/scripts.git/bin/podman_db_backup_homeserver.py";
    path = [pkgs.python3];
  };

  ## Podman containers

  virtualisation.podman = {
    enable = true;
    extraPackages = [pkgs.zfs];
  };
  virtualisation.containers.storage.settings = {
    storage = {
      driver = "zfs";
      graphroot = "/var/lib/containers/storage";
      runroot = "/run/containers/storage";
    };
  };
  virtualisation.oci-containers.containers = {
    gollum = {
      image = "gollum";
      autoStart = true;
      cmd = ["--allow-uploads" "page" "--ref" "main"];
      volumes = [
        "/home/firecat53/docs/family/scott/wiki:/home/gollum/wiki"
        "/home/firecat53/.gitconfig:/home/gollum/.gitconfig:ro"
      ];
      user = "1000:100";
      extraOptions = [
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.gollum.rule=Host(`gollum.domain.com`)"
        "--label=traefik.http.routers.gollum.entrypoints=websecure"
        "--label=traefik.http.routers.gollum.tls.certResolver=le"
        "--label=traefik.http.routers.gollum.middlewares=auth-gollum"
        "--label=traefik.http.middlewares.auth-gollum.basicauth.users=firecat53:$apr1$xxxxxx"
        "--label=traefik.http.services.gollum.loadbalancer.server.port=4567"
      ];
    };
    jackett = {
      image = "jackett";
      autoStart = true;
      user = "1000:100";
      extraOptions = [
        "--init=true"
        "--pod=download"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.jackett.rule=Host(`jackett.domain.com`)"
        "--label=traefik.http.routers.jackett.entrypoints=websecure"
        "--label=traefik.http.routers.jackett.tls.certResolver=le"
        "--label=traefik.http.routers.jackett.middlewares=headers"
        "--label=traefik.http.services.jackett.loadbalancer.server.port=9117"
      ];
      volumes = ["jackett_config:/config" "/mnt/downloads:/data"];
    };
    # Note: jellyfin server discovery doesn't work without host networking (ports 7359 and 1900 udp)
    jellyfin = {
      image = "jellyfin";
      autoStart = true;
      user = "1000:100";
      ports = ["1900:1900/udp" "7359:7359/udp"];
      extraOptions = [
        "--device=/dev/dri"
        "--init=true"
        "--tz=local"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.jellyfin.rule=Host(`jellyfin.domain.com`)"
        "--label=traefik.http.routers.jellyfin.entrypoints=websecure"
        "--label=traefik.http.routers.jellyfin.tls.certResolver=le"
        "--label=traefik.http.routers.jellyfin.middlewares=headers"
        "--label=traefik.http.services.jellyfin.loadbalancer.server.port=8096"
      ];
      volumes = [
        "jellyfin_config:/config"
        "jellyfin_cache:/cache"
        "/mnt/media:/media"
        "/mnt/downloads:/downloads"
      ];
    };
    mariadb = {
      image = "docker.io/library/mariadb:latest";
      autoStart = true;
      user = "mysql:mysql";
      cmd = ["--transaction-isolation=READ-COMMITTED" "--log-bin=msqyld-bin" "--binlog-format=ROW"];
      extraOptions = ["--pod=cloud"];
      volumes = ["mariadb_data:/var/lib/mysql"];
      environment = {
        MYSQL_DATABASE = "nextcloud";
        MYSQL_USER = "nextcloud";
        MYSQL_PASSWORD = "xxxxx";
        MYSQL_ROOT_PASSWORD = "yyyyyy";
      };
      dependsOn = ["redis"];
    };
    miniflux = {
      image = "miniflux";
      autoStart = true;
      user = "1000:100";
      environment = {
        DATABASE_URL = "user=miniflux password=miniflux dbname=miniflux sslmode=disable host=postgres";
        POLLING_FREQUENCY = "15";
        RUN_MIGRATIONS = "1";
      };
      dependsOn = ["postgres"];
      extraOptions = [
        "--dns=192.168.1.1"
        "--pod=flux"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.miniflux.rule=Host(`rss.domain.com`)"
        "--label=traefik.http.routers.miniflux.entrypoints=websecure"
        "--label=traefik.http.routers.miniflux.tls.certResolver=le"
        "--label=traefik.http.routers.miniflux.middlewares=headers"
        "--label=traefik.http.services.miniflux.loadbalancer.server.port=8080"
      ];
    };
    nextcloud = {
      image = "nextcloud:local";
      autoStart = true;
      user = "1000:100";
      dependsOn = ["mariadb" "redis"];
      environment = {
        MYSQL_HOST = "127.0.0.1";
        REDIS_HOST = "127.0.0.1";
        TRUSTED_PROXIES = "10.88.0.1/24";
        NEXTCLOUD_TRUSTED_DOMAINS = "nc.domain.com";
        MAIL_DOMAIN = "firecat53.net";
        OVERWRITEHOST = "nc.domain.com";
        OVERWRITEPROTOCOL = "https";
        OVERWRITECLIURL = "https://nc.domain.com";
        PHP_MEMORY_LIMIT = "2G";
        PHP_UPLOAD_LIMIT = "2G";
      };
      extraOptions = [
        "--device=/dev/dri"
        "--init=true"
        "--pod=cloud"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.nextcloud.rule=Host(`nc.domain.com`)"
        "--label=traefik.http.routers.nextcloud.entrypoints=websecure"
        "--label=traefik.http.routers.nextcloud.tls.certResolver=le"
        "--label=traefik.http.routers.nextcloud.middlewares=headers,nextcloud-redirectregex@file"
        "--label=traefik.http.services.nextcloud.loadbalancer.server.port=80"
        "--sysctl=net.ipv4.ip_unprivileged_port_start=80"
      ];
      volumes = ["nextcloud_config:/var/www/html" "/mnt/media:/data"];
    };
    nginx = {
      image = "nginx";
      autoStart = true;
      volumes = ["/srv/http:/var/www/misc:ro" "/srv/rss:/var/www/rss:ro"];
      extraOptions = [
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.nginx.rule=Host(`domain.com`) && ((PathPrefix(`/misc`) || PathPrefix(`/rss`)))"
        "--label=traefik.http.routers.nginx.entrypoints=websecure"
        "--label=traefik.http.routers.nginx.tls.certResolver=le"
        "--label=traefik.http.routers.nginx.middlewares=headers"
        "--label=traefik.http.services.nginx.loadbalancer.server.port=8080"
      ];
    };
    podman-exporter = {
      image = "podman-exporter";
      autoStart = true;
      ports = ["9882:9882"];
      volumes = ["/run/podman/podman.sock:/run/podman/podman.sock"];
      environment = {
        CONTAINER_HOST = "unix:///run/podman/podman.sock";
      };
    };
    postgres = {
      image = "docker.io/library/postgres:15-alpine";
      autoStart = true;
      user = "70:70";
      extraOptions = ["--pod=flux"];
      volumes = ["miniflux_data:/var/lib/postgresql/data"];
    };
    qbittorrent = {
      image = "qbittorrent";
      autoStart = true;
      user = "1000:100";
      dependsOn = ["wireguard-client"];
      environment = {
        QBT_WEBUI_PORT = "8081";
      };
      extraOptions = [
        "--init=true"
        "--network=container:wireguard-client"
        "--pod=wg"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.qbittorrent.rule=Host(`qbt.domain.com`)"
        "--label=traefik.http.routers.qbittorrent.entrypoints=websecure"
        "--label=traefik.http.routers.qbittorrent.tls.certResolver=le"
        "--label=traefik.http.routers.qbittorrent.middlewares=headers"
        "--label=traefik.http.services.qbittorrent.loadbalancer.server.port=8081"
      ];
      volumes = ["qbittorrent_config:/config" "/mnt/downloads:/data"];
    };
    redis = {
      image = "docker.io/library/redis:latest";
      autoStart = true;
      user = "1000:100";
      cmd = ["redis-server" "--save" "59" "1" "--loglevel" "warning"];
      extraOptions = ["--pod=cloud"];
      volumes = ["redis_data:/data"];
    };
    samba = {
      image = "samba";
      autoStart = true;
      ports = ["137:137/udp" "138:138/udp" "139:139" "445:445"];
      volumes = [
        "samba_config:/config"
        "/mnt/downloads:/mnt/downloads"
        "/mnt/media:/mnt/media"
        "/home:/home"
      ];
    };
    plex = {
      image = "docker.io/plexinc/pms-docker:latest";
      autoStart = true;
      ports = ["32400:32400" "32410:32410" "32412:32412" "32413:32413" "32414:32414"];
      volumes = ["plex_config:/config" "/mnt/downloads:/data" "/mnt/media:/mnt/media"];
      environment = {
        TZ = "America/Los_Angeles";
        PLEX_CLAIM = "claim-sg6qnQnT5p7fB8hnFmp2";
        PLEX_UID = "1000";
        PLEX_GID = "100";
        ADVERTISE_IP = "http://192.168.1.2:32400/";
        ALLOWED_NETWORKS = "192.168.1.0/24";
      };
      extraOptions = ["--device=/dev/dri" "--init=true" "--no-healthcheck"];
    };
    radarr = {
      image = "radarr";
      autoStart = true;
      user = "1000:100";
      extraOptions = [
        "--init=true"
        "--pod=download"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.radarr.rule=Host(`radarr.domain.com`)"
        "--label=traefik.http.routers.radarr.entrypoints=websecure"
        "--label=traefik.http.routers.radarr.tls.certResolver=le"
        "--label=traefik.http.routers.radarr.middlewares=headers"
        "--label=traefik.http.services.radarr.loadbalancer.server.port=7878"
      ];
      volumes = ["radarr_config:/config" "/mnt/downloads:/data"];
    };
    sabnzbd = {
      image = "sabnzbd";
      autoStart = true;
      user = "1000:100";
      extraOptions = [
        "--init=true"
        "--pod=download"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.sabnzbd.rule=Host(`sabnzbd.domain.com`)"
        "--label=traefik.http.routers.sabnzbd.entrypoints=websecure"
        "--label=traefik.http.routers.sabnzbd.tls.certResolver=le"
        "--label=traefik.http.routers.sabnzbd.middlewares=headers"
        "--label=traefik.http.services.sabnzbd.loadbalancer.server.port=8080"
      ];
      volumes = ["sabnzbd_config:/config" "/mnt/downloads:/data"];
    };
    socks-proxy = {
      image = "socks-proxy";
      autoStart = true;
      dependsOn = ["wireguard-client"];
      extraOptions = [
        "--pod=wg"
        "--network=container:wireguard-client"
      ];
    };
    sonarr = {
      image = "sonarr";
      autoStart = true;
      user = "1000:100";
      extraOptions = [
        "--init=true"
        "--pod=download"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.sonarr.rule=Host(`sonarr.domain.com`)"
        "--label=traefik.http.routers.sonarr.entrypoints=websecure"
        "--label=traefik.http.routers.sonarr.tls.certResolver=le"
        "--label=traefik.http.routers.sonarr.middlewares=headers"
        "--label=traefik.http.services.sonarr.loadbalancer.server.port=8989"
      ];
      volumes = ["sonarr_config:/config" "/mnt/downloads:/data"];
    };
    syncthing = {
      image = "syncthing";
      autoStart = true;
      ports = ["22000:22000" "22000:22000/udp" "21027:21027/udp"];
      volumes = [
        "syncthing_config:/config"
        "/mnt/media:/mnt/media"
        "/srv:/srv"
        "/var/backups:/var/backups"
      ];
      extraOptions = [
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.syncthing.rule=Host(`syncthing.domain.com`)"
        "--label=traefik.http.routers.syncthing.entrypoints=websecure"
        "--label=traefik.http.routers.syncthing.tls.certResolver=le"
        "--label=traefik.http.routers.syncthing.middlewares=headers"
        "--label=traefik.http.services.syncthing.loadbalancer.server.port=8384"
      ];
    };
    traefik = {
      image = "docker.io/library/traefik:latest";
      autoStart = true;
      ports = ["80:80" "443:443"];
      volumes = ["traefik_config:/etc/traefik" "/run/podman/podman.sock:/var/run/docker.sock"];
      extraOptions = [
        "--add-host=host.docker.internal:10.88.0.1"
        "--env-file=/root/.cloudflare"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.traefik-http.rule=Host(`monitor.domain.com`)"
        "--label=traefik.http.routers.traefik-http.entrypoints=web"
        "--label=traefik.http.routers.traefik-https.rule=Host(`monitor.domain.com`)"
        "--label=traefik.http.routers.traefik-https.entrypoints=websecure"
        "--label=traefik.http.routers.traefik-https.service=api@internal"
        "--label=traefik.http.routers.traefik-https.tls.certResolver=le"
        "--label=traefik.http.routers.traefik-https.middlewares=auth,headers"
        "--label=traefik.http.middlewares.auth.basicauth.users=firecat53:$apr1$xxxxxx"
        "--label=traefik.http.middlewares.headers.headers.browserxssfilter=true"
        "--label=traefik.http.middlewares.headers.headers.contenttypenosniff=true"
        "--label=traefik.http.middlewares.headers.headers.forcestsheader=true"
        "--label=traefik.http.middlewares.headers.headers.framedeny=true"
        "--label=traefik.http.middlewares.headers.headers.customframeoptionsvalue=SAMEORIGIN"
        "--label=traefik.http.middlewares.headers.headers.sslhost=domain.com"
        "--label=traefik.http.middlewares.headers.headers.sslredirect=true"
        "--label=traefik.http.middlewares.headers.headers.stsincludesubdomains=true"
        "--label=traefik.http.middlewares.headers.headers.stspreload=true"
        "--label=traefik.http.middlewares.headers.headers.stsseconds=315360000"
      ];
    };
    transmission = {
      image = "transmission";
      autoStart = true;
      ports = ["9091:9091" "30020:30020" "30020:30020/udp"];
      volumes = ["transmission_config:/config" "/mnt/downloads:/data"];
      user = "1000:100";
      extraOptions = [
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.transmission.rule=Host(`transmission.domain.com`)"
        "--label=traefik.http.routers.transmission.entrypoints=websecure"
        "--label=traefik.http.routers.transmission.tls.certResolver=le"
        "--label=traefik.http.routers.transmission.middlewares=auth-transmission"
        "--label=traefik.http.middlewares.auth-transmission.basicauth.users=firecat53:$apr1$xxxxxx"
        "--label=traefik.http.services.transmission.loadbalancer.server.port=9091"
      ];
    };
    unifi = {
      image = "unifi";
      autoStart = true;
      volumes = ["unifi_config:/config"];
      ports = ["3478:3478/udp" "6789:6789" "8080:8080" "8843:8843" "8880:8880" "10001:10001/udp"];
      extraOptions = [
        "--init=true"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.unifi.rule=Host(`unifi.domain.com`)"
        "--label=traefik.http.routers.unifi.entrypoints=websecure"
        "--label=traefik.http.routers.unifi.tls.certResolver=le"
        "--label=traefik.http.routers.unifi.middlewares=headers"
        "--label=traefik.http.services.unifi.loadbalancer.server.port=8443"
        "--label=traefik.http.services.unifi.loadbalancer.server.scheme=https"
        "--label=traefik.http.services.unifi.loadbalancer.passhostheader=true"
      ];
    };
    vaultwarden = {
      image = "vaultwarden";
      autoStart = true;
      user = "1000:100";
      volumes = ["vaultwarden_data:/data"];
      environment = {
        DOMAIN = "https://bw.domain.com";
        SMTP_HOST = "smtp.domain.com";
        SMTP_FROM = "from@domain.com";
        SMTP_PORT = "587";
        SMTP_SECURITY = "starttls";
        SMTP_USERNAME = "me@domain.com";
        SMTP_PASSWORD = "xxxxxx";
        ROCKET_PORT = "8080";
      };
      extraOptions = [
        "--init=true"
        "--label=traefik.enable=true"
        "--label=traefik.http.routers.vaultwarden.rule=Host(`bw.domain.com`)"
        "--label=traefik.http.routers.vaultwarden.entrypoints=websecure"
        "--label=traefik.http.routers.vaultwarden.tls.certResolver=le"
        "--label=traefik.http.routers.vaultwarden.middlewares=headers"
        "--label=traefik.http.services.vaultwarden.loadbalancer.server.port=8080"
      ];
    };
    wireguard-client = {
      image = "wireguard-client";
      autoStart = true;
      volumes = ["wireguard_config:/etc/wireguard"];
      environment = {
        LOCAL_NETWORKS = "10.1.1.0/24,192.168.1.0/24";
      };
      extraOptions = [
        "--cap-add=NET_ADMIN"
        "--cap-add=NET_RAW"
        "--dns=172.16.0.1"
        "--pod=wg"
      ];
    };
  };
  # For wireguard-client
  boot.kernel.sysctl."net.ipv4.conf.all.src_valid_mark" = 1;
}
4 Likes