When working with Docker containers on complex networks, you often need to add static routes so containers can reach networks that aren’t directly connected to their default gateway. This becomes especially important when using macvlan network drivers where containers get their own IP addresses on your physical network.

I’ve just released drouter, a lightweight systemd service that solves this problem by automatically injecting routes into Docker containers based on simple labels.

The Problem

Consider this scenario: you’re using a macvlan network driver so your containers get real IP addresses on your network (say 192.168.1.0/24). Your router is at 192.168.1.1, but you have additional internal subnets like 10.0.0.0/8 that are reachable through a different gateway at 192.168.1.254.

Without custom routes, your containers can only reach networks directly connected to their default gateway. Traffic to 10.0.0.0/8 would fail because the container doesn’t know how to route to that network.

Traditional solutions require either:

  • Running containers with NET_ADMIN capabilities (security risk)
  • Manual route configuration after container startup (not scalable)
  • Complex init scripts inside containers (maintenance overhead)

The Solution

Drouter monitors Docker container events and automatically adds routes to containers based on labels. When a container starts with drouter.routes.* labels, the service enters the container’s network namespace and configures the specified routes.

Here’s how simple it is to configure:

# docker-compose.yml
services:
  app:
    image: nginx
    networks:
      - macvlan_net
    labels:
      drouter.routes.ipv4: "10.0.0.0/8 via 192.168.1.254"

Now your container can reach the 10.0.0.0/8 network through the 192.168.1.254 gateway, even though it’s not the default route.

Installation

Drouter runs as a systemd service on your Docker host. See the installation instructions on GitHub for the latest setup steps.

Configuration Examples

Multiple Routes

You can specify multiple routes using semicolon separation or multi-line format:

# Single line with semicolons
labels:
  drouter.routes.ipv4: "10.0.0.0/8 via 192.168.1.254;172.16.0.0/12 via 192.168.1.253"

# Multi-line format (easier to read)
labels:
  drouter.routes.ipv4: |
    10.0.0.0/8 via 192.168.1.254
    172.16.0.0/12 via 192.168.1.253

IPv6 Support

Drouter fully supports IPv6 routing:

labels:
  drouter.routes.ipv6: "2001:db8::/32 via fe80::1"

Docker CLI

For non-compose deployments:

docker run -d \
  --label drouter.routes.ipv4="10.0.0.0/8 via 192.168.1.254" \
  --net macvlan_net \
  nginx

How It Works

Drouter uses Docker’s event API to monitor container lifecycle events. When a container starts with route labels, it:

  1. Detects the container’s network namespace
  2. Uses nsenter to enter the namespace
  3. Adds routes with standard ip route add commands
  4. Skips duplicate routes to avoid conflicts

The service handles Docker daemon restarts gracefully and doesn’t require elevated privileges for the containers themselves.

Why This Matters

This approach is particularly valuable for:

  • Macvlan networks: Containers with real IPs need custom routing for internal networks
  • Multi-homed environments: Networks with multiple gateways or complex topologies
  • Microservices: Different services needing access to different network segments
  • Security: Avoiding NET_ADMIN capabilities in containers

Before drouter, I was manually configuring routes or writing custom init scripts. Now it’s as simple as adding a label to my compose file.

The source code and detailed documentation are available on GitHub. Give it a try if you’re dealing with complex Docker networking scenarios!