From e569a564178921dca7ceb487874ad0893da7c98d Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 29 Oct 2026 21:04:22 -0600 Subject: [PATCH] Reserve fixed IPs on the lava network so Caddy can always start lava was created by hand without an ip_range, so any container could be given Caddy's fixed IP (172.20.0.5). After a reboot the containers race for the low addresses and Caddy often fails with "Address already in use". - The docker role now creates lava as 172.20.0.0/16 with dynamic addresses limited to 172.20.128.0/17, so 172.20.0.x is free for fixed IPs, and warns if the network still has the old layout. - Gitea gets fixed IP 172.20.0.6 once the network has the reserved range, since its ufw-docker SSH rule points at its IP. - ufw-docker allow rules for Caddy (80, 443) and Gitea SSH are managed by the playbook and follow IP changes. - migrate-lava-network.yml recreates the existing network once: it takes down every compose project on lava (volumes kept), recreates the network, and brings them back up, Caddy first. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 3 + group_vars/all/infrastructure.yml | 8 +- migrate-lava-network.yml | 129 ++++++++++++++++++++ roles/docker/tasks/development/gitea.yml | 9 +- roles/docker/tasks/infrastructure/caddy.yml | 10 +- roles/docker/tasks/main.yml | 40 ++++++ roles/docker/templates/gitea-compose.yml.j2 | 5 + 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 migrate-lava-network.yml diff --git a/CLAUDE.md b/CLAUDE.md index fd69e61..dbf883e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -112,6 +112,9 @@ Many services depend on Authentik for SSO. When deploying new services, consider - Network connectivity requirements within Docker stack - Hairpinning fixes for internal service-to-service communication +### Shared Docker Network (`lava`) +Created by the docker role as `{{ docker.subnet }}` with `ip_range: {{ docker.ip_range }}`. Docker only hands out dynamic addresses from that range, so `172.20.0.x` is reserved for fixed IPs: Caddy (`docker.hairpin_ip`, .5) and Gitea (`docker.gitea_ip`, .6). Both need fixed IPs because ufw-docker firewall rules point at container IPs. A network without the range (the 2024 original) is migrated once with `migrate-lava-network.yml`. + ### Hairpinning Resolution Services inside Docker containers cannot reach external domains that resolve to the same server. Fix by adding `extra_hosts` mappings: diff --git a/group_vars/all/infrastructure.yml b/group_vars/all/infrastructure.yml index 2c1a647..8b01935 100644 --- a/group_vars/all/infrastructure.yml +++ b/group_vars/all/infrastructure.yml @@ -4,7 +4,13 @@ docker: network_name: "lava" stacks_path: "/opt/stacks" - hairpin_ip: "172.20.0.5" + hairpin_ip: "172.20.0.5" # Caddy's fixed IP + # Shared network layout. Docker only hands out addresses from ip_range, + # so 172.20.0.x is kept free for fixed IPs and can't be taken by others. + subnet: "172.20.0.0/16" + gateway: "172.20.0.1" + ip_range: "172.20.128.0/17" + gitea_ip: "172.20.0.6" # fixed so the ufw-docker SSH rule stays valid # SMTP configuration smtp: diff --git a/migrate-lava-network.yml b/migrate-lava-network.yml new file mode 100644 index 0000000..660225d --- /dev/null +++ b/migrate-lava-network.yml @@ -0,0 +1,129 @@ +--- +# One-off: recreate the shared docker network with a reserved address range. +# +# Why: lava was created without an ip_range, so any container can be given +# Caddy's fixed IP (172.20.0.5) and Caddy then fails with "Address already +# in use". Docker can't change a network's range in place, so every stack on +# it is taken down, the network is recreated, and the stacks come back up. +# Volumes (all data) are kept. Expect a few minutes of downtime for everything. +# +# Run: ansible-playbook migrate-lava-network.yml -i hosts.yml --vault-password-file vault_pass +# Then: ansible-playbook site.yml -i hosts.yml --vault-password-file vault_pass +# (gives gitea its fixed IP and fixes its firewall rule) +# +# The list of stacks is saved to /root/lava-migration-