lxd-docker-firewall-conflict — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited lxd-docker-firewall-conflict (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
When Docker and LXD share a host, Docker sets the iptables `FORWARD` chain policy to `DROP` and only ACCEPTs traffic for its own bridges. Forwarded packets from the LXD bridge (lxdbr0) match nothing and fall through to the DROP policy, so LXD containers/VMs lose all outbound internet — even though the host itself is fine and the LXD bridge has ipv4.nat=true. The fix is to ACCEPT the LXD bridge in Docker's DOCKER-USER chain and persist it.
⚠️ This is not a NAT, DNS, or LXD-config problem. The bridge's ipv4.nat=true and net.ipv4.ip_forward=1 are already correct — packets are dropped at the FORWARD hook before NAT ever applies. Don't waste time reconfiguring the bridge, DNS, or lxd init.
# Detects managed LXD bridges, adds DOCKER-USER ACCEPT rules, and installs a
# systemd unit so they survive reboots + docker restarts. Idempotent; re-runnable.
bash scripts/fix-lxd-docker-forward.shapt update / apt-get / curl times out on IPv4 (connection timed out,Failed to fetch) and/or instantly says Network is unreachable on IPv6.
packer-lxd image build errors in its first shell provisioner at apt update → `Failed to fetch ...Could not connect ... connection timed out`.
# 1. Host reaches the internet but the container does not -> forwarding/firewall, not DNS/NAT.
curl -4 -sI http://archive.ubuntu.com | head -1 # host: "HTTP/1.1 200 OK"
# 2. The smoking gun: FORWARD policy DROP + Docker jumps, and DOCKER-USER does NOT accept the LXD bridge.
sudo iptables -S FORWARD # -> "-P FORWARD DROP" and "-A FORWARD -j DOCKER-USER"
sudo iptables -S DOCKER-USER # -> only "-N DOCKER-USER" (empty): nothing accepts lxdbr0
sudo iptables -L FORWARD -n -v | head -3 # the "policy DROP" pkt/byte counter climbs while a container retries
# 3. Prove the bridge is fine (rules out the usual red herrings).
lxc network get lxdbr0 ipv4.nat # -> true
sysctl net.ipv4.ip_forward # -> net.ipv4.ip_forward = 1If FORWARD is policy DROP with Docker jumps and DOCKER-USER has no lxdbr0 ACCEPT, it's this bug.
Docker reserves the DOCKER-USER chain — evaluated first in FORWARD and never flushed by Docker — for exactly this. Accept the LXD bridge in both directions:
sudo iptables -I DOCKER-USER -i lxdbr0 -j ACCEPT # container -> internet
sudo iptables -I DOCKER-USER -o lxdbr0 -j ACCEPT # replies -> containerReplace lxdbr0 with your managed bridge name if different (lxc network list shows MANAGED=YES bridges).
Manually-added rules are lost on reboot (Docker recreates DOCKER-USER empty at boot). Do not reach for `iptables-persistent` / `netfilter-persistent` here — it snapshots Docker's volatile per-container rules and races Docker's own boot-time setup. Instead use a tiny oneshot unit ordered after docker.service that idempotently re-inserts only our rules (this is what the Quick-start script installs):
# /etc/systemd/system/lxd-docker-forward.service
[Unit]
Description=Persist LXD bridge ACCEPT rules in Docker's DOCKER-USER chain
After=docker.service
Wants=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/usr/sbin/iptables -N DOCKER-USER
ExecStart=/bin/sh -c '/usr/sbin/iptables -C DOCKER-USER -i lxdbr0 -j ACCEPT 2>/dev/null || /usr/sbin/iptables -I DOCKER-USER -i lxdbr0 -j ACCEPT'
ExecStart=/bin/sh -c '/usr/sbin/iptables -C DOCKER-USER -o lxdbr0 -j ACCEPT 2>/dev/null || /usr/sbin/iptables -I DOCKER-USER -o lxdbr0 -j ACCEPT'
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload && sudo systemctl enable --now lxd-docker-forward.servicelxc launch ubuntu:24.04 nettest
lxc exec nettest -- curl -4 -sI http://archive.ubuntu.com | head -1 # "HTTP/1.1 200 OK"
lxc delete -f nettestProve persistence actually rebuilds the rules (simulates a fresh boot's empty chain). ⚠️ Briefly drops LXD container connectivity — run only when no LXD instances need the network:
sudo iptables -F DOCKER-USER # empty it, as at boot
sudo systemctl restart lxd-docker-forward.service
sudo iptables -S DOCKER-USER # the two ACCEPT rules are backOn modern Ubuntu both Docker (iptables-nft) and LXD (its own inet lxd table) attach base chains to the kernel's forward netfilter hook. A packet must survive every base chain at that hook: LXD can accept in its own table, but Docker's ip filter FORWARD chain with policy drop then drops it anyway (in nftables, one chain's accept does not override another chain's drop at the same hook). Placing an ACCEPT in DOCKER-USER (part of the ip filter table) resolves that table's verdict to accept, so the packet is no longer dropped. NAT (ipv4.nat=true) lives in the nat table at a different hook and never runs because the packet is already gone at forward.
lxdbr0 defaults to ipv6.address=none, so the instant Network is unreachable on IPv6in logs is harmless — the real failure is the IPv4 timeout. If you enable IPv6 on the bridge, the same rules apply (the script covers every managed bridge, both directions).
MANAGED=YES bridges; override withBRIDGES="lxdbr0 lxdbr1" bash scripts/fix-lxd-docker-forward.sh.
DOCKER-USER contents, so the rules survive systemctl restart docker; only areboot empties the chain — which the systemd unit handles.
virbr0, Incus, plainbrctl/netplan bridges): same DOCKER-USER fix, just a different interface name.
"iptables": false in /etc/docker/daemon.json stops Docker managingthe firewall entirely, but then you own all of Docker's NAT/port rules yourself. The DOCKER-USER approach is the supported one.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.