Warning: Docker does not play well with UFW

Sat 09 December 2017

So I was experimenting with docker a few weeks ago on my VPS (running Debian 9). In particular I was trying to create a memory limited container for running go-ipfs that would function well in the low memory environment (the VPS only has 1GB RAM). As mentioned previously, go-ipfs is quite RAM greedy. Enough to swallow all available memory and send my server into a slow swapping hell for a good few hours until the kernel OOM killer sprang into action.

I ran something like the following to create/start the container:

$ docker run -p4001:4001 -p5001:5001 -p8080:8080 -m 256M --name ipfs-node \
    --restart always my_ipfs_image

which opens and forwards ports 4001, 5001 and 8080 from the host into the container. Technically, only port 4001 (tcp) is supposed to be exposed to the outside world according to the IPFS docs (5001 is the API port and is most definitely not supposed to be accessible from the internets and 8080 is the IPFS gateway).

I wasn't the least bit concerned about running the above command since I had UFW set to deny all incoming connections by default (with exceptions for SSH and some other services I ran). For those not familiar with UFW, it's a simple front-end for iptables (a utility to configure the Linux firewall). It has served me well for almost 10 years.

Little did I know that docker is designed to sidestep tools like UFW and directly make changes to iptables to perform port forwarding and NAT. And worse still UFW was completely oblivious to these changes. By sheer luck I ran an nmap scan on my VPS from home and realized what was happening. Googling "UFW docker" showed that this was a known issue.

I initially just gave up on the container route to handle the go-ipfs memory problems and created a script to restart its daemon every 12 hours. But I came across a simple solution to the problem yesterday via this excellent blog post.

In short, to get docker to work with UFW, the steps are (as root):

$ echo "{
\"iptables\": false
}" > /etc/docker/daemon.json
$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ systemctl reboot # reboot your machine 
$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

And now you can use docker with UFW in peace!