This project runs a single container exposing a UDP service that replies with its own hostname to any incoming datagram.
The intent is to publish the service on IPv4 and IPv6 anycast addresses on the host, so the service becomes reachable via anycast when the host advertises those addresses/prefixes.
- 1 container:
udp-hostname - 1 dual-stack Docker network (
anycast_net)- IPv4:
10.200.0.0/24(container:10.200.0.2) - IPv6:
fd00:200::/64(container:fd00:200::2)
- IPv4:
- Host-published UDP port (default
5300/udp) on both IPv4 and IPv6
docker compose up --buildAvoid port conflicts (especially :53) and avoid touching other Compose projects by:
- Using a different
COMPOSE_PROJECT_NAME(separate containers/networks), and - Binding to a high UDP port on loopback only.
Example:
# Pick a free UDP port (example: 15300)
ss -lun | rg ':15300' || true
export COMPOSE_PROJECT_NAME=udp-hostname-test
export PUBLISHED_PORT=15300
export ANYCAST_IPV4=127.0.0.1
export ANYCAST_IPV6=::1
docker compose up -d --buildTest:
echo -n ping | nc -u -w1 127.0.0.1 15300
python3 - <<'PY'
import socket
s=socket.socket(socket.AF_INET6, socket.SOCK_DGRAM); s.settimeout(1)
s.sendto(b'ping', ('::1', 15300))
print(s.recvfrom(1024)[0].decode().strip())
PYTear down only this stack:
COMPOSE_PROJECT_NAME=udp-hostname-test docker compose down -vTest locally from the host (or anywhere that can reach the host):
echo -n ping | nc -u -w1 127.0.0.1 5300
# For IPv6, target a non-loopback IPv6 address of the host (any global/ULA on an interface).
HOST_IPV6="$(ip -6 -br addr show scope global | awk 'NR==1{print $3}' | cut -d/ -f1)"
echo -n ping | nc -u -w1 -6 "$HOST_IPV6" 5300If nc on your system doesn’t print UDP responses reliably, use socat:
echo -n ping | socat - UDP4-DATAGRAM:127.0.0.1:5300,so-broadcast
echo -n ping | socat - UDP6-DATAGRAM:[::1]:5300compose.yml publishes the container port to the host, and by default binds on all addresses:
- IPv4:
0.0.0.0:${PUBLISHED_PORT}:5300/udp - IPv6:
[::]:${PUBLISHED_PORT}:5300/udp
To bind only on the anycast addresses (recommended), set:
export ANYCAST_IPV4=45.54.76.1
export ANYCAST_IPV6=2607:f740:e633:deec::2
export PUBLISHED_PORT=5300
docker compose up --buildAnycast reachability is a host/network property. Docker Compose can only publish the UDP port; the host must have the anycast addresses configured and must advertise them (typically via BGP).
The host must have the anycast addresses configured on an interface that receives the anycast traffic. Common patterns:
- Add them to the physical uplink interface (e.g.
enp1s0), or - Add them to
lo(loopback) and ensure appropriate routing/ARP/NDP behavior (depends on provider design).
Example (temporary, for illustration):
ip addr add 45.54.76.1/32 dev lo
ip -6 addr add 2607:f740:e633:deec::2/128 dev loIf your provider expects the addresses on the uplink interface, configure them there instead, and persist via netplan/systemd-networkd.
Docker’s published ports rely on netfilter (nftables via iptables-nft on Ubuntu) and conntrack.
Recommended sysctls:
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1The anycast addresses must be routed to this host. In many setups this means:
- Your upstream(s) establish BGP sessions with the host, and
- The host advertises the anycast prefix(es) (not just a host route), and
- The upstream accepts those announcements.
Example FRR approach (high level):
- Run
bgpd(FRR) on the host. - Announce the prefix containing your anycast address (e.g.
45.54.76.0/24and2607:f740:e633:deec::/48, depending on your allocation and policy). - Ensure you have filtering in place so you only announce what you intend.
This repository does not attempt to automate BGP on a real host; it only documents the expected moving parts.
Allow inbound UDP on the published port to the anycast addresses, and allow established/related return traffic.
At minimum:
- UDP
5300to45.54.76.1 - UDP
5300to2607:f740:e633:deec::2
- Binding published ports to a specific host IP is supported via the
ports:entries incompose.yml. - If you use port
53/udpinstead of5300/udp, you’ll need privileges on the host (or CAPs) and likely to stop any existing DNS service bound to:53.