Kali in Docker — Method 1 (manual CLI)
Step-by-step Docker setup using docker run and docker exec.
Slower than Method 2, but every flag is visible and every step is something you
can reason about. Read this once even if you plan to use Method 2 — knowing what
the compose file is doing makes the whole lab easier to debug.
Before any audit happens, the engineer has to build a controlled environment to do the work inside. This section walks through that build by hand, one command at a time, on a regular laptop — the audit toolkit ends up isolated inside a "container" (a sealed-off process the laptop runs but does not share its files with). The reason to build it manually first, rather than running an automation script, is accountability: if you ever need to ask the engineer what a particular setting does and why, the answer cannot be "the script handled it" — they will have made that choice deliberately. Method 2 (the next section) automates the same build, but this section is the proof that the engineer understands what the automation is doing.
Docker behaves differently on Linux, Windows, and macOS. On Linux, the
container can share the host's network stack and NIC directly via
--network host. On Windows and macOS, Docker runs inside a
lightweight Linux VM, so true host networking is not available — the
container uses bridge mode with port mapping instead. The MCP server still
works the same way on all three; only the network plumbing differs. Tabs
below switch between the platforms.
Step 0 — Install Docker
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker
# Install Docker Desktop for Windows from:
# https://www.docker.com/products/docker-desktop
# During installation, choose the WSL2 backend
# (recommended over Hyper-V).
# Install Docker Desktop for Mac from:
# https://www.docker.com/products/docker-desktop
# Apple Silicon (M1/M2/M3/M4) is fully supported.
Step 1 — Pull the official Kali Linux image
The image is the same on all three platforms.
docker pull kalilinux/kali-rolling
Step 2 — Run the container
This is where the platforms diverge. Pick your tab. Each command boots
/sbin/init as PID 1 so that systemd is available inside the
container — the MCP server will run as a systemd service later.
docker run -d \
--network host \
--privileged \
--tmpfs /run \
--tmpfs /run/lock \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
--cgroupns=host \
--name kali-mcp \
kalilinux/kali-rolling \
/sbin/init
docker run -d `
--privileged `
--tmpfs /run `
--tmpfs /run/lock `
-p 5000:5000 `
--name kali-mcp `
kalilinux/kali-rolling `
/sbin/init
PowerShell uses the backtick (`) as its line-continuation character —
the equivalent of \ on Linux/macOS. The cgroup volume mount is omitted
because Docker Desktop manages cgroups via WSL2 automatically.
docker run -d \
--privileged \
--tmpfs /run \
--tmpfs /run/lock \
-p 5000:5000 \
--name kali-mcp \
kalilinux/kali-rolling \
/sbin/init
The cgroup volume mount is omitted because Docker Desktop on macOS manages cgroups inside its internal Linux VM.
What the flags do
--network host(Linux only) — container shares the host's network stack and NIC directly, no separate IP.--privileged— required for systemd to manage cgroups inside the container.--tmpfs /run,--tmpfs /run/lock— systemd needs these as writable tmpfs mounts.-v /sys/fs/cgroup:/sys/fs/cgroup:rw(Linux only) — gives systemd the host cgroup filesystem.--cgroupns=host(Linux only) — uses the host cgroup namespace; required for systemd.-p 5000:5000(Windows/macOS) — maps the MCP server port to the host./sbin/init— boots systemd as PID 1 instead of a shell.
Step 3 — Update apt and install the Kali toolset
Same on all three platforms.
docker exec kali-mcp apt update
docker exec kali-mcp apt install -y kali-linux-default
Substitute kali-linux-headless for a lighter install, or
kali-linux-everything for the full suite. The default metapackage
is what most readers will want.
Step 4 — Install the official Kali MCP server
docker exec kali-mcp apt install -y mcp-kali-server
Step 5 — Create the systemd service file
Writing the unit file with cat > ... << EOF from inside
a docker exec call avoids any line-ending issues on Windows hosts.
docker exec kali-mcp bash -c 'cat > /etc/systemd/system/kali-mcp-server.service << EOF
[Unit]
Description=Kali MCP Server
After=network.target
[Service]
ExecStart=/usr/bin/kali-server-mcp
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF'
Step 6 — Enable and start the service
docker exec kali-mcp systemctl daemon-reload
docker exec kali-mcp systemctl enable kali-mcp-server
docker exec kali-mcp systemctl start kali-mcp-server
Step 7 — Verify the service is running
docker exec kali-mcp systemctl status kali-mcp-server
You should see active (running). The MCP server now listens on:
| Platform | Address |
|---|---|
| Linux | 127.0.0.1:5000 |
| Windows | localhost:5000 |
| macOS | localhost:5000 |
Step 8 — Commit the container as a reusable image
Bake everything you have configured into a new image so subsequent runs do not repeat the install steps.
docker commit kali-mcp kali-mcp-ready
Step 9 — Future launches
docker run -d \
--network host \
--privileged \
--tmpfs /run \
--tmpfs /run/lock \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
--cgroupns=host \
--name kali-mcp \
kali-mcp-ready \
/sbin/init
docker run -d `
--privileged `
--tmpfs /run `
--tmpfs /run/lock `
-p 5000:5000 `
--name kali-mcp `
kali-mcp-ready `
/sbin/init
docker run -d \
--privileged \
--tmpfs /run \
--tmpfs /run/lock \
-p 5000:5000 \
--name kali-mcp \
kali-mcp-ready \
/sbin/init
The kali-mcp-server service comes up automatically every time
because of the systemd WantedBy=multi-user.target on the unit.
Quick reference
| Task | Command |
|---|---|
| Check container is running | docker ps |
| Open a shell in the container | docker exec -it kali-mcp /bin/bash |
| Check MCP server status | docker exec kali-mcp systemctl status kali-mcp-server |
| Stop the MCP server | docker exec kali-mcp systemctl stop kali-mcp-server |
| Restart the MCP server | docker exec kali-mcp systemctl restart kali-mcp-server |
| Stop the container | docker stop kali-mcp |
| Remove the container | docker rm kali-mcp |
--privileged grants the container elevated access to the host.
Run this lab on a machine you treat as a single-purpose audit workstation,
not on a shared development laptop. The same point applies to Method 2.
Check yourself
Three questions on the manual setup
You can copy and paste the commands and end up with a working container. You shouldn't rely on copy-paste for the bits where being wrong has real-world consequences — these three test the parts that matter.
Why is --privileged on the docker run command, and what is the cost of using it?
It runs the container as root inside; there is no cost — every container runs that way.
--privileged goes much further. It is not free.
It is required so systemd can manage cgroups inside the container; the cost is that the container has meaningfully elevated access to the host, so the lab should live on a single-purpose audit workstation.
It enables faster networking inside the container.
--privileged. The flag is about kernel capabilities and device access, not throughput.
Why does the container boot /sbin/init as PID 1 instead of a shell?
Because shells cannot be PID 1 inside Docker containers.
So that systemd is available, which is what runs mcp-kali-server as a managed service that auto-starts on every container boot.
WantedBy=multi-user.target only works if systemd is PID 1.
To prevent the container from being controlled with docker exec.
docker exec works exactly the same whether PID 1 is a shell or systemd. The choice is about service management, not about access.
After Step 7, where is the MCP server reachable from?
From anywhere on your LAN — the port is published to your NIC.
--network host, it is still loopback-bound by the server itself.