The four ci.env keys (INFINITO_DNS_IP, INFINITO_DOMAIN, INFINITO_DOMAIN_RE,
INFINITO_IP4) were only honored by docker compose --env-file, so processes
that sourced .env via load.sh saw INFINITO_IP4 as unset. As a result
networks.internet.ip4 in the Ansible playbook fell through to the
127.0.0.1 default and the DinD DNS-check handler failed on the deploy
server matrix.
Moving the defaults into env/default.env makes them flow through the same
.env / load.sh / os.environ chain as every other INFINITO_* key, so
\${INFINITO_IP4} in compose.yml, lookup('env', 'INFINITO_IP4') in
group_vars, and envsubst over the CoreDNS Corefile template all see the
same value deterministically.
Side fix: utils/env/parser.py now undoes the writer's \\\\/\\" escaping for
quoted values, closing a round-trip gap that surfaced once
INFINITO_DOMAIN_RE (the only value containing a literal backslash) started
flowing through the generator.
All --env-file env/ci.env call sites (compose.py, down.py, install/lint/
test wrappers, resolve scripts, test-dns workflow + script) are removed.
coredns.py now reads .env via parse_static_env, docs and unit/integration
tests follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.1 KiB
Network 🌐
When compose pulls hang, image builds fail to resolve registry manifests, make install-lint times out on a TLS handshake, or DNS lookups return surprising addresses, the cause is almost always one of three things: MTU mismatch, IPv6 misrouting, or DNS / proxy interference.
Diagnose tool 🕵️
make debug-network
Runs __main__.py inside the running infinito container and produces a structured DNS / TCP / TLS / PMTU report against the standard infrastructure hosts (github.com, ghcr.io, registry-1.docker.io, auth.docker.io, pypi.org, files.pythonhosted.org, registry.npmjs.org, objects.githubusercontent.com, raw.githubusercontent.com), separately for IPv4 and IPv6 when available. Extra hosts can be appended via INFINITO_NET_DEBUG_HOSTS="example.com api.example.org" make debug-network.
On first run it self-installs iputils-ping + iproute2 via the container's package manager (Debian / Ubuntu apt, Arch pacman, Fedora / CentOS / RHEL dnf). The installer waits up to two minutes for an apt-lock if a parallel deploy is running. IPv6 probes are auto-skipped when the bridge has no usable IPv6 default route.
For each host the script reports four signals:
| Signal | Means | Fails when |
|---|---|---|
DNS |
socket.getaddrinfo resolution time + returned addresses |
resolver unreachable, no records, AAAA/A missing |
TCP |
socket.connect time to the resolved address on :443 |
routing missing, firewall drops, upstream not listening |
TLS |
ssl.wrap_socket handshake time, negotiated protocol, certificate CN |
MTU drops mid-handshake, MITM cert mismatch, CA bundle missing, server-side issue |
PMTU |
largest ICMP-DF payload that survives (= path MTU − 28 bytes IP+ICMP header) | container lacks ping after auto-install (auto-skipped), or path drops every probe size |
The classic failure shape (DNS [OK], TCP [OK], TLS [FAIL] TimeoutError after 8s) is an MTU mismatch, not a TLS-layer bug. Small packets (TCP-SYN, DNS replies) survive; the larger TLS-ServerHello with the certificate chain is dropped silently because the path MTU is below the bridge MTU and ICMP-needs-frag is filtered upstream. When auth.docker.io is OK but ghcr.io / registry-1.docker.io time out, the chain-length difference between hosts (Cloudflare-fronted ≈ small chain, GitHub/AWS ≈ large chain) confirms MTU as the cause rather than a blanket TLS block.
DNS resolutions to 172.30.0.4 for hosts like pypi.org are intentional: the package-cache frontend overrides those names in /etc/hosts, so traffic terminates at the local registry mirror with 0.00s DNS/TCP/TLS times instead of hitting the public registry.
MTU 📦
Docker defaults to MTU 1500. If the host outbound interface uses a smaller MTU (e.g. due to VPN, tunnels, or jumbo-frame negotiation failure), packets MAY be dropped or fragmented and TLS handshakes time out mid-flight (see the diagnose tool's classic-failure shape above).
The local development compose stack SHOULD inherit the host Docker MTU automatically from /etc/docker/daemon.json. If auto-detection is unavailable or wrong, override it explicitly with INFINITO_OUTER_NETWORK_MTU.
Inspect the host MTUs:
ip link show docker0
ip link show eth0
Fix via /etc/docker/daemon.json:
{"mtu": 1450}
Then restart Docker:
sudo systemctl restart docker
For a one-off local override of the compose bridge, regenerate .env with the desired value and recreate the stack cleanly:
INFINITO_OUTER_NETWORK_MTU=1400 make dotenv
make down && make up
A clean down && up is required after the change: make up alone recreates the bridge but compose then re-attaches existing containers with dynamic IPs instead of the static default.env ones, which breaks CoreDNS.
IPv6 🔢
If IPv6 is active but misconfigured, Docker MAY attempt to pull over IPv6 and fail.
ip -6 addr show
curl -6 https://registry-1.docker.io/v2/
The recommended way to disable IPv6 for local development is via Make:
make disable-ipv6
This also restarts docker.service and then calls make refresh, so the running Infinito development stack is recreated when one is active and the new setting reaches fresh container network namespaces.
To restore the original IPv6 settings afterwards:
make restore-ipv6
This restore path also restarts docker.service and then calls make refresh, so the running Infinito development stack is recreated when one is active and the restored setting reaches fresh container network namespaces.
You can also refresh the running local stack directly after host-level changes:
make refresh
Alternatively, disable IPv6 only in Docker via /etc/docker/daemon.json:
{"ipv6": false}
General Connectivity 🔌
ping -c 3 registry-1.docker.io
curl -v https://registry-1.docker.io/v2/
Check firewall rules, proxy settings, and DNS configuration. The proxy env vars and CA bundle summary sections of make debug-network surface the same information from inside the container.