How to use UFW with docker

Photo by Stephen Radford — burning house

Introduction

One of the most important things when creating secure Web applications is setting up good firewall. UFW, or Uncomplicated Firewall, is a command line tool that allows you to configure firewall settings easily compared to using iptables. In this tutorial we will prepare Docker container contains basic Nginx code with using UFW to showcase use case in real life.

I also provided more resources in the end of this article.

Prerequisites

  1. Would be good if you have basic understanding of how Docker and Docker Compose works. Familiarity with basic commands like docker compose up, docker ps, docker build and docker run.
  2. General understanding of how reverse proxies, Nginx works.
  3. Already having a Remote server. We will use it in every operation in this article.

Installation

Let's first install the UFW. To check whether UFW is installed or not we can run:

sudo ufw status

If it's not installed we can simply install it:

sudo apt install ufw

To check if installation was successful, we can run sudo ufw status again.

Preparing docker compose file

Let's start with the docker-compose.yml file. In this example we will just host static html with Nginx and docker.

docker-compose.yml:

services:
  nginx:
    image: nginx:1-alpine
    ports:
      - 8082:80
    volumes:
      - ./index.html/:/usr/share/nginx/html
  • services: These are the services that will create our application. In this case it just contains nginx.
  • image: The docker image will be used for our nginx service, in our case it's nginx:1-alpine.
  • ports: Specifies the ports nginx container will use. 8082 is the public port accessible by the outside world. 80 is the container's port we are listening on.
  • volumes: Mounts volumes for docker image to use. In our case index.html will be replaced to /usr/share/nginx/html inside container.

Let's create our index.html file next to docker-compose.yml:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello world</h1>
  </body>
</html>

To start our application we can run:

docker compose up

You should be able to reach the application at http://yourip:8082.

Setting up UFW rules

Now, let's test UFW with our example. Firstly let's disable UFW.

sudo ufw disable

Disabling/Enabling IPv6

Depending on your needs, you might want to enable or disable IPv6. In latest versions of Ubuntu, IPv6 is enabled by default. To change it, edit the configuration file at /etc/default/ufw.

We can simply change IPV6 to yes to allow IPV6.

There are lots of configuration files that allow us to fine tune UFW:

  • /etc/default/ufw: high level configuration, such as default policies, IPv6 support and kernel modules to use
  • /etc/ufw/before[6].rules: rules evaluated before any rules added via the ufw command
  • /etc/ufw/after[6].rules: rules evaluated after any rules added via the ufw command
  • /etc/ufw/sysctl.conf: kernel network tunables
  • /etc/ufw/ufw.conf: sets whether or not ufw is enabled on boot and sets the LOGLEVEL

Applying changes

Before enabling UFW, be aware it blocks SSH by default — allow SSH first:

sudo ufw allow ssh

Let's also allow http and https:

sudo ufw allow http
sudo ufw allow https

Start UFW:

sudo ufw enable

Now UFW will run automatically whenever we start our server.

Configuring ports

Let's block all incoming connections so we can manually decide which ones to allow:

sudo ufw default deny incoming

Issues with docker

You'd expect the website to be blocked now — but Docker by default modifies iptables, bypassing UFW's deny rules. We need to fix this.

Thanks to Feng's stack overflow answer, we can modify /etc/ufw/after.rules and add the following at the end:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

Restart UFW and docker:

sudo systemctl restart ufw &&
sudo systemctl restart docker

Now port 8082 should be blocked. To allow specific ports:

sudo ufw allow 8082

Conclusion

We've learned how to:

  • Install and configure UFW
  • Set up Docker with Nginx
  • Handle Docker's interaction with UFW
  • Manage firewall rules for specific ports
  • Configure basic security settings