Skip to content
DevOps AI ToolKit
Newsletter

Kali Linux on Docker · Part 6 of 16

Kali Linux Docker Networking for DevOps Engineers

Difficulty: Intermediate ~20 min Part 6/16
Prerequisites: Kali Linux fundamentalsBasic Docker knowledge
Series progress6 / 16
Series curriculum (16 lessons)

When you run a Kali container, it comes up with its own IP address, its own routing table, and its own view of DNS — a miniature, self-contained network stack that looks and behaves almost exactly like a small Linux host on a private network. Understanding that stack is not a Kali-specific curiosity: it is the same networking model that sits underneath every production microservice you will ever troubleshoot. Learn it once here, on a disposable container you can break and rebuild in seconds, and you will read docker inspect, service-to-service connection failures, and DNS timeouts in production with far less guesswork.

This lesson keeps everything local. Only scan, inspect, or test systems you own or have explicit permission to assess — every command below targets your own containers and benign public endpoints like example.com.

The Container Network Namespace

The single most important idea in Docker networking is the network namespace. A namespace is a Linux kernel feature that gives a process its own private copy of a piece of the system. A network namespace gives a process its own private network stack: its own interfaces, its own IP addresses, its own routing table, its own ARP cache, and its own set of open ports.

When Docker starts your Kali container, it creates a fresh network namespace and moves the container’s processes into it. That is why the container sees an interface called eth0 with an IP that is nothing like your laptop’s IP, and why “port 8080” inside the container is a completely separate thing from “port 8080” on the host. They live in different namespaces, so they do not collide.

🛠️ DevOps Perspective — Every Kubernetes pod is, at its core, one or more containers sharing a single network namespace. When you understand that a namespace is just an isolated network stack, “why can two containers in the same pod talk over localhost but a container in another pod cannot” stops being magic and becomes obvious. This is the same primitive, all the way up the stack.

The container’s namespace is connected to the outside world through a virtual ethernet pair (a veth pair): a tiny software cable with one end inside the container (appearing as eth0) and the other end plugged into a bridge on the host. Think of it as a patch cable between the container and a virtual switch.

The Docker Bridge: docker0

By default, Docker creates a virtual switch on the host called docker0. It is a software bridge — the equivalent of a physical network switch, implemented in the Linux kernel. Every container on the default bridge network gets a veth cable plugged into docker0, which is how containers on the same host can reach each other and how they reach the outside world.

Here is the whole path a packet from a Kali container takes to reach the internet:

Internet
   |
Docker Host
   |
docker0
   |
Kali Container

Read it bottom to top for an outbound request: the Kali container sends a packet out eth0, it arrives on docker0, the Docker host applies NAT and forwards it out its real network interface, and it reaches the internet. Responses come back down the same path. docker0 is the hinge the whole thing turns on — it is both the default gateway for containers and the switch that connects them to one another.

💡 Notedocker0 gets its own IP on the host (commonly 172.17.0.1). That address is the container’s default gateway. If you have ever wondered where the mysterious 172.17.x.x addresses in docker inspect come from, this bridge is the answer.

Looking at the Stack From Inside the Container

Let’s start a Kali container and inspect its network from the inside. We use the official-style image and drop into a shell:

docker run --rm -it kalilinux/kali-rolling bash
  • --rm removes the container automatically when you exit, so you leave no clutter behind.
  • -i keeps STDIN open so you can type into the shell.
  • -t allocates a TTY so the shell is interactive and readable.
  • bash is the command we run inside the container.

If ip isn’t present in a minimal image, install it once inside the container with apt-get update && apt-get install -y --no-install-recommends iproute2 (the iproute2 package provides ip).

ip addr — what addresses do I have?

ip addr

ip addr lists every network interface in the current namespace and the addresses assigned to each. Inside the container you will typically see two:

  • lo — the loopback interface, 127.0.0.1. This is the container’s own localhost, private to its namespace. A process listening on 127.0.0.1 inside the container is reachable only from inside that container.
  • eth0 — the container’s link to docker0, with an address like 172.17.0.2/16. This is the IP other containers and the host use to reach it.

That eth0 address is the container IP. Docker assigns it automatically from the bridge network’s subnet (172.17.0.0/16 by default) via its built-in IPAM (IP Address Management). You did not configure it, and it changes every time the container restarts — which is exactly why you never hardcode container IPs and instead rely on DNS names (more on that below).

🔎 Troubleshooting Tip — When a service is “unreachable,” ip addr is diagnostic step one. No eth0? The container isn’t attached to a network. An eth0 with no IP? IPAM or the bridge is broken. This is the same first move you make on a bare-metal host that “fell off the network.”

ip route — where do packets go?

ip route

ip route prints the routing table — the rules that decide which interface a packet leaves by. Inside a default-bridge container you’ll see something like:

default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
  • The default via 172.17.0.1 line is the default gateway: anything not on the local subnet is sent to 172.17.0.1, which is docker0 on the host. This is how the container reaches the internet.
  • The 172.17.0.0/16 dev eth0 line says “addresses in this subnet are directly reachable on eth0” — that’s how the container talks to its neighbors on the same bridge without going through the gateway.

If a container can resolve DNS but can’t reach anything, a missing or wrong default route is a prime suspect — identical to diagnosing a misconfigured gateway on any Linux server.

cat /etc/resolv.conf — how do names get resolved?

cat /etc/resolv.conf

/etc/resolv.conf tells the system which DNS resolver to ask. Docker writes this file for the container. What you see depends on the network type:

  • On the default bridge, Docker usually copies the host’s resolver (e.g. nameserver 8.8.8.8), sometimes via a host-level stub.
  • On a user-defined bridge, you’ll see nameserver 127.0.0.11 — Docker’s embedded DNS server. That special address is the key to container name resolution: it lets containers find each other by name instead of by their shifting IPs.

Test resolution from inside the container:

getent hosts example.com

getent hosts performs a name lookup exactly the way a normal application would (through the system resolver), so it’s a faithful test of what your service will experience — more representative than tools that talk to DNS directly.

🏭 Why This Matters in Production — Understanding container DNS and network namespaces makes troubleshooting production microservices significantly easier. The overwhelming majority of “service A can’t reach service B” incidents are one of three things: A is looking up the wrong name, A resolved a stale IP, or A and B aren’t on the same network. ip addr, ip route, and /etc/resolv.conf answer all three, and they are the same three files whether you’re in a throwaway Kali container or a production pod.

NAT and Outbound Traffic

When your Kali container reaches out to example.com, its source address is a private 172.17.x.x that means nothing on the public internet. Docker makes this work with NAT (Network Address Translation): as the packet leaves docker0 and heads for the host’s real interface, the kernel’s iptables/nftables rules rewrite the source address to the host’s IP. Replies come back to the host and get translated back to the container. This is source NAT (masquerading) — the exact same mechanism your home router uses to share one public IP among many devices, and the same mechanism a cloud NAT gateway uses for a private subnet.

The practical consequence: outbound connections from a container work out of the box, but inbound connections do not — the outside world has no route to a private 172.17.x.x address. That’s the problem published ports solve.

Published Ports: -p

To let something outside the container reach a service inside it, you publish a port:

docker run --rm -it -p 8080:80 kalilinux/kali-rolling bash
  • -p 8080:80 maps host port 8080 to container port 80. The format is always HOST:CONTAINER.
  • A client connecting to http://localhost:8080 on the host hits Docker’s proxy/NAT rules, which forward the traffic across the namespace boundary to port 80 inside the container.

Publishing a port installs a destination NAT (DNAT) rule on the host: “traffic arriving on host:8080 → send to container:80.” This is precisely the job of an ingress controller or cloud load balancer in production — translate an externally reachable address/port into an internal service endpoint. -p is that concept in its smallest form.

⛔ Production Warning-p 8080:80 binds to all host interfaces by default, exposing the service to your whole network. To keep it local while testing, bind to loopback explicitly: -p 127.0.0.1:8080:80. Publish only the ports you actually need, and never expose a debugging or admin port to 0.0.0.0 on a shared host.

Host Networking: --network host

You can skip the namespace entirely:

docker run --rm -it --network host kalilinux/kali-rolling bash

With --network host, the container does not get its own network namespace. It shares the host’s stack directly: ip addr inside the container shows the host’s interfaces and IPs, there is no docker0 in the path, no NAT, and no -p mapping (a port opened in the container is opened directly on the host). This is occasionally useful — for raw packet-capture and network-scanning work where the extra layer gets in the way, or for squeezing out the small NAT overhead.

🔐 Security Note--network host removes the container’s network isolation. The container can now see and bind every host interface and port, sniff host traffic, and reach anything the host can reach on localhost — including services the host only meant to expose to itself (databases, metrics endpoints, the cloud metadata service). A compromise inside the container is now a compromise with the host’s full network reach. Treat host networking like --privileged: use it only when a specific tool genuinely requires it, prefer a targeted --cap-add NET_RAW/--cap-add NET_ADMIN on a normal bridge network instead, and never make it your default.

User-Defined Bridges

The default bridge network works, but it has a notable limitation: containers on it can only reach each other by IP, not by name. The fix is a user-defined bridge, which you create yourself:

docker network create labnet
docker run --rm -it --network labnet --name scanner kalilinux/kali-rolling bash
  • docker network create labnet creates a new isolated bridge network named labnet.
  • --network labnet attaches the container to it, and --name scanner gives the container a stable DNS name.

On a user-defined bridge you get automatic DNS-based service discovery: a container named web is reachable simply as web from any other container on labnet, because Docker’s embedded DNS at 127.0.0.11 resolves container names to their current IPs. This is the local mirror of how Kubernetes Services and cloud service discovery work — you connect to a stable name and the platform tracks the shifting IP behind it. User-defined bridges also isolate their members from containers on other networks, giving you clean segmentation for a lab. There’s a dedicated lesson on this: user-defined networks.

🛠️ DevOps Perspective — The moment you have two containers that need to talk (say, a Kali scanner probing a web target in a lab), put them on a user-defined bridge and address them by name. It’s more reliable than IPs, it survives restarts, and it teaches you the exact mental model — name-based discovery over a private network — that you’ll use for every service mesh and DNS-driven deployment in production.

Looking at the Stack From the Host

Two host-side commands turn Docker’s networking from a black box into something you can read directly.

docker network ls

docker network ls

docker network ls lists every Docker network on the host. Out of the box you’ll see three built-ins:

  • bridge — the default docker0 network described above.
  • host — the “no isolation” network used by --network host.
  • none — a namespace with no connectivity at all (loopback only), for containers that must be fully offline.

Any user-defined bridges (like labnet) show up here too, so this is your inventory of what networks exist.

docker network inspect bridge

docker network inspect bridge

docker network inspect dumps the full configuration of a network as JSON — the subnet, the gateway, the IPAM settings, and, critically, every container currently attached along with its IP. This is the host-side counterpart to running ip addr/ip route inside each container: instead of hopping into containers one by one, you get the whole map from one command. When a container “can’t reach” another, docker network inspect confirms in one shot whether they’re even on the same network and what IPs they actually hold.

🧪 Try It — Run docker network inspect bridge and find the Subnet and Gateway under IPAM. Now start a container and compare its eth0 from ip addr against the Containers list in the inspect output. The container’s IP will fall inside that subnet, and its gateway will match — the inside view and the outside view of the same namespace, reconciled.

Try It Yourself

Build a tiny two-container lab and trace a connection end to end.

# 1. Create an isolated network for the lab
docker network create labnet

# 2. Start a simple web target on it, named "web"
docker run -d --network labnet --name web nginx

# 3. Start Kali on the same network and drop into a shell
docker run --rm -it --network labnet --name scanner kalilinux/kali-rolling bash
  • -d runs the web container in the background (detached).
  • The web container is a stock nginx, serving on port 80 inside the lab.

Now, from inside the scanner shell, work down the stack:

ip addr                 # confirm eth0 has a labnet IP
ip route                # confirm a default route via the gateway
cat /etc/resolv.conf    # expect nameserver 127.0.0.11 (embedded DNS)
getent hosts web        # resolve the peer container BY NAME

If getent hosts web returns an IP, name-based discovery is working. Confirm reachability at the application layer:

apt-get update && apt-get install -y --no-install-recommends curl
curl -sS http://web/    # fetch the nginx welcome page over the lab network

When you’re done, exit the scanner (it auto-removes), then clean up:

docker rm -f web
docker network rm labnet

You just exercised, in miniature, the exact request path a production service uses: resolve a name, route the packet, cross the network, hit the app.

The Docker Networking Troubleshooting Workflow

When a container “can’t connect,” resist the urge to guess. Work down the layers in order — each step tells you whether the problem is above or below it, so you never re-check something you’ve already cleared:

  1. Container Running?docker ps. If it isn’t up (or is restarting), nothing else matters. Check docker logs first.
  2. Network Attached?docker network inspect <net> (or docker inspect <container>). Is the container on the network you expect, alongside the peer it’s trying to reach?
  3. IP?ip addr inside the container. Does eth0 exist and hold an address in the network’s subnet? No IP means IPAM/bridge trouble.
  4. Route?ip route inside the container. Is there a default route via the gateway? A missing default route breaks everything off-subnet.
  5. DNS?cat /etc/resolv.conf and getent hosts <name>. Can the name resolve? Wrong resolver or a stale/failed lookup stops the connection before a packet is even sent.
  6. TCP Port? — from inside the container, nc -vz <target> <port> (or curl). Does the port actually accept a connection? This separates “can’t reach the host” from “host is up but the port is closed/filtered.”
  7. Application? — only now suspect the app itself: wrong path, TLS mismatch, auth, a 500. If everything above passed, the network is fine and the fault is in the service.

This is the same top-of-funnel-to-bottom discipline you apply in production; Docker just makes every layer inspectable in seconds. For deeper dives at specific layers, see DNS troubleshooting for step 5, nmap service discovery for step 6, and tcpdump packet capture when you need to watch the packets themselves.

Common Problems

Symptom: container can reach IPs but not names. Resolution works nowhere, but curl http://172.17.0.3/ succeeds. This is a DNS-layer failure (step 5). Check /etc/resolv.conf — on a user-defined bridge it should point at 127.0.0.11. If you’re on the default bridge, remember it has no automatic name resolution between containers; move the containers to a user-defined bridge.

Symptom: two containers can’t reach each other by name. getent hosts web fails from scanner. Almost always they’re on different networks. Run docker network inspect <net> and confirm both appear in the Containers list. The default bridge also won’t do name resolution — user-defined bridges are required for it.

Symptom: published port isn’t reachable from the host. curl localhost:8080 refuses the connection even though -p 8080:80 was set. Verify the process inside the container is actually listening on 0.0.0.0:80, not 127.0.0.1:80 — a service bound to container-localhost is invisible to the port mapping. Also confirm the mapping with docker port <container>.

Symptom: everything breaks after adding --network host. Your -p flags now do nothing (they’re ignored with host networking) and ports may collide with services already on the host. This is expected: host networking removes the namespace, so there’s nothing to publish into. Drop back to a bridge network unless a tool specifically needs the host stack.

🔎 Troubleshooting Tip — Capture the “healthy” output of ip addr, ip route, and docker network inspect from a working container once. When something breaks later, a side-by-side diff against that known-good baseline turns “Expected State vs Observed State” from a slogan into a two-second comparison.

Where To Go Next

What You Learned

  • A container gets its own network namespace — private interfaces, IP, routing table, and ports — connected to the host through a veth pair into the docker0 bridge.
  • Inside a container, ip addr, ip route, and cat /etc/resolv.conf reveal the container IP, the default gateway, and the DNS resolver — the three things behind most connectivity failures.
  • NAT lets containers reach the internet with private IPs, published ports (-p HOST:CONTAINER) use DNAT to let the outside in, and both mirror how load balancers and cloud NAT gateways work in production.
  • --network host trades away network isolation for direct access to the host stack — powerful, occasionally necessary, and a real security exposure to be used deliberately.
  • User-defined bridges add DNS-based service discovery so containers reach each other by name, the same model as Kubernetes Services and cloud service discovery.
  • From the host, docker network ls and docker network inspect give you the whole map, and a layered workflow — Running → Attached → IP → Route → DNS → Port → App — turns “it can’t connect” into a systematic 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