| |

Docker 91 🐳 Debugging Networking Issues: tcpdump, nsenter, and Inspecting Container Network Namespaces

⏱️ Estimated read: 12 min | |

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

CommandPurpose
docker inspect -f '{{.State.Pid}}' <name>Get container PID
nsenter -t <pid> -nEnter network namespace only
nsenter -t <pid> -n -p -mEnter network, PID, and mount
nsenter -t <pid> -aEnter all namespaces

Tcpdump Options for Container Debugging

OptionPurpose
-i eth0Capture on container’s eth0
-nnNo 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

StepCommandWhat It Reveals
1ip addrContainer’s IP and interface state
2ip routeContainer’s routing table
3ss -tlnpListening sockets
4tcpdump -i eth0Whether packets leave/return
5curl -vEnd-to-end connectivity

nsenter vs. docker exec

Aspectnsenterdocker exec
Runs fromHostInside container
Uses host toolsYesNo
Enters cgroupsNoYes
Works if container lacks shellYesNo
Recommended forNetwork debuggingApplication 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

PitfallWhy It HappensFix
tcpdump captures nothingRunning on host without namespace entrynsenter -t $PID -n first
Capture filter uses host portHost-mapped port not visible insideUse container’s internal port
nsenter command not foundutil-linux not installedInstall util-linux on host
Shell exits immediatelyTarget container is stoppedUse a running container
Capture file is emptyWrong interface nameVerify 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

ItemValue
Network namespaceIsolated per container; invisible from host
nsenterEnters namespace from host using container PID
PID lookupdocker inspect -f '{{.State.Pid}}' <name>
Network-only entrynsenter -t <pid> -n
All namespacesnsenter -t <pid> -a
Capture interfaceeth0 inside namespace
Capture portContainer’s internal port, not host-mapped
Save capturetcpdump -w file.pcap
nsenter vs docker execHost 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 tcpdump on the host captures host traffic, not container traffic.
  • nsenter enters the namespace from the host. It uses the container’s PID and the -n flag 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:80 listens 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.
  • nsenter bypasses cgroup limits. A container that is out of memory may be unable to run docker exec, but nsenter can 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.

Was this helpful?


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!