Docker container with ip address on local network

Shortly after I started my new job in August I put an instance of gitlab on a server and started promoting using it over the git-repos-on-an-NFS-share that had been used thus far. Thanks to the Docker image of Gitlab CE it only took about 5 minutes to have a running instance – setting up authentication via LDAP only took slightly longer. So far I’ve been running it on non-standard ports, just like the guide suggests, but I’ve gotten to the point where I’d like to make it a bit more official. That means I’d like to get it off non-standard ports, but I do really like running it in Docker. In short, I need to give the Docker image an ip address on the local network.

Unfortunately Docker doesn’t make it easy to do that, but I found an article on four ways to connect a docker container to a local network. What follows is my take on the instruction for how to use a macvlan device to achieve it.

An image for testing

First I looked around for a simple echo server to run in my testing image:

module Main
       where

import Control.Concurrent
import Control.Monad
import Network.Socket
import System.IO

srvPort :: PortNumber
srvPort = 2048

main :: IO ()
main = withSocketsDo $ do
  newSocket <- socket AF_INET Stream defaultProtocol
  setSocketOption newSocket ReuseAddr 1
  bindSocket newSocket $ SockAddrInet srvPort iNADDR_ANY
  listen newSocket 2
  runServer echo newSocket

runServer :: (String -> String) -> Socket -> IO()
runServer f s = forever $ do
  (usableSocket,_) <- accept s
  forkIO $ interactWithSocket f usableSocket

interactWithSocket :: (String -> String) -> Socket -> IO()
interactWithSocket f s = do
  handle <- socketToHandle s ReadWriteMode
  forever $ f <$> hGetLine handle >>= hPutStrLn handle

echo :: String -> String
echo = ("R: " ++)

Run it and test it by pointing netcat to port 2048.

Once that was built I put together a Dockerfile that uses Debian Jessie as a base and copies in the echo server (I based it on another image):

FROM debian:8.2
MAINTAINER Magnus Therning <magnus@therning.org>

RUN TERM=vt220 apt-get update && \
    TERM=vt220 DEBIAN_FRONTEND=noninteractive apt-get -y install \
        apt-utils \
        dialog \
        libgmp10 \
    && true

RUN useradd -G users -m -s /bin/bash myuser && \
    echo "root:root" | chpasswd

USER myuser
COPY echosrv /home/myuser/
CMD bash --login

I popped the Dockerfile and the binary into a folder srv/ so I can build the image using

$ docker build --rm --tag=nw srv

and then started using

$ docker run --rm --interactive --tty --name=echo nw /home/myuser/echosrv

A handy alias

The following alias will turn out to be very handy indeed

$ alias docker-pid="docker inspect --format '{{ .State.Pid }}'"

The network setup

The local network is 192.168.1.0/24 and I found an unused address, 192.168.1.199 that I decided to use for my experiment. The gateway is on 192.168.1.1.

First create the macvlan device:

$ sudo ip link add mybridge link enp0s25 type macvlan mode bridge

Then put it into the network namespace of the running container, and bring it up:

$ sudo ip link set netns $(docker-pid echo) mybridge
$ sudo nsenter -t $(docker-pid echo) -n ip link set mybridge up

Now the device need its address and the routing has to be set up:

$ sudo nsenter -t $(docker-pid echo) -n ip route del default
$ sudo nsenter -t $(docker-pid echo) -n ip addr add 192.168.1.199/24 dev mybridge
$ sudo nsenter -t $(docker-pid echo) -n ip route add default via 192.168.1.1 dev mybridge

That’s it, now the docker container is reachable on 192.168.1.199. Well, it’s reachable on that ip from any computer on the network except for the host. In order for the host to be able to reach it add another route:

$ sudo ip route add 192.168.1.199 dev docker0

DHCP

If you’d rather have the container on an ip handed out by a DHCP server, then you replace the three commands for setting ip and route with:

$ sudo nsenter -t $(docker-pid echo) -n -- dhclient -d maclan
Leave a comment