How to Use Pipework to Understand Docker Container Networking: Advanced Network Configuration

How to Use pipework to Understand Docker Container Networking

Long before Docker had a proper pluggable networking model, there was a small shell script called Pipework, written by Jérôme Petazzoni, that a huge number of us relied on to do things Docker itself couldn’t yet do — attach containers to VLANs, put them on multiple interfaces, bridge them to physical networks, or hand them a static IP outside Docker’s own IPAM. Pipework is effectively unmaintained today and Docker’s native networking (custom bridges, macvlan, overlay) now covers almost everything it was built for, but I still think it’s one of the best tools for actually understanding what container networking is doing at the ip/veth/namespace level, because it doesn’t hide anything from you. This guide walks through using it and, just as importantly, explains what it’s teaching you along the way.

What Pipework Actually Is

Pipework is a single Bash script that wraps low-level Linux networking primitives — network namespaces, veth pairs, bridges, VLAN sub-interfaces, and macvlan/ipvlan devices — into simple one-line commands that operate against a running container. It doesn’t run a daemon, doesn’t maintain state, and doesn’t do IPAM; every time you run it, it performs a discrete networking operation and exits. That simplicity is exactly why it’s such a good teaching tool: every command maps to something you could type yourself with ip and nsenter.

Prerequisites

  • A Linux host with Docker installed.
  • git to fetch the Pipework script.
  • iproute2 and bridge-utils (Pipework shells out to ip and, for some operations, brctl).
sudo apt-get update
sudo apt-get install -y git iproute2 bridge-utils
git clone https://github.com/jpetazzo/pipework.git
sudo cp pipework/pipework /usr/local/bin/pipework
sudo chmod +x /usr/local/bin/pipework

Confirm it’s available:

pipework --help

Understanding the Core Mechanism

Before running any Pipework command, it helps to know what it’s doing under the hood for the most common case — attaching a container to a bridge:

  1. It finds the container’s PID via docker inspect.
  2. It creates a veth pair on the host.
  3. It moves one end of the pair into the container’s network namespace using ip link set <veth> netns <pid>.
  4. It renames the container-side interface (commonly to eth1, since eth0 is usually already taken by Docker’s own bridge).
  5. It assigns the requested IP address and brings the interface up, all via nsenter-style namespace operations.

Every one of the manual GRE and OVS steps I’ve done by hand in other guides is essentially what Pipework automates for the “connect a container to a bridge/interface” case.

Example 1: Attaching a Container to an Existing Linux Bridge

Say I already have a bridge br1 on the host (created with brctl addbr br1 or ip link add br1 type bridge), and I want a running container to get a second interface on it with a static IP, independent of Docker’s own networking:

docker run -d --name web nginx:alpine
sudo pipework br1 web 192.168.50.10/24

Verify inside the container:

docker exec web ip addr show eth1
eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet 192.168.50.10/24 scope global eth1

The container now has two interfaces: eth0 (Docker’s normal bridge, still working exactly as before) and eth1 (Pipework’s addition, on br1, with a static IP Docker never allocated or knows about).

Example 2: Attaching a Container Directly to a Physical Interface with MACVLAN

One of the things Pipework made easy long before Docker had a native macvlan driver was giving a container its own MAC address directly on the physical network, bypassing NAT entirely — useful when a container genuinely needs to look like a first-class device on the LAN.

sudo pipework eth0 web 192.168.1.100/24@192.168.1.1

Here, eth0 is the host’s physical interface, 192.168.1.100/24 is the container’s new address on that LAN, and @192.168.1.1 sets the default gateway. Internally this creates a macvlan sub-interface rather than a veth+bridge pair, since bridging directly onto a physical NIC that way isn’t possible without one.

Check it:

docker exec web ip route
default via 192.168.1.1 dev eth1
192.168.1.0/24 dev eth1 scope link src 192.168.1.100

Docker’s own native macvlan driver now does exactly this in a supported, daemon-managed way:

docker network create -d macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  -o parent=eth0 macvlan-net

docker run -d --name web2 --network macvlan-net --ip 192.168.1.101 nginx:alpine

I show both side by side deliberately — Pipework’s one-liner and Docker’s native equivalent do the same underlying thing, and comparing them is a good way to see what the native driver is abstracting away.

Example 3: VLAN Tagging with Pipework

If I need a container to sit on a tagged VLAN rather than the native untagged network:

sudo pipework eth0.100 web 10.100.0.5/24

Here eth0.100 tells Pipework to create (or reuse) an 802.1Q VLAN sub-interface for VLAN ID 100 on top of eth0, then attach the container to it. Internally this is equivalent to:

sudo ip link add link eth0 name eth0.100 type vlan id 100
sudo ip link set eth0.100 up

followed by the same bridge/veth attachment logic as before.

Example 4: Connecting Two Containers Directly, No Bridge at All

Pipework can also wire two containers together with a direct point-to-point veth pair and no bridge involved:

docker run -d --name c1 busybox sleep 3600
docker run -d --name c2 busybox sleep 3600
sudo pipework --direct-phys c1 c2 10.200.0.1/30 10.200.0.2/30

(Exact direct-link syntax varies by Pipework version — check pipework --help for the flags supported in the version you clone, since this project has had several community forks with slightly different feature sets.)

Why I Still Use Pipework as a Teaching Tool, Not a Production Tool

Pipework has no daemon, no persistent configuration, and nothing survives a container restart or host reboot unless I script the Pipework calls into my own startup automation. It also predates and doesn’t integrate with Docker’s docker network command family at all — from Docker’s perspective, a Pipework-attached interface is invisible; docker network inspect won’t show it. That’s fine for learning and for one-off debugging, but it’s exactly why Docker built proper networking (custom bridges, overlay, macvlan, ipvlan) into the daemon itself: production systems need attachments that are tracked, reproducible, and torn down automatically when a container stops.

Mapping Pipework Concepts to Modern Docker Networking

What Pipework does manuallyModern Docker equivalent
pipework br1 <container> <ip>/<mask>docker network create --driver bridge + docker run --network
pipework eth0 <container> <ip>/<mask>@<gw>docker network create --driver macvlan -o parent=eth0
pipework eth0.100 <container> <ip>docker network create --driver macvlan -o parent=eth0.100 with a pre-created VLAN sub-interface
Manual veth + namespace scriptingDocker’s own libnetwork implementation, driven by the docker network CLI

Troubleshooting

  • pipework: command not found after cloning: make sure the script was copied somewhere on $PATH and marked executable (chmod +x).
  • Container has the interface but no connectivity: check that the bridge or physical interface Pipework attached to is actually up and, for macvlan-style attachments, that the physical switch port allows the extra MAC address (some cloud providers and switches restrict multiple MACs per port — this is a very common failure mode on AWS EC2, where ENIs don’t allow arbitrary MAC/IP combinations without explicitly registering secondary IPs).
  • Changes disappear after container restart: expected — Pipework is not persistent. Either re-run it in a container’s entrypoint/startup script, or migrate to a native Docker network driver that Docker itself manages the lifecycle of.

Best Practices

  • Use Pipework to learn and to debug, not as a permanent part of a production stack, since nothing about its attachments is tracked by Docker or survives restarts automatically.
  • Whenever you find yourself relying on a specific Pipework command in production, look for the equivalent native Docker network driver first (macvlan, ipvlan, or a custom bridge) — in almost every case there now is one.
  • If you do keep Pipework calls in production scripts for a legacy system, wrap them in idempotent startup logic (check if the interface already exists before trying to create it again) to avoid errors on restart.

Persisting Pipework Attachments Across Restarts

Since Pipework has no daemon of its own, the attachments it creates vanish the moment a container restarts or a host reboots. In the one legacy environment I still maintain that relies on it, I handle this by wrapping the Pipework call in the container’s own startup sequence rather than running it from the host after the fact:

docker run -d --name legacy-app \
  --entrypoint /bin/sh \
  myorg/legacy:latest \
  -c "sleep 2 && /usr/local/app/start.sh"

and a small systemd unit on the host that waits for the container to exist, then attaches it:

# /etc/systemd/system/pipework-legacy-app.service
[Unit]
Description=Attach legacy-app to br1 via Pipework
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/bin/sh -c 'until docker inspect legacy-app >/dev/null 2>&1; do sleep 1; done'
ExecStart=/usr/local/bin/pipework br1 legacy-app 192.168.50.10/24

[Install]
WantedBy=multi-user.target

This is exactly the kind of lifecycle management that Docker’s native network drivers give you automatically, which is worth sitting with for a moment: every extra line of glue code here is a line of code a native docker network driver doesn’t need, because the daemon itself tracks the attachment and reapplies it whenever the container starts.

Summary

Pipework earns its place in a container networking guide not because it’s something I’d deploy today, but because running through its commands — veth pairs, bridge attachment, macvlan, VLAN tagging — is one of the fastest ways to actually understand what Docker’s native network drivers are doing for you automatically. Once the mechanics click, moving to docker network create with the bridge, macvlan, or overlay drivers feels like a natural next step rather than a black box.

References

  • Pipework project repository: https://github.com/jpetazzo/pipework
  • Docker macvlan network driver documentation: https://docs.docker.com/network/drivers/macvlan/
  • Docker network drivers overview: https://docs.docker.com/network/drivers/
  • Linux network namespaces documentation (man7.org): https://man7.org/linux/man-pages/man8/ip-netns.8.html
Total
0
Shares

Leave a Reply

Previous Post
How to Configure the Docker Daemon IP Tables and IP Forwarding Settings

How to Configure the Docker Daemon IP Tables and IP Forwarding Settings: Complete Networking Guide

Next Post
How to Set Up a Custom Bridge for Docker

How to Set Up a Custom Bridge for Docker: Network Configuration and Container Connectivity

Related Posts