Nftables, when integrated with Docker, allows for fine-grained control over container network traffic, acting as a more powerful and flexible alternative to iptables.

Let’s see nftables in action managing Docker’s network. Imagine we have a simple web server running in a Docker container.

# Start a basic Nginx container
docker run -d --name my-nginx -p 8080:80 nginx

By default, Docker manages its own iptables rules (or nftables rules if iptables-nft is in use) to expose ports. If we want to add a more specific rule, say, to only allow access to port 8080 from a specific IP address, we’d interact with nftables directly.

First, let’s find the Docker network interface name. Docker often creates its own bridge networks.

# List Docker networks
docker network ls
# Inspect a network to find its bridge interface
docker network inspect bridge

This might reveal an interface like br-xxxxxxxxxxxx. Now, let’s create an nftables rule to restrict access.

# Create a new table for Docker rules
sudo nft add table ip docker_filter

# Create a chain within that table, attached to the Docker bridge interface
sudo nft add chain ip docker_filter ingress { type filter hook ingress device br-xxxxxxxxxxxx \; priority -100 \; }
sudo nft add chain ip docker_filter egress { type filter hook egress device br-xxxxxxxxxxxx \; priority 100 \; }

# Allow traffic from a specific IP to the container's exposed port
sudo nft add rule ip docker_filter ingress ip saddr 192.168.1.100 tcp dport 8080 accept

# Drop all other incoming traffic to that port
sudo nft add rule ip docker_filter ingress tcp dport 8080 drop

# Allow Docker's internal traffic (important for container communication)
sudo nft add rule ip docker_filter ingress accept
sudo nft add rule ip docker_filter egress accept

This setup allows us to build a comprehensive firewall for containers. The ingress chain processes packets entering the br-xxxxxxxxxxxx interface (which is where traffic destined for containers arrives), and the egress chain processes packets leaving it. By placing specific accept rules before a general drop rule, we enforce our policy. The priority values are crucial; -100 for ingress is typically before Docker’s own FORWARD chain, and 100 for egress is after.

The problem nftables solves in this context is the often cumbersome and less readable nature of iptables, especially when dealing with dynamic container environments. Nftables’ syntax is more structured, allowing for easier management of complex rulesets. It also offers better performance and more granular control over packet filtering, connection tracking, and NAT.

The type filter hook ingress device br-xxxxxxxxxxxx priority -100 part is key. It tells nftables to create a filter table, hook into the ingress path of the network stack, specifically for packets arriving on the br-xxxxxxxxxxxx interface (your Docker bridge), and to evaluate these rules before Docker’s default forwarding rules (hence the negative priority). Similarly, the egress chain hooks into packets leaving the bridge.

A common pitfall is forgetting to allow Docker’s internal networking traffic. If you only add restrictive rules, containers might not be able to communicate with each other or with the host. The accept rules for ingress and egress without specific IP/port matching are often necessary to permit Docker’s management of container traffic.

When you change your nftables configuration, you need to ensure it persists across reboots. This typically involves saving the ruleset to a file (e.g., /etc/nftables.conf) and ensuring the nftables service is enabled and started.

Once you’ve got your basic filtering in place, the next logical step is to explore how to manage stateful connections for your containers, perhaps using the ct helper or ct state expressions in nftables.

Want structured learning?

Take the full Nftables course →