Docker 91 🐳 Debugging Networking Issues: tcpdump, nsenter, and Inspecting Container Network Namespaces
Docker networking issues are difficult to diagnose from the host because each container has its own isolated network namespace. The eth0 interface, routing table, and socket state that belong to the container are invisible from the host’s default namespace. The tools that solve this problem are nsenter, which lets you enter the container’s network namespace from the host, and tcpdump, which captures packets from the container’s perspective rather than the host’s.
This chapter covers the namespace isolation model that makes debugging difficult, the nsenter mechanism that bridges the host and container network contexts, the tcpdump filtering rules that must match the container’s internal ports rather than the host’s mapped ports, and the diagnostic sequence that distinguishes problems inside the container from problems in the host’s iptables rules or the Docker bridge.
Key point: nsenter runs from the host, not from inside the container. It enters the target container’s network namespace using the container’s PID, which means you can use the host’s debugging tools while seeing the container’s network view. This is essential when the container image lacks tcpdump, ss, or other diagnostic utilities.
Why namespace entry is required
The isolation problem. Docker creates a separate network namespace for each container. This namespace contains its own loopback interface, its own eth0 connected to the Docker bridge through a veth pair, its own routing table, and its own firewall rules . When you run tcpdump on the host without entering the container’s namespace, you capture traffic on the host’s interfaces, not the container’s. Packets that are internal to the container’s namespace—loopback traffic, for example—are completely invisible.
The missing tools problem. Many container images are minimal by design. A Node.js or Python application image may not include tcpdump, ss, netstat, or ip. Installing these tools inside the running container is often impossible or undesirable, especially in production. nsenter solves this by using the host’s tools while operating in the container’s namespace. The host has tcpdump; the namespace provides the container’s network interfaces.
The docker exec limitation. docker exec runs a command inside the container, which means it uses the container’s filesystem and its installed tools. If the image lacks tcpdump, docker exec tcpdump fails. docker exec also enters the container’s cgroups, which means resource limits apply. nsenter does not enter cgroups, so it is not subject to the container’s memory or CPU limits . For debugging, this distinction matters: a container that is out of memory may be unable to run docker exec, but nsenter can still enter its namespace from the host.
a. Finding the Container PID and Entering the Namespace
The first step is identifying the container’s main process ID on the host. The docker inspect command with a format template returns the PID directly.
# Get the container PID using the container name
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' my-nginx)
# Verify the PID
echo $CONTAINER_PID
With the PID, nsenter enters the network namespace using the -t flag to specify the target process and the -n flag to enter only the network namespace.
# Enter the container's network namespace
sudo nsenter -t $CONTAINER_PID -n
The shell prompt does not change, but the network context does. Running ip addr now shows the container’s interfaces: lo and eth0 with the container’s IP address, not the host’s . The -n flag is deliberate: entering only the network namespace avoids the complication of the container’s filesystem not containing the host’s shell or tools. The host’s /bin/bash and /usr/sbin/tcpdump remain available.
If you need to enter multiple namespaces—for example, to inspect processes as the container sees them—the flags combine. -n for network, -p for PID, and -m for mount.
# Enter network, PID, and mount namespaces
sudo nsenter -t $CONTAINER_PID -n -p -m /bin/bash
When entering the mount namespace with -m, the shell path must exist in the container’s filesystem. If it does not, omit -m and use only -n for network debugging.
b. Capturing Packets Inside the Namespace
Once inside the network namespace, tcpdump sees the container’s interfaces. The interface name is typically eth0, but it can be verified with ip addr before capturing.
# Inside the namespace: verify interfaces
ip addr
# Capture on the container's eth0, port 80 (the container's internal port)
tcpdump -i eth0 -nn port 80
The port number is the container’s internal port, not the host-mapped port. If the container was started with -p 8080:80, the traffic to capture is on port 80 inside the namespace, because that is where the application is listening . Capturing on port 8080 inside the namespace would capture nothing, because the mapping from 8080 to 80 happens in the host’s iptables rules, not inside the container.
For saving captures for later analysis in Wireshark, the -w flag writes the packets to a file. The file is created in the current directory, which is the host’s directory if nsenter was invoked from the host without entering the mount namespace.
# Save to a file for Wireshark analysis
tcpdump -i eth0 -nn -w /tmp/container-capture.pcap port 80
# Limit the capture to avoid filling disk
tcpdump -i eth0 -c 1000 -w /tmp/container-capture.pcap
The -c flag limits the number of packets, which is important when capturing on a busy interface. Without it, the capture file grows until manually stopped.
c. Practical Scenarios and Verification
The namespace entry pattern applies to any network diagnostic tool, not just tcpdump. The host’s ss command, run inside the container’s namespace, shows the container’s listening sockets rather than the host’s.
# Inside the namespace: check listening ports
ss -tlnp
# Check routing table as the container sees it
ip route
# Test connectivity from the container's network context
curl -v http://external-service:8080
A common diagnostic sequence for a container that cannot reach an external service: first, verify the container’s IP and routing table with ip addr and ip route. Second, test DNS resolution with getent hosts or nslookup if available. Third, capture packets on eth0 to see whether the connection attempt leaves the container and whether a response returns. If packets leave but no response returns, the problem is outside the container—in the host’s iptables rules, the Docker bridge, or the external network. If no packets leave, the problem is inside the container’s network configuration or the application’s binding .
For DNS issues specifically, the container’s /etc/resolv.conf can be read through nsenter with the mount namespace, or the DNS query can be tested from within the network namespace. On user-defined Docker networks, the embedded DNS resolver at 127.0.0.11 handles name resolution; on the default bridge network, the host’s resolver is used directly.
Complete Example Session
# ============================================
# PART 1: IDENTIFY THE CONTAINER
# ============================================
docker ps
# Note the container name or ID
# ============================================
# PART 2: GET THE CONTAINER PID
# ============================================
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' my-nginx)
echo $CONTAINER_PID
# Output: 31889 (example)
# ============================================
# PART 3: ENTER THE NETWORK NAMESPACE
# ============================================
sudo nsenter -t $CONTAINER_PID -n
# Prompt does not change; network context does
# ============================================
# PART 4: VERIFY THE CONTAINER'S INTERFACES
# ============================================
ip addr
# Shows lo and eth0 with the container's IP
# ============================================
# PART 5: CHECK LISTENING PORTS
# ============================================
ss -tlnp
# Shows the application listening on port 80
# ============================================
# PART 6: CAPTURE TRAFFIC ON THE CONTAINER'S INTERFACE
# ============================================
tcpdump -i eth0 -nn port 80
# Captures traffic as the container sees it
# ============================================
# PART 7: SAVE CAPTURE FOR ANALYSIS
# ============================================
tcpdump -i eth0 -nn -c 1000 -w /tmp/capture.pcap port 80
# ============================================
# PART 8: TEST CONNECTIVITY FROM INSIDE
# ============================================
curl -v http://external-api.example.com/health
# ============================================
# PART 9: EXIT THE NAMESPACE
# ============================================
exit
# Returns to the host's network namespace
# ============================================
# PART 10: ALTERNATIVE — SINGLE COMMAND WITHOUT SHELL
# ============================================
sudo nsenter -t $CONTAINER_PID -n tcpdump -i eth0 -nn port 80
# Runs tcpdump directly without entering a shell
The ten parts trace the full workflow: identify, get PID, enter namespace, verify context, capture, save, test, exit. Part 10 shows the non-interactive variant, which is useful for scripts or when the container’s filesystem lacks a shell.
Quick Reference
Namespace Entry Commands
| Command | Purpose |
|---|---|
docker inspect -f '{{.State.Pid}}' <name> | Get container PID |
nsenter -t <pid> -n | Enter network namespace only |
nsenter -t <pid> -n -p -m | Enter network, PID, and mount |
nsenter -t <pid> -a | Enter all namespaces |
Tcpdump Options for Container Debugging
| Option | Purpose |
|---|---|
-i eth0 | Capture on container’s eth0 |
-nn | No DNS or port name resolution |
-c <count> | Limit packet count |
-w <file> | Write to pcap file |
port <n> | Filter by container’s internal port |
Diagnostic Sequence
| Step | Command | What It Reveals |
|---|---|---|
| 1 | ip addr | Container’s IP and interface state |
| 2 | ip route | Container’s routing table |
| 3 | ss -tlnp | Listening sockets |
| 4 | tcpdump -i eth0 | Whether packets leave/return |
| 5 | curl -v | End-to-end connectivity |
nsenter vs. docker exec
| Aspect | nsenter | docker exec |
|---|---|---|
| Runs from | Host | Inside container |
| Uses host tools | Yes | No |
| Enters cgroups | No | Yes |
| Works if container lacks shell | Yes | No |
| Recommended for | Network debugging | Application access |
Best Practices
✅ Do This:
# Get the PID with a format template
docker inspect -f '{{.State.Pid}}' my-container # ✅
# Enter only the network namespace for network debugging
sudo nsenter -t $PID -n # ✅
# Capture on the container's internal port
tcpdump -i eth0 -nn port 80 # ✅
# Use -c to limit capture size
tcpdump -i eth0 -c 1000 -w capture.pcap # ✅
❌ Don’t Do This:
# Run tcpdump on the host without entering the namespace
sudo tcpdump -i eth0 # sees host traffic, not container's # ❌
# Capture on the host-mapped port inside the namespace
tcpdump -i eth0 port 8080 # container listens on 80 # ❌
# Assume docker exec works when the image lacks tools
docker exec my-container tcpdump # may fail: tcpdump not installed # ❌
# Enter the mount namespace and expect host tools to be available
nsenter -t $PID -a tcpdump # tcpdump may not exist in container # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| tcpdump captures nothing | Running on host without namespace entry | nsenter -t $PID -n first |
| Capture filter uses host port | Host-mapped port not visible inside | Use container’s internal port |
nsenter command not found | util-linux not installed | Install util-linux on host |
| Shell exits immediately | Target container is stopped | Use a running container |
| Capture file is empty | Wrong interface name | Verify with ip addr inside namespace |
Real-World Examples
1. Get Container PID
docker inspect -f '{{.State.Pid}}' my-app
2. Enter Network Namespace
sudo nsenter -t 12345 -n
3. Verify Container Interfaces
ip addr show
4. Check Listening Ports
ss -tlnp
5. Capture on Container Port
tcpdump -i eth0 -nn port 3000
6. Save Capture
tcpdump -i eth0 -c 500 -w /tmp/app.pcap port 3000
7. Test DNS from Container
getent hosts api.example.com
8. Check Routing Table
ip route
9. Non-Interactive Capture
sudo nsenter -t $PID -n tcpdump -i eth0 -nn port 80
10. Exit Namespace
exit
Visual
Network Namespace Isolation
┌──────────────────────────────────────────────────────────────┐
│ HOST NETWORK NAMESPACE │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ eth0 (host IP) ││
│ │ docker0 bridge ││
│ │ veth pairs ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ veth-host │ │ veth-host │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
├───────────┼────────────────────────┼─────────────────────────┤
│ ▼ ▼ │
│ CONTAINER A NS CONTAINER B NS │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ lo │ │ lo │ │
│ │ eth0 (172.17.x) │ │ eth0 (172.17.y) │ │
│ │ own routes │ │ own routes │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ Host tcpdump sees veth-host, not eth0 inside container. │
│ nsenter enters the container's namespace. │
│ │
└──────────────────────────────────────────────────────────────┘
nsenter Workflow
┌──────────────────────────────────────────────────────────────┐
│ HOST TERMINAL │
│ │
│ ┌─────────────────┐ │
│ │ docker inspect │ Get PID │
│ │ -f '{{.State.Pid}}' │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ nsenter -t PID │ Enter network namespace │
│ │ -n │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ CONTAINER'S NETWORK VIEW │ │
│ │ │ │
│ │ ip addr → container's eth0 │ │
│ │ ss → container's sockets │ │
│ │ tcpdump → container's traffic │ │
│ │ │ │
│ │ Host tools available │ │
│ │ Host filesystem available (without -m) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ The host's tcpdump binary runs in the container's │
│ network context. │
│ │
└──────────────────────────────────────────────────────────────┘
Port Mapping and Capture
┌──────────────────────────────────────────────────────────────┐
│ docker run -p 8080:80 nginx │
│ │
│ ┌─────────────────┐ │
│ │ HOST │ │
│ │ port 8080 │ iptables DNAT → 172.17.0.2:80 │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ CONTAINER │ │
│ │ port 80 │ Application listens here │
│ └─────────────────┘ │
│ │
│ Inside namespace: │
│ tcpdump port 80 ← captures traffic │
│ tcpdump port 8080 ← captures nothing │
│ │
│ The mapping happens in the host's iptables, │
│ not inside the container's namespace. │
│ │
└──────────────────────────────────────────────────────────────┘
Diagnostic Decision Flow
┌──────────────────────────────────────────────────────────────┐
│ CONTAINER CANNOT REACH EXTERNAL SERVICE │
│ │
│ ┌─────────────────┐ │
│ │ ip route inside │────► No default route? │
│ │ container │ → Network config issue │
│ └────────┬────────┘ │
│ │ Default route exists │
│ ▼ │
│ ┌─────────────────┐ │
│ │ DNS resolution │────► Fails? │
│ │ (getent hosts) │ → DNS issue │
│ └────────┬────────┘ │
│ │ Resolves │
│ ▼ │
│ ┌─────────────────┐ │
│ │ tcpdump on eth0 │────► No packets leave? │
│ │ │ → App binding issue │
│ └────────┬────────┘ │
│ │ Packets leave │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Response │────► No response? │
│ │ returns? │ → Host iptables, bridge, │
│ │ │ or external network │
│ └─────────────────┘ │
│ │
│ Each step isolates the layer where the problem lives. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Network namespace | Isolated per container; invisible from host |
nsenter | Enters namespace from host using container PID |
| PID lookup | docker inspect -f '{{.State.Pid}}' <name> |
| Network-only entry | nsenter -t <pid> -n |
| All namespaces | nsenter -t <pid> -a |
| Capture interface | eth0 inside namespace |
| Capture port | Container’s internal port, not host-mapped |
| Save capture | tcpdump -w file.pcap |
nsenter vs docker exec | Host tools, no cgroup limits vs. container tools, cgroup limits |
Key takeaways:
- Each container has its own network namespace. The interfaces, routing table, and sockets that belong to the container are not visible from the host’s default namespace. Running
tcpdumpon the host captures host traffic, not container traffic. nsenterenters the namespace from the host. It uses the container’s PID and the-nflag to enter only the network namespace. The host’s tools—tcpdump,ss,ip—remain available because the filesystem is still the host’s.- The capture port is the container’s internal port. A container started with
-p 8080:80listens on port 80 inside the namespace. The mapping from 8080 to 80 happens in the host’s iptables rules. Capturing on port 8080 inside the namespace captures nothing. nsenterbypasses cgroup limits. A container that is out of memory may be unable to rundocker exec, butnsentercan still enter its namespace because it does not enter the cgroup. This makes it the tool of last resort when the container is unhealthy.- The diagnostic sequence isolates the failing layer. Check the routing table first, then DNS resolution, then whether packets leave the container, then whether responses return. Each step narrows the problem to a specific layer: container configuration, DNS, application binding, host iptables, or external network.
Remember: Docker networking is namespace-based isolation, and debugging it requires entering the namespace where the problem lives. The host’s network view is not the container’s network view. nsenter bridges the two without requiring tools inside the container or modifying the running container. tcpdump inside the namespace captures the packets the container actually sends and receives. The port filter must match the container’s internal port, not the host-mapped port, because the mapping happens outside the namespace. The diagnostic sequence—routing, DNS, packet capture, response—narrows the problem to the layer that is failing.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!