Ample Opportunities for Issues
Numerous bug reports have been raised in the past about IPv6. Please make sure your setup around DMS is correct when using IPv6!
If your host system supports IPv6 and an
AAAA DNS record exists to direct IPv6 traffic to DMS, you may experience issues when an IPv6 connection is made:
- The original client IP is replaced with the gateway IP of a docker network.
- Connections fail or hang.
The impact of losing the real IP of the client connection can negatively affect DMS:
- Users unable to login (Fail2Ban action triggered by repeated login failures all seen as from the same internal Gateway IP)
- Mail inbound to DMS is rejected (SPF verification failure, IP mismatch)
- Delivery failures from sender reputation being reduced (due to bouncing inbound mail from rejected IPv6 clients)
- Some services may be configured to trust connecting clients within the containers subnet, which includes the Gateway IP. This can risk bypassing or relaxing security measures, such as exposing an open relay.
When the host network receives a connection to a containers published port, it is routed to the containers internal network managed by Docker (typically a bridge network).
By default, the Docker daemon only assigns IPv4 addresses to containers, thus it will only accept IPv4 connections (unless a
docker-proxy process is listening, which the default daemon setting
userland-proxy: true enables). With the daemon setting
userland-proxy: true (default), IPv6 connections from the host can also be accepted and routed to containers (even when they only have IPv4 addresses assigned).
userland-proxy: false will require the container to have atleast an IPv6 address assigned.
This can be problematic for IPv6 host connections when internally the container is no longer aware of the original client IPv6 address, as it has been proxied through the IPv4 or IPv6 gateway address of it's connected network (eg:
172.17.0.1 - Docker allocates networks from a set of default subnets).
This can be fixed by enabling a Docker network to assign IPv6 addresses to containers, along with some additional configuration. Alternatively you could configure the opposite to prevent IPv6 connections being made.
- Avoiding an
AAAADNS record for your DMS FQDN would prevent resolving an IPv6 address to connect to.
- You can also use
userland-proxy: false, which will fail to establish a remote connection to DMS (provided no IPv6 address was assigned).
With UFW or Firewalld
When one of these firewall frontends are active, remote clients should fail to connect instead of being masqueraded as the docker network gateway IP. Keep in mind that this only affects remote clients, it does not affect local IPv6 connections originating within the same host.
You can enable IPv6 support in Docker for container networks, however compatibility concerns may affect your success.
The official Docker documentation on enabling IPv6 has been improving and is a good resource to reference.
ip6tables support so that Docker will manage IPv6 networking rules as well. This will allow for IPv6 NAT to work like the existing IPv4 NAT already does for your containers, avoiding the above issue with external connections having their IP address seen as the container network gateway IP (provided an IPv6 address is also assigned to the container).
Configure the following in
"experimental" : true,
experimental: trueis currently required for
ip6tables: trueto work.
userland-proxysetting can potentially affect connection behavior for local connections.
Now restart the daemon if it's running:
systemctl restart docker.
Next, configure a network with an IPv6 subnet for your container with any of these examples:
Create an IPv6 ULA subnet
About these examples
These examples are focused on a IPv6 ULA subnet which is suitable for most users as described in the next section.
- You may prefer a subnet size smaller than
/112, which still provides over 65k IPv6 addresses), especially if instead configuring for an IPv6 GUA subnet.
- The network will also implicitly be assigned an IPv4 subnet (from the Docker daemon config
The preferred approach is with user-defined networks via
compose.yaml (recommended) or CLI with
docker network create:
Create the network in
compose.yaml and attach a service to it:
- subnet: fd00:cafe:face:feed::/64
Override the implicit
You can optionally avoid the service assignment by overriding the
default user-defined network that Docker Compose generates. Just replace
The Docker Compose
default bridge is not affected by settings for the default
Using the network outside of this
To reference this network externally (from other compose files or
docker run), assign the networks
name key in
Create the network via a CLI command (which can then be used with
docker run --network dms-ipv6):
docker network create --ipv6 --subnet fd00:cafe:face:feed::/64 dms-ipv6
Optionally reference it from one or more
This approach is discouraged
Add these two extra IPv6 settings to your daemon config. They only apply to the default
bridge docker network aka
docker0 (which containers are attached to by default when using
Compose projects can also use this network via
Do not use
2001:db8:1::/64 for your private subnet
2001:db8 address prefix is reserved for documentation. Avoid creating a subnet with this prefix.
Presently this is used in examples for Dockers IPv6 docs as a placeholder, while mixed in with private IPv4 addresses which can be misleading.
If you've configured IPv6 address pools in
/etc/docker/daemon.json, you do not need to specify a subnet explicitly. Otherwise if you're unsure what value to provide, here's a quick guide (Tip: Prefer IPv6 ULA, it's the least hassle):
fd00:cafe:face:feed::/64is an example of a IPv6 ULA subnet. ULA addresses are akin to the private IPv4 subnets you may already be familiar with. You can use that example, or choose your own ULA address. This is a good choice for getting Docker containers to their have networks support IPv6 via NAT like they already do by default with IPv4.
- IPv6 without NAT, using public address space like your server is assigned belongs to an IPv6 GUA subnet.
- Typically these will be a
/64block assigned to your host, but this varies by provider.
- These addresses do not need to publish ports of a container to another IP to be publicly reached (thus
ip6tables: trueis not required), you will want a firewall configured to manage which ports are accessible instead as no NAT is involved. Note that this may not be desired if the container should also be reachable via the host IPv4 public address.
- You may want to subdivide the
/64into smaller subnets for Docker to use only portions of the
/64. This can reduce some routing features, and require additional setup / management via a NDP Proxy for your public interface to know of IPv6 assignments managed by Docker and accept external traffic.
- Typically these will be a
With Docker CLI or Docker Compose, run a
traefik/whoami container with your IPv6 docker network and port 80 published. You can then send a curl request (or via address in the browser) from another host (as your remote client) with an IPv6 network, the
RemoteAddr value returned should match your client IPv6 address.
docker run --rm -d --network dms-ipv6 -p 80:80 traefik/whoami
# On a different host, replace `2001:db8::1` with your DMS host IPv6 address
curl --max-time 5 http://[2001:db8::1]:80
IPv6 ULA address priority
DNS lookups that have records for both IPv4 and IPv6 addresses (eg:
localhost) may prefer IPv4 over IPv6 (ULA) for private addresses, whereas for public addresses IPv6 has priority. This shouldn't be anything to worry about, but can come across as a surprise when testing your IPv6 setup on the same host instead of from a remote client.
The preference can be controlled with
/etc/gai.conf, and appears was configured this way based on the assumption that IPv6 ULA would never be used with NAT. It should only affect the destination resolved for outgoing connections, which for IPv6 ULA should only really affect connections between your containers / host. In future IPv6 ULA may also be prioritized.