Skip to content
DevOps AI ToolKit
Newsletter

Kali Linux on Docker · Part 9 of 16

Use Nmap From a Kali Linux Docker Container

Difficulty: Intermediate ~16 min Part 9/16
Prerequisites: Kali Linux fundamentalsBasic Docker knowledge
Series progress9 / 16
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. web runs nginx, 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 from kali, the hostname web resolves to the web container. (Review how that DNS works in Kali Docker networking.)
  • command: sleep infinity keeps the Kali container alive so you can exec into 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 the security-lab network. 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 -d creates the network and starts both services in the background (-d = detached).
  • docker compose exec kali bash runs an interactive Bash shell inside the already-running kali container. (exec runs a command in an existing container; contrast with run, 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
  • nmap is the scanner; web is the target. Because you are on the security-lab network, Docker DNS resolves web to 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, or filtered.

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 Tipopen means something accepted a TCP connection. closed means the host answered but nothing is listening there. filtered means Nmap got no answer at all — usually a firewall or network policy silently dropping packets. In a deployment audit, an expected port showing filtered instead of open is 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
  • -sV enables 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 (-sT connect scans, and the -sV probes 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 the NET_RAW / NET_ADMIN capabilities 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, then nmap web and nmap -sV web. Record the observed ports. Then edit compose.yaml to swap web for an image that also listens on another port (or add a second service), docker compose up -d again, re-scan, and watch the new port appear in your Observed column.

Try It Yourself

A tighter loop to cement the workflow:

  1. Write down the Expected State first: for a stock nginx web, that is “port 80 open, HTTP, nothing else.”
  2. nmap web — record the Observed open ports. Do they match the expectation?
  3. nmap -sV web — upgrade guesses to evidence. Confirm the software and version answering on 80.
  4. Introduce a change (a second listener or a different image) and re-scan. Note precisely which line is new.
  5. 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 web and a plain ping web (or nc -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

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-lab network, nmap web discovers open ports by service name (thanks to Docker DNS), and nmap -sV web upgrades 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 -sV scans 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.

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

Related on DevOps AI Toolkit