If you’ve ever tried to assign a routable and externally visible IP address to your Docker containers, you probably know that it’s not a trivial task. There are a few posts out there that show a few interesting methods, like this or this or even from the creators of Docker itself.
I’ve been digging for a better method, that will still allow you to use Docker API and don’t execute any host-side commands after starting every container. Fortunately, it turns out that Docker doesn’t try to recreate a network bridge it uses to connect containers together, if the bridge already exists. So you may use it like this:
Create a new bridge:
brctl addbr mybridge
ip link set mybridge up
Look at the settings of your primary adapter:
ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.211.55.49 netmask 255.255.255.0 broadcast 10.211.55.255
See the default route:
ip route
default via 10.211.55.1 dev eth0 proto static metric 100
Add eth0 to the bridge:
brctl addif mybridge eth0
Configure the bridge:
ip addr del 10.211.55.49/24 dev eth0
ip addr add 10.211.55.49/24 dev mybridge
Set up default route on the bridge:
ip route del default
ip route add default via 10.211.55.1 dev mybridge
Create a new network (replace with the networks relevant to you):
docker network create --driver=bridge --subnet 10.211.55.0/24 --gateway 10.211.55.49 --ip-range 10.211.55.128/25 -o "com.docker.network.bridge.name"="mybridge" mybridge
Where:
--subnet
should be the same as the network you want to expose containers to (e.g. eth0)--gateway
specifies the IP address that will be set on the bridge and through which containers will route traffic. This is not the external gateway (10.211.55.1). Unfortunately there is no separate setting for the bridge’s IP.--ip-range
is important to specify so that docker won’t automatically allocate addresses that you know will intersect with other hosts on the network, as it assumes total control of the subnet otherwise.- and the
com.docker.network.bridge.name
option tells docker to not generate a bridge name, and use the provided one.
Then you may start creating containers as usual:
docker run --rm -t -i --net mybridge alpine:3.4 /bin/sh
ifconfig eth0
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:10.211.55.128 Bcast:0.0.0.0 Mask:255.255.0.0
...
and then from outside of your host:
ping 10.211.55.128
PING 10.211.55.128 (10.211.55.128): 56 data bytes
64 bytes from 10.211.55.128: icmp_seq=0 ttl=64 time=0.393 ms
64 bytes from 10.211.55.128: icmp_seq=1 ttl=64 time=0.259 ms
64 bytes from 10.211.55.128: icmp_seq=2 ttl=64 time=0.352 ms
That works out of the box, with standard Docker API. You only need to create a bridge in advance and set everything up carefully. You may even prefer to use external IPAM system to allocate IPs, and set them manually via --ip
.