Kali Linux on Docker · Part 9 of 16
Use Nmap From a Kali Linux Docker Container
Series curriculum (16 lessons)
Nmap is often introduced as an attacker’s reconnaissance tool, but for a DevOps engineer it is something much more mundane and much more useful: a way to ask a running service, “which ports are you actually listening on?” In this lesson you will run Nmap from a Kali container against a lab web service and use it to answer a deployment question — does the container expose what its manifest says it should?
⛔ Production Warning — Nmap is a scanner. Only scan, inspect, or test systems you own or have explicit written permission to assess. Scanning hosts, cloud tenants, or networks you are not authorized to test can be illegal and will get you blocked or reported. Everything in this lesson targets a local lab you build yourself. Do not point these commands at arbitrary external systems, a public IP you found, or a “just to see” target. When in doubt, the answer is no.
The Mental Model: Expected vs Observed State
Every deployment carries an intent — a manifest, a compose.yaml, a runbook — that declares which ports a service should expose. Nmap tells you what the service is really doing on the network. Good infrastructure validation is just comparing the two:
Expected State
vs
Observed State
When they match, you have evidence the deployment is correct. When they differ, you have found a bug: a missing service, a firewall dropping traffic, or — the interesting case — an extra port nobody meant to expose. Nmap turns “I think it’s fine” into “here is what is on the wire.”
🛠️ DevOps Perspective — This is the same discipline as reconciling desired vs actual state in Kubernetes or Terraform. You are not “hacking” the container; you are auditing it against its own spec. Framing scans as validation keeps you on the right side of both the law and your on-call rotation.
The Lab: One Isolated Network, One Target
We use the isolated lab from the Docker Compose security lab lesson: a user-defined bridge network called security-lab with a web service and a kali service attached to it. Nothing in this lab is published to your host or the internet — Kali and web can talk to each other and to nothing else.
A minimal compose.yaml for the lab looks like this:
services:
web:
image: nginx:stable
networks: [security-lab]
# NOTE: intentionally no "ports:" mapping — the lab stays internal.
kali:
image: kalilinux/kali-rolling
command: sleep infinity
networks: [security-lab]
networks:
security-lab:
driver: bridge
services:declares the containers.webrunsnginx, which listens on port 80 by default.networks: [security-lab]attaches each service to the shared user-defined bridge. On a user-defined network, Docker’s built-in DNS lets containers reach each other by service name — so fromkali, the hostnamewebresolves to thewebcontainer. (Review how that DNS works in Kali Docker networking.)command: sleep infinitykeeps the Kali container alive so you canexecinto it. Without a long-running command the container would start, have nothing to do, and exit.- There is deliberately no
ports:block — the lab is not reachable from your host, only from inside thesecurity-labnetwork. That isolation is the whole point.
Bring it up and get a shell inside Kali:
docker compose up -d
docker compose exec kali bash
docker compose up -dcreates the network and starts both services in the background (-d= detached).docker compose exec kali bashruns an interactive Bash shell inside the already-runningkalicontainer. (execruns a command in an existing container; contrast withrun, which creates a new one.)
Install Nmap inside the Kali container if it is not already present:
apt-get update && apt-get install -y --no-install-recommends nmap \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
--no-install-recommends skips optional extras to keep the image small; apt-get clean and removing /var/lib/apt/lists/* drop the package cache so you do not leave megabytes of index files behind. (To bake Nmap in permanently instead of installing it every session, see Build a DevOps security toolbox image.)
Scenario: “Web Is Supposed to Expose HTTP on Port 80”
Here is the concrete question this lesson answers. A deployment is supposed to expose HTTP on port 80. Does the deployed web container actually have that service available on the network? Let’s stop assuming and check.
Step 1: Discover Open Ports
From inside the Kali shell, scan the web service by name:
nmap web
nmapis the scanner;webis the target. Because you are on thesecurity-labnetwork, Docker DNS resolveswebto the container’s IP, so you never need to hand-code an address.- With no flags, Nmap runs a default TCP scan of the 1,000 most common ports and reports each as
open,closed, orfiltered.
Expected output for a healthy single-service web:
Starting Nmap ...
Nmap scan report for web (172.20.0.2)
Host is up (0.000090s latency).
Not shown: 999 closed tcp ports (reset)
PORT STATE SERVICE
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 0.12 seconds
Read this against the spec. Expected: port 80 open, everything else closed. Observed: exactly that. The SERVICE column here (http) is Nmap’s guess based on the well-known port number — it has not actually talked to the service yet. That distinction matters in the next step.
🔎 Troubleshooting Tip —
openmeans something accepted a TCP connection.closedmeans the host answered but nothing is listening there.filteredmeans Nmap got no answer at all — usually a firewall or network policy silently dropping packets. In a deployment audit, an expected port showingfilteredinstead ofopenis a strong signal of a firewall or misapplied network rule, not a dead service.
Step 2: Confirm the Service and Version
Knowing a port is open is not the same as knowing what is answering on it. Ask Nmap to actually probe the service:
nmap -sV web
-sVenables service and version detection. Instead of guessing from the port number, Nmap opens each port, reads the banners, and sends protocol-specific probes to identify the real software and version.
Expected output:
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.24.0
Now the SERVICE and VERSION columns are evidence, not inference: something is genuinely speaking HTTP on port 80, and it is nginx 1.24.0. That closes the loop on the scenario — the deployment does what its spec claims.
🏭 Why This Matters in Production — Comparing expected ports against actual service exposure can reveal deployment or firewall configuration mistakes long before a user or an auditor does. A service that should be internal but shows an open management port, an app that should serve HTTPS but is answering plaintext HTTP, or a “port 80 open” that is actually a stale debug server on 8080 — version detection surfaces all of these. Bake a scan of your own services into a staging check and you catch exposure drift automatically.
Step 3: Find the Unexpected Extra Port
The most valuable audits find things you did not expect. Suppose someone updates the web image to also run a small debug endpoint on port 8080 “temporarily,” and it never gets removed. Re-run the scan:
nmap -sV web
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.24.0
8080/tcp open http Werkzeug httpd 2.3.0 (Python)
Compare to spec once more. Expected: only port 80. Observed: 80 and 8080. That second line is a finding. Nobody meant to expose a Python debug server, but there it is on the network — exactly the kind of forgotten sidecar that becomes an incident. The value of the scan is not that it “hacked” anything; it is that it caught the gap between what the manifest declared and what the container actually put on the wire.
🔐 Security Note — Default Nmap TCP scans (
-sTconnect scans, and the-sVprobes here) work fine with no special privileges because they use ordinary sockets. Some Nmap scan types — SYN scan (-sS), OS detection (-O), and raw-packet features — need theNET_RAW/NET_ADMINcapabilities to craft raw packets. Add only those with a narrow--cap-add=NET_RAW(or--cap-add=NET_ADMIN) on the container. Do not reach for--privileged: it strips nearly all container isolation and hands the workload broad access to the host kernel, far more than a scanner needs. Least privilege means adding the one capability the tool requires, never all of them.
🧪 Try It — Recreate the scenario end to end:
docker compose up -d,docker compose exec kali bash, thennmap webandnmap -sV web. Record the observed ports. Then editcompose.yamlto swapwebfor an image that also listens on another port (or add a second service),docker compose up -dagain, re-scan, and watch the new port appear in your Observed column.
Try It Yourself
A tighter loop to cement the workflow:
- Write down the Expected State first: for a stock nginx
web, that is “port 80 open, HTTP, nothing else.” nmap web— record the Observed open ports. Do they match the expectation?nmap -sV web— upgrade guesses to evidence. Confirm the software and version answering on 80.- Introduce a change (a second listener or a different image) and re-scan. Note precisely which line is new.
- State the finding as a diff: “Expected 80 only; observed 80 + 8080 (Werkzeug). Extra port is unaccounted for.” That sentence is a validation result you could paste into a ticket.
Common Problems
Think expected vs observed here too: you expect a scan report listing port 80; anything else points at the cause.
Symptom: Failed to resolve "web" or nmap: web: Name or service not known
Nmap cannot find the target’s address. On a user-defined network this is almost always that Kali and web are not on the same network, so Docker DNS has no record to resolve. Confirm both services list security-lab under networks:, and check from inside Kali with getent hosts web (it should print an IP). Review Kali Docker networking for how service-name DNS works. The default bridge network does not provide name resolution — you must be on a user-defined network like security-lab.
Symptom: Note: Host seems down / 0 hosts up, or every port shows filtered
Nmap cannot reach the target at all. The usual cause is that the web container is not running — docker compose ps should show it Up. If web exited (a bad image or a crash-looping command), there is nothing to scan; check docker compose logs web. A blanket filtered result can also mean a network policy is dropping packets, but in the isolated lab a stopped target is the far more common explanation.
Symptom: nmap: command not found
Nmap is not installed in this Kali container. Run the apt-get install line above, or use a toolbox image that includes it (see Build a DevOps security toolbox image). Remember that a --rm container loses installed packages on exit — baking tools into an image avoids reinstalling every session.
Symptom: scan is slow, or -sS/-O reports “requires root privileges / operation not permitted”
Raw-packet scan types need extra capabilities the container does not have by default. Either stick to the default connect scan and -sV (which need no special privileges), or add a narrow --cap-add=NET_RAW — never --privileged. See the Security Note above.
🔎 Troubleshooting Tip — When results look wrong, separate “can I reach the target?” from “what is on the target?” Prove reachability first with
getent hosts weband a plainping web(ornc -vz web 80). Only once the host clearly resolves and responds should you trust — or distrust — the port list Nmap returns.
Where to Go Next
- Understand the DNS and bridge mechanics behind service-name scanning in Kali Docker networking.
- See the full lab this lesson builds on in the Docker Compose security lab.
- Make Nmap and friends permanent in Build a DevOps security toolbox image.
- Go deeper on the scanner itself in Nmap for DevOps.
What You Learned
- Nmap is an infrastructure-validation tool: it turns a deployment’s expected port/service spec into a testable observed result you can diff.
- From a Kali container on the
security-labnetwork,nmap webdiscovers open ports by service name (thanks to Docker DNS), andnmap -sV webupgrades port guesses into confirmed service-and-version evidence. - The high-value outcome is catching mismatches — a missing expected port (deployment/firewall issue) or an unexpected extra port like a forgotten debug server on 8080.
- Only ever scan systems you own or are explicitly authorized to test; default connect and
-sVscans need no special privileges, and raw-packet scans need a narrow--cap-add=NET_RAW, never--privileged. - When a scan fails, split the problem into reachability (resolves? container up? same network?) versus exposure (which ports answer), and let expected-vs-observed guide the diagnosis.
Recommended Reading
- View Book on Amazon Affiliate link
Kali Linux Penetration Testing Bible
A comprehensive reference for structured security-testing workflows with Kali.
- View Book on Amazon Affiliate link
Mastering Kali Linux for Advanced Penetration Testing
An advanced deep-dive into Kali for experienced security testers.
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