This blog post was initially intended to explain how to migrate from iptables to nftables — there are plenty of similar posts all over the internet. However, I soon realised that I was also running Docker on my laptop, which still does not natively support nftables at the time of writing. I therefore decided to write a dual-aim article: switching to nftables and allowing Docker containers to access the network.

Disable iptables

To fully transition to nftables and prevent conflicts, it’s important to stop and disable the legacy iptables services. You can do this with the following commands:

sudo systemctl stop iptables
sudo systemctl disable iptables

sudo systemctl stop ip6tables
sudo systemctl disable ip6tables

This ensures that iptables and ip6tables won’t interfere with your nftables rules on boot.

(Optional) Remove legacy iptables package iptables-nft

Docker with nftables

The Docker daemon can be configured using the /etc/docker/daemon.json file. If the file does not exist, create it and add the following content:

{
  "iptables": false,
  "ip6tables": false
}

For more configuration options, refer to the official documentation.

Restart the Docker deaemon for the changes to take effect:

sudo systemctl restart docker

Then, open the nftables configuration file (/etc/nftables.conf) with your preferred text editor. This is my minimal firewall configuration, which I run on my everyday laptop and which blocks all incoming traffic while allowing Docker connectivity.

#!/usr/sbin/nft -f

flush ruleset

define docker_if = "docker0"
define wan_if = "eth0"  # Change this to your real external interface (e.g. wlan0)

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop

        # Allow loopback traffic
        iifname lo accept

        # Accept established and related connections
        ct state established,related accept

        # Allow ICMP (v4 and v6)
        ip protocol icmp accept
        ip6 nexthdr ipv6-icmp accept

        # Allow DHCPv6 client replies
        udp dport 546 udp sport 547 accept

        # Allow limited incoming multicast (IPv4 & IPv6)
        ip daddr 224.0.0.0/4 accept
        ip6 daddr ff00::/8 accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop

        # Allow containers to forward to WAN
        iifname $docker_if oifname $wan_if accept
        # Allow return traffic
        ct state established,related accept
    }

    chain output {
        type filter hook output priority 0; policy accept
    }
}

table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100; policy accept

        # Masquerade container traffic going to internet
        oifname $wan_if ip saddr 172.17.0.0/16 masquerade
    }
}

This configuration only supports containers connected to Docker’s default bridge network (docker0) using the default IP range (172.17.0.0/16). It does not apply to custom Docker networks or alternative runtimes.

Closing words

I’ll update this post as soon as there are any relevant updates or official support from the Docker team.