# Docker Swarm node build type. # # Produces NixOS hosts that form a Docker Swarm manager cluster. Two nodes # (ha-docker-1, ha-docker-2) are both managers so either can accept Docker # API and `docker stack` commands. # # Key differences from the existing `docker` build type (used by CT 105): # - nextcloud-cron-job.nix is EXCLUDED — `docker exec` breaks in swarm # because the target container may be on the other node. The cron job # is replaced by a nextcloud-cron sidecar in the Nextcloud stack. # See docs/internal/docker-swarm-cutover.md. # - traefik/rotate-logs.nix is EXCLUDED — log rotation moves to Docker's # json-file log driver (max-size/max-file on the Traefik service # definition). See docs/internal/docker-swarm-cutover.md. # - raspi/mount-data.nix is EXCLUDED — specific to CT 105's backup role. # - Swarm firewall ports (2377/tcp, 7946/tcp+udp, 4789/udp) are opened # on the swarm NIC (ens20/vmbr3) only. # - checkReversePath = "loose" is required for the Swarm ingress routing # mesh: VXLAN return traffic is asymmetric (arrives ens20, exits ens18). # - beszel-agent is enabled for host-level monitoring. { pkgs, vars, ... }: { # Pin Docker Engine to version 29, matching CT 105, so image layers cached # on NFS volumes remain compatible across old and new hosts. nixpkgs.overlays = [ (final: prev: { docker = prev.docker_29; docker_cli = prev.docker_29; }) ]; imports = [ ../docker/enable-service.nix ../docker/mount-data.nix ../docker/docker-health-to-gotify.nix ../beszel/enable-agent.nix ../services/enable-rpcbind.nix ]; environment.systemPackages = with pkgs; [ nfs-utils ]; boot.supportedFilesystems = [ "nfs" ]; systemd.tmpfiles.rules = [ # Symlink ~/docker → NFS config mount so the docker-health-to-gotify # script (and operator convenience) resolves ~/docker/... correctly. "L+ /home/${vars.primaryUser}/docker - - - - ${vars.nfsShares.dockerConfig.mountpoint}" "d /mnt/docker 0755 ${vars.primaryUser} users -" ]; users.users.${vars.primaryUser}.extraGroups = [ "docker" ]; networking.firewall = { # LAN-facing service ports — same as the existing docker build type. allowedTCPPorts = [ vars.ports.dockerHttp vars.ports.dockerHttps vars.ports.dockerExtra vars.ports.beszelHub ]; # Swarm inter-node ports restricted to the swarm NIC (ens20/vmbr3). # vmbr3 is an isolated internal bridge — no LAN reachability. interfaces.${vars.haDockerSwarmInterface} = { allowedTCPPorts = [ vars.ports.dockerSwarmMgmt # 2377 — Raft + cluster management vars.ports.dockerSwarmDisc # 7946 — Serf gossip (TCP half) ]; allowedUDPPorts = [ vars.ports.dockerSwarmDisc # 7946 — Serf gossip (UDP half) vars.ports.dockerSwarmVxlan # 4789 — VXLAN overlay data path ]; }; # Docker Swarm ingress routing mesh creates asymmetric routes: a request # arrives on ens18 (LAN) for a container that lives on ens20's VXLAN # overlay; the return path differs from the incoming interface. Strict # rp_filter drops these packets. "loose" allows them. checkReversePath = "loose"; }; }