Hetzner Public IPv4 and IPv6 Subnet routing with Docker

Ben Mitchell
4 min readApr 10, 2021

--

So, you have just purchased an IPv4 Subnet from Hetzner and it has been assigned to your dedicated server. But now you’re wondering how you forward it to docker containers so each container has its own public IPv4? Or perhaps you have already done so, but each container’s outgoing traffic shows up as the Host’s IP instead of the Containers? Well, I spent many months figuring it out myself before finally getting it working.

Prerequisites

  1. A IPv4 Subnet. I have ordered a /27 one which contains 32 unique IPv4 addresses.
  2. Docker
  3. Some patience

For the sake of this tutorial i have been given the subnet: 148.251.100.100/27

For IPv6, we split the Host’s IPv6 block into 4 parts. Our hosts IPv6 Block is 2a01:4f8:123:123::/64. Which when split into 4 parts yields:

2a01:4f8:123:123::/66

2a01:4f8:123:123:4000::/66 ← We will use this one for docker

2a01:4f8:123:123:8000::/66

2a01:4f8:123:123:c000::/66

Step 1

In order to create the network for your docker containers you must first familiarise yourself with your subnet.

Firstly, you need to workout your Gateway IP. Because of the way Hetzner Routes the Subnet through the host, your gateway ip must be an Ip from the subnet. This might not make sense initially but it will later on. For the sake of simplicity I use the Subnet Address + 1 method. So because my subnet is 148.251.100.100/27 I will add 1 to it, making my Gateway IP 148.251.100.101. The Same thing is done for the IPv6 making our IPv6 gateway 2a01:4f8:123:123:4000::1

Step 2

Before we can begin making the network, we must first ensure our host is set up to route traffic correctly. To do this edit /etc/sysctl.conf With your favorite editor and find net.ipv4.ip_forward.

Remove the # before the beginning if it exists and change the end from 0 to 1. Your line should look like this:

net.ipv4.ip_forward = 1

Once done, edit /etc/docker/daemon.json. This file might be blank or new, this is fine. Add the following (replacing the IPv6 with your chosen block)

{
"ipv6": true,
"fixed-cidr-v6": "2a01:4f8:123:123:4000::/66"
}

Once you have done these two steps, you now need to set up your Network interface so that there is no overlapping of the subnets.

In Ubuntu, open /etc/network/interfaces with an editor of your choice. Note down the interface name. For me it is enp1s0 but yours might be different. This is very important as failing to do so will result in a loss of connectivity. With your interface name remembered, clear every line in the file so that it is blank.

Now copy and paste the following:

### Hetzner Online GmbH installimagesource /etc/network/interfaces.d/*auto loiface lo inet loopbackiface lo inet6 loopbackauto enp1s0iface enp1s0 inet staticaddress #mainIPnetmask #mainIPNetMaskgateway #mainIPGateWayiface enp1s0 inet6 staticaddress #IPv6netmask 66gateway fe80::1

Your IPv6 address is the 2nd address of the first block we created earlier, for example: 2a01:4f8:123:123::2

You can find your mainIP, mainIPNetMask and mainIPGateWay in the Hetzner robot. It is the main IP of your server NOT your subnet. You most likely connected using it over ssh. To find the gateway and netmask simply hover over the address in the robot.

Robot

Once done, save the file and reboot. Your server should be online within 2 or 3 minutes. Sometimes it can take as long as 5 or 10 minutes depending on the server board you have.

Step 3

Now that everything is set up. It is fairly easy going forward.

Firstly, create the docker network with whatever name you like. In my case i am using the name “subnet1”. Replace my values with yours.

docker network create \
--driver=bridge \
-o "com.docker.network.bridge.enable_ip_masquerade"="false" \
--subnet=148.251.100.100/27 \
--gateway=148.251.100.101 \
--ipv6 \
--subnet=2a01:4f8:123:123:4000::/66 \
--gateway=2a01:4f8:123:123:4000::1 \
subnet1

Now you can create a new container and attach it to the network like you normally do. You can specify a IPv4 and IPv6 to use, or you can let docker assign it automatically.

Example:

docker run -it --name=container1 --net=subnet1 --ip=148.251.100.102 --ip6=2a01:4f8:123:123:4000::2 ubuntu:latest bin/bash

If you already have existing containers you can attach them to the new network with the following command:

docker network connect --ip=148.251.100.102 --ip6=2a01:4f8:123:123:4000 subnet1 containerName

Sanity Check

Everything should be working as intended. However, it is important to check now to save time later. To do so, run the following command: (with your values)

docker run -it --name=container1 --net=subnet1 --ip=148.251.100.102 --ip6=2a01:4f8:123:123:4000::2 ubuntu:latest bin/bash

Once done, you should be dropped into a ubuntu container bash shell. Now run the following commands to install curl

apt update
apt install curl

Once done, run the following command:

curl ifconfig.me

curl should then output the IPv4 address specified when creating the container. This shows that not only does the container have an internet connection, but also the traffic is as if it came from the container.

That last part is what took me so long. The secret ingredient to the puzzle was when we created the network, we assigned the com.docker.network.bridge.enable_ip_masquerade to false. This means docker will not interfere with the outgoing traffic from the container. Without this option, outgoing traffic comes from the Host’s IP.

Conclusion

Docker is very neat and allows for some very cool projects such as an all in one mail solution. However, it can be quite complex once you start needing to do advanced stuff with it.

For newcomers, I would recommend using Portainer to manage your docker instances. It is free and easy to install and use. In fact, it runs in its own docker.

That is all i have to say, check out some of my other articles or leave a comment below if you have any questions :)

Ben Mitchell

--

--

Ben Mitchell

iOS Developer, Server administrator and Technology Enthusiast