Docker & ContainersXXVII · Firewallsnftables
nftables hosts — which ruleset is actually in charge
What you'll learn
- Distinguish iptables-nft from a native nftables firewall backend
- Read Docker rules out of nft list ruleset
- Recognise the legacy/nft split-ruleset failure
- Place your own nftables base chain relative to Docker
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-11
“This host uses nftables” describes two completely different situations, and they need different answers. Establish which one you are in before you write a rule.
Situation 1: nftables kernel, iptables tooling
This is the common case on every current distribution. iptables is
a compatibility front end — iptables-nft — that translates your
rules into nftables and loads them into a table named ip filter or
ip nat. There is no legacy x_tables code in the path.
Docker’s rules go through the same front end. So on a modern
“nftables host”, Docker’s chains are nftables rules that happen to be
named DOCKER-USER, DOCKER-FORWARD and so on. Everything in
docker-user-chain-and-rule-persistence applies unchanged.
$ iptables --version
docker info --format 'Firewall Backend: {{.FirewallBackend.Driver}}'iptables v1.8.11 (nf_tables)
Firewall Backend: iptablesYou can read Docker’s rules with either tool. They are the same rules:
sudo iptables -S DOCKER-USER
sudo nft list chain ip filter DOCKER-USER
sudo nft list ruleset | head -60The split-ruleset failure
iptables and iptables-legacy are two different binaries writing
to two different subsystems. If Docker is using one and your tooling
or a leftover boot script is using the other, you have two rulesets,
each invisible to the other’s tooling. iptables -S shows your
rules, the kernel enforces a different set, and every diagnosis you
make from that output is wrong.
iptables --version
sudo iptables-legacy -S 2>/dev/null | head
sudo iptables-nft -S | head
update-alternatives --display iptables 2>/dev/null || true
sudo lsmod | grep -E 'ip_tables|nf_tables'If iptables-legacy -S returns anything beyond the three empty
built-in chains, something is writing to the legacy path. Find it and
migrate it. Running both is not a supported configuration and the
symptom — “the rule is there and it does nothing” — costs hours.
Situation 2: Docker’s native nftables backend
Docker 29.0 added an nftables firewall backend. It is experimental; upstream says “configuration options, behavior and implementation may all change in future releases”. Treat it as something to know about rather than something to standardise on.
{
"firewall-backend": "nftables"
}
or --firewall-backend=nftables on the daemon command line.
What changes:
- Docker creates tables named
ip docker-bridgesandip6 docker-bridges, with base chains plus per-network chains. The internal layout may change between releases, so do not build tooling that parses it. - There is no
DOCKER-USERequivalent. The chain you have been told to use for the last decade does not exist here. - It cannot be enabled while the daemon is in Swarm mode; overlay network rules have not been migrated.
docker info --format '{{.FirewallBackend.Driver}}'
sudo nft list tables
sudo nft list table ip docker-bridgesPlacing your own rules
Because there is no DOCKER-USER, you do not add rules to Docker’s
tables — Docker owns them and will rewrite them. You create your own
table with a base chain at the same hook, and use priority to
decide whether it runs before or after Docker’s chain. Lower priority
values run first.
The standard named priorities are the ones every nftables document uses:
| Name | Value | Hook typically used |
|---|---|---|
raw | -300 | prerouting |
mangle | -150 | all |
dstnat | -100 | prerouting |
filter | 0 | forward, input, output |
security | 50 | filter hooks |
srcnat | 100 | postrouting |
Do not guess the number Docker uses. Read it:
sudo nft -a list table ip docker-bridges | grep -E 'hook|priority'
Then give your chain a lower value if you need to be evaluated first.
Upstream also documents --bridge-accept-fwmark, which lets a
firewall mark set elsewhere override Docker’s drop rules — useful
when your policy lives in a mangle chain earlier in the path.
Sanity check
Knowledge check · 4 questions
Q1. On a host where iptables --version reports (nf_tables) and docker info reports the iptables firewall backend, where are Docker rules visible?
Q2. A hand-written table inet filter has an input chain with policy drop. Why does it not block a published container port?
Q3. Which statements about Docker 29 native nftables backend are correct? Select all that apply.
Q4. If iptables-legacy -S and iptables-nft -S both return rules, one of the two rulesets is being silently ignored by whoever reads only the other.
Passing score: 75%. Answers are checked in this browser.