Memory was tight on the box, so I stopped another project by hand to make room for a k3s install. The install pulled in br_netfilter, which flipped a kernel switch host-wide. The project I'd stopped came back up — and its containers couldn't reach each other anymore.
My first move was restarting the docker daemon, on the theory that bringing everything back up fresh would shake the problem loose. It didn't touch this one. It did reassign every container IP on a second, unrelated network, which left me with an allow-list somewhere no longer admitting the source it had been written to admit. That's its own story. This one is about the first failure, and it turned out to have nothing to do with the daemon restart at all.
The symptom held up under testing. No container on that bridge could reach any other — I checked several pairs, and they all failed the same way. Every attempt just ran until it timed out; nothing came back refused. A refused connection means a socket answered and said no. A timeout means the packet went somewhere and nothing came back, which puts the fault in the network layer, not in whatever was supposed to be listening on the other end. A different stack on the same host, on a different bridge, kept working the whole time. Whatever this was, it wasn't host-wide.
The only change
Only one thing had actually changed on the host that day: the k3s install, and specifically what it did to the kernel underneath it. k3s's own systemd unit runs ExecStartPre=-/sbin/modprobe br_netfilter — from /etc/systemd/system/k3s.service — before the service itself starts. That's k3s loading the module as shipped, not a step I added.
Loading the module isn't what matters on its own. What matters is what comes with it: br_netfilter brings net.bridge.bridge-nf-call-iptables into play, and somewhere in that install, the switch flipped — from 0 to 1, host-wide. That sysctl decides whether traffic that never leaves a bridge gets shown to iptables at all. Two containers on the same bridge talking to each other is bridge-internal traffic in the strict sense — packets forwarded between ports of the same bridge, no routing involved. At 0, the kernel never submits that traffic to iptables; nothing evaluates it. At 1, it does — the same chains that judge traffic arriving on a real interface now get asked to judge this too.
Nothing about the containers changed, and nothing about the project I'd stopped and brought back up changed either. What changed is which packets iptables now gets a say over. k3s did that on purpose: loading the module and turning on that sysctl is baked into how it starts, on any host it's installed to. It isn't a step aimed at this box specifically, and it isn't a step k3s skipped or fumbled. The install did exactly what it was built to do.
Flipping the switch, and what it doesn't prove
I tested the sysctl directly that day. net.bridge.bridge-nf-call-iptables to 0, and the containers could reach each other again — immediately, no restart, no wait. Back to 1, and they stopped again, just as fast. I flipped it a second time to be sure. Same result both directions.
That's confirmation, not a diagnosis. All it shows is that whatever's dropping this traffic sits downstream of that switch — not what that thing is, and not why it's dropping these particular packets between these particular containers instead of every packet crossing the bridge. This identifies the mechanism, not the rule. And leaving the sysctl at 0 was never a real fix — the switch is host-wide, and k3s's unit sets it unconditionally every time it starts, so leaving it at