Kali Linux on Docker · Part 13 of 16
Packet Capture With tcpdump in Kali Docker
Series curriculum (16 lessons)
When something on your network is misbehaving — a connection that hangs, a TLS handshake that fails, a service that swears it sent a response nobody received — the ground truth is always on the wire. tcpdump lets you read that wire. Running it from a disposable Kali container keeps the capture tooling reproducible and off your host, but it comes with two things you must understand first: which network the container can actually see, and what privilege packet capture really needs.
This lesson shows you how packet capture works inside a container’s network namespace, how to grant only the one capability tcpdump requires (not --privileged), and how to save a capture to the host so you can read it back later.
The two ideas that make container capture make sense
Before a single packet, hold two concepts in your head.
1. A container has its own network namespace. When you run a normal container, Docker gives it a private network stack — its own interfaces, its own routing table, its own view of “the network.” Inside that container tcpdump can only see traffic that actually crosses its interfaces. It cannot see your host’s Wi-Fi or your host’s other containers unless they share the same network. This is the number-one surprise for people new to container capture: you run tcpdump, you see almost nothing, and you assume it is broken. It is not — you are simply looking at a different, isolated network stack than the one carrying the traffic you care about.
2. Packet capture is a privileged operation. Reading raw frames off an interface requires the CAP_NET_RAW Linux capability. A default container drops it, so tcpdump fails with a permission error. The wrong fix is to reach for --privileged; the right fix is to add back the one capability capture needs. We will get to exactly why below.
💡 Note — “Namespace” and “capability” are the two levers here. The namespace decides what traffic is visible; the capability decides whether you are allowed to read it. Almost every capture problem is one of these two.
Which interfaces can the container see?
Start Kali and list the interfaces from inside it:
docker run --rm -it --name kali kali-devops-toolbox
--rmremoves the container when you exit, keeping the environment disposable.-ikeeps STDIN open and-tallocates a TTY, so you get an interactive shell.--name kaligives it a stable name for later reference.kali-devops-toolboxis the custom image built earlier in this series; substitutekali-rollingif you have not built it yet.
Inside the container, ask tcpdump what it can capture on:
tcpdump -D
tcpdump -Dlists the interfaces available inside this namespace. On a default bridge container you will typically seeeth0(the container’s link to the Docker network) andlo(loopback) — and nothing else. There is nowlan0, no host interface. That short list is your visibility boundary.
This is the moment to internalize expected-vs-observed thinking: you expect to capture the traffic between your lab services, so you must make sure that traffic actually crosses eth0 of the container you are running tcpdump in.
🛠️ DevOps Perspective — This is the same model you hit with a Kubernetes sidecar: a debug container sees the pod’s network namespace, not the node’s. Getting comfortable with “what can this namespace see?” in Docker is exactly the reflex you need when you
kubectl debuga pod later.
Grant the right privilege — NOT —privileged
Try to capture and, on a default container, you will hit this:
tcpdump: eth0: You don't have permission to capture on that device
(socket: Operation not permitted)
The container dropped CAP_NET_RAW, so the kernel refuses to open a raw capture socket. You will find plenty of forum answers that say “just add --privileged.” Do not.
--privileged does not grant a capability — it grants all of them, disables the seccomp and AppArmor filters that normally sandbox the container, and exposes host devices. It effectively removes most of the isolation that made running an offensive toolkit in a container safe in the first place. A privileged Kali container is close to a root shell on your host.
Grant only what capture needs instead:
docker run --rm -it \
--cap-add=NET_RAW \
--name kali \
kali-devops-toolbox
--cap-add=NET_RAWadds back the single capability that letstcpdumpopen a raw socket and read frames offeth0. Everything else the container still lacks — this is least privilege in one flag.- The rest of the flags are unchanged from before.
If you also need to change interface state — for example put an interface into promiscuous mode or bring one up — add NET_ADMIN as well:
docker run --rm -it \
--cap-add=NET_RAW \
--cap-add=NET_ADMIN \
--name kali \
kali-devops-toolbox
--cap-add=NET_ADMINgrants network administration rights (interface config, promiscuous mode). Add it only if plainNET_RAWproves insufficient — start narrow, widen only when a real error tells you to.
🔐 Security Note — Never normalize
--privilegedfor packet capture. It removes seccomp/AppArmor confinement, hands the container every capability, and exposes host devices — a container breakout from there is a host compromise. Packet capture needs exactly one capability,CAP_NET_RAW(plusNET_ADMINonly for promiscuous/interface changes). Adding those two is a scalpel;--privilegedis a sledgehammer. Least privilege is not a nicety here — it is the whole reason to run offensive tooling in a container rather than on the host. Only capture, scan, or inspect traffic on systems you own or have explicit permission to assess.
Capturing with filters
Now the actual capture. tcpdump’s power is in narrowing the firehose down to the packets you care about with a capture filter (BPF syntax). A few you will use constantly:
tcpdump -i eth0 -n
-i eth0captures on the container’seth0interface.-nskips DNS resolution of addresses so output is fast and does not itself generate lookup traffic that pollutes your capture.
Filter by host, port, and protocol:
tcpdump -i eth0 -n host web
tcpdump -i eth0 -n port 80
tcpdump -i eth0 -n 'host web and port 80'
tcpdump -i eth0 -n 'tcp and port 443'
host webkeeps only packets to or from the host namedweb(Docker’s embedded DNS resolves that name on a user-defined network).port 80keeps only packets on TCP/UDP port 80.host web and port 80combines both — the quotes protect the spaces from the shell.tcp and port 443narrows to TLS traffic. Good filters are the difference between a readable capture and 40,000 unrelated packets.
A couple more flags worth knowing:
tcpdump -i eth0 -n -c 20 -v port 80
-c 20stops after 20 packets — invaluable so a capture does not run forever.-vincreases verbosity (TTL, IP options, etc.);-vv/-vvvadd more.
Save to a PCAP file that survives the container
A container is disposable — when it exits, its filesystem is gone, and so is any capture you left inside it. Write the PCAP to a bind-mounted directory so it lands on the host instead. Start Kali with your host’s current directory mapped to /workspace:
docker run --rm -it \
--cap-add=NET_RAW \
--network security-lab \
-v "$(pwd):/workspace" \
--name kali \
kali-devops-toolbox
--network security-labattaches Kali to the user-defined lab network so it can actually see the lab traffic (recall: the namespace decides visibility).-v "$(pwd):/workspace"bind-mounts your current host directory into the container at/workspace. Anything written there appears on the host immediately and persists after the container is gone.
Now capture to a file under that mount:
tcpdump -i eth0 -n -w /workspace/capture.pcap 'host web and port 80'
-w /workspace/capture.pcapwrites raw packets to a PCAP file instead of printing them. Because/workspaceis the bind mount,capture.pcapshows up in your host directory as it is written.- Press
Ctrl+Cto stop;tcpdumpreports how many packets it captured.
Read the capture back — either from the same container, from a fresh one, or with Wireshark on the host:
tcpdump -r /workspace/capture.pcap -n
tcpdump -r /workspace/capture.pcap -n 'port 80'
-r /workspace/capture.pcapreads packets from the file instead of a live interface. Reading a saved file needs no special capability — you can drop--cap-addentirely for offline analysis.- You can apply a display filter at read time (
'port 80') to re-slice a capture you already have, so it is fine to capture broadly and narrow later.
🏭 Why This Matters in Production — In a real incident you rarely analyze on the box that captured. You grab a short, filtered PCAP on the affected host (or a debug container beside it), copy it out via a mount or
docker cp, and analyze offline in Wireshark on your workstation — where you can follow TCP streams, spot retransmits, and see exactly where a handshake died. Capturing to a persisted/workspace/capture.pcapand reading it back withtcpdump -ris that workflow in miniature: capture with least privilege, persist the artifact, analyze somewhere safe.
Host capture vs container capture
Sometimes the container’s eth0 genuinely cannot see what you need — you want all the host’s traffic, or traffic on a network the container is not attached to. You have a few options, in order of preference:
- Attach the container to the right Docker network (
--network). Best first move: put Kali on the same user-defined network as the services you are debugging so their traffic crosses itseth0. Least privilege, full isolation. - Capture on the host directly with the host’s own
tcpdump. If you truly need the host’s physical interface, runningtcpdumpon the host (withsudo) is often cleaner and safer than weakening a container to reach host interfaces. --network hostmakes the container share the host’s network namespace, so it sees every host interface. This is powerful but it removes network isolation — the container is now on the host’s network stack directly.
⛔ Production Warning — Reach for
--network host(or worse,--privileged) only as a last resort, and never on a shared or production host. Sharing the host network namespace means the container can bind host ports and sniff host interfaces; combined with an offensive toolkit that is a lot of blast radius. If you only need lab traffic, a user-defined network plus--cap-add=NET_RAWgives you everything with none of the exposure.
Try It Yourself
🧪 Try It — Capture live lab traffic, persist it to the host, and read it back — all with a single capability.
- Create a lab network and a benign target:
docker network create security-labdocker run --rm -d --network security-lab --name web nginx
- Launch Kali with capture privilege, the lab network, and a host mount:
docker run --rm -it --cap-add=NET_RAW --network security-lab -v "$(pwd):/workspace" --name kali kali-devops-toolbox
- Confirm visibility inside Kali:
tcpdump -Dshould listeth0. - Start a capture to the mount, filtered to the target:
tcpdump -i eth0 -n -w /workspace/capture.pcap 'host web and port 80'
- In a second terminal, generate traffic:
docker exec kali curl -s -o /dev/null http://web(orcurl -I http://webfrom inside a second Kali shell). - Back in the capture terminal, press
Ctrl+C. It should report a non-zero packet count. - Read it back offline:
tcpdump -r /workspace/capture.pcap -n. You should see the HTTP request and response packets. - Confirm persistence: exit Kali, then
ls capture.pcapon the host — the file is still there. Open it in Wireshark if you like. - Clean up:
docker stop web && docker network rm security-lab.
Expected state: tcpdump -D shows eth0, the capture records packets, and capture.pcap survives on the host. If observed state differs, work through Common Problems.
Common Problems
Symptom: tcpdump: eth0: You don't have permission to capture on that device (socket: Operation not permitted).
The container lacks CAP_NET_RAW, so the kernel refuses the raw socket.
- Diagnose: you started the container without
--cap-add=NET_RAW. Confirm withdocker inspect -f '{{.HostConfig.CapAdd}}' kali— an empty list means capture is not permitted. - Fix: restart the container with
--cap-add=NET_RAW. Do not “fix” it with--privileged— that removes isolation you do not need to remove to capture packets. If you additionally need promiscuous mode, add--cap-add=NET_ADMIN.
Symptom: capture runs but shows no packets (or only ARP/broadcast noise).
Privilege is fine; visibility is the problem — the traffic you want is not crossing this namespace’s eth0.
- Diagnose:
tcpdump -Dshowseth0, but the target service is on a different network. Check withdocker inspect -f '{{json .NetworkSettings.Networks}}' kaliand confirm it lists the same network as the target. - Fix: attach Kali to the target’s network (
--network security-lab), then re-run the capture. Remember the container cannot see host or unrelated-container traffic by default — that isolation is deliberate.
Symptom: capture worked, but the PCAP file is gone after the container exits.
You wrote it inside the container’s ephemeral filesystem, not the bind mount.
- Diagnose: the container was started without
-v "$(pwd):/workspace", or you wrote to a path outside/workspace. - Fix: start with the mount and write to
-w /workspace/capture.pcap. Verify mid-capture that the file already appears on the host withls capture.pcap.
🔎 Troubleshooting Tip — Split “no packets” into its two possible causes the same way you split DNS from reachability. Permission failures throw an explicit “Operation not permitted” — fix with
NET_RAW. Visibility failures capture cleanly but show nothing relevant — fix the network the container is on. The error text tells you which half you are in.
Where to go next
- Not sure which network the container can see? Review Docker networking for Kali — it decides what your capture can reach.
- Want your PCAPs and other artifacts to survive every container run? See volumes and persistence.
- Lock down capture and every other privilege properly with container security best practices.
- Go deeper on the tool itself in tcpdump for DevOps.
What You Learned
- A container has its own network namespace:
tcpdump -Dshows only the interfaces (eth0,lo) inside it, so what you can capture is bounded by which Docker network the container is attached to. - Packet capture needs the
CAP_NET_RAWcapability; grant it with--cap-add=NET_RAW(plusNET_ADMINonly for promiscuous/interface changes) rather than--privileged, which strips most container isolation. - How to filter captures by interface,
host,port, and protocol, and how to cap a run with-c. - How to write a capture to
/workspace/capture.pcapover a bind mount so it persists on the host, then read it back offline withtcpdump -r— which needs no special capability. - When host capture (
--network hostor the host’s owntcpdump) is appropriate, and why a user-defined network plusNET_RAWis the least-privilege default.
Recommended Reading
- View Book on Amazon Affiliate link
Mastering Kali Linux for Advanced Penetration Testing
An advanced deep-dive into Kali for experienced security testers.
- View Book on Amazon Affiliate link
Kali Linux Penetration Testing Bible
A comprehensive reference for structured security-testing workflows with Kali.
Affiliate Disclosure: Some links on this page are affiliate links. If you purchase through one of these links, DevOps AI Toolkit may earn a commission at no additional cost to you. See our affiliate disclosure.
← Back to Kali Linux on Docker Back to Kali Linux