Kali Linux on Docker · Part 4 of 16
Build a Custom Kali Linux Docker Image
Series curriculum (16 lessons)
Installing tools by hand every time you start a container works for a quick check, but it does not scale. The moment you close that container, the tools are gone, and the next person who runs your image has to remember the exact same apt-get install line you did. The fix is a Dockerfile — a small text file that describes, step by step, how to build an image with your tools already baked in. In this lesson you will write a Dockerfile for a lean Kali toolbox, build it, run it, and understand every instruction so you can extend it later.
What a Dockerfile actually is
A Dockerfile is a recipe. Docker reads it top to bottom, runs each instruction, and saves the result as a reusable image — a frozen filesystem plus a bit of metadata about how to start it. Once built, that image is a fixed artifact: anyone who pulls it gets exactly the tools you put in, in exactly the versions the base image pinned. That is the whole point. Instead of “install these ten packages and hope you typed it right,” you ship one file, and the build is repeatable on any machine with Docker.
Here is the toolbox we are going to build. Create an empty directory, drop this in a file named Dockerfile (no extension), and we will walk through it line by line.
FROM kalilinux/kali-rolling
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
dnsutils \
iproute2 \
iputils-ping \
jq \
nmap \
openssl \
tcpdump \
traceroute \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
CMD ["bash"]
That is a complete, working image in four instructions. Let’s break down what each one does.
FROM kalilinux/kali-rolling
Every image starts from a base image, and FROM names it. Here we build on top of kalilinux/kali-rolling, the official Kali rolling-release image. Docker pulls that image first, then layers your changes on top. You are not building Kali from scratch — you are inheriting a full Kali userland and adding exactly the packages you care about. FROM must be the first real instruction in every Dockerfile.
💡 Note —
kali-rollingis a moving target: it tracks Kali’s rolling release, so rebuilding next month may pull newer package versions. That is usually what you want for a security toolbox, but see the DevOps Perspective below on pinning when you need bit-for-bit reproducibility.
RUN apt-get update && apt-get install ...
RUN executes a command at build time and saves the result as a new layer in the image. This is the instruction that actually installs your tools. A few things are deliberate here:
apt-get update && apt-get installare chained in oneRUN. They are joined with&&and line-continuation backslashes (\) so the whole thing is a single instruction that produces a single layer. If you splitupdateandinstallinto two separateRUNlines, Docker can cache theupdatelayer and later reuse a stale package index — you would install old or missing packages. Keeping them together avoids that classic trap.-yanswers “yes” to apt’s prompts automatically. A build is non-interactive; there is no human to type “Y”, so without this the build hangs or fails.--no-install-recommendstells apt to install only the packages you asked for, not the long list of “recommended” extras Debian would normally pull in. On a toolbox image that trims a lot of weight — smaller image, faster pulls, less to keep patched.apt-get clean && rm -rf /var/lib/apt/lists/*deletes apt’s downloaded package archives and the package index it fetched duringupdate. You do not need them once the tools are installed, and because this happens in the sameRUNlayer, the deleted files never get baked into the image. Do the cleanup in a laterRUNand it would not help — the files would already be frozen in an earlier layer.
The tools themselves are a focused DevOps troubleshooting kit: curl (HTTP), dnsutils (dig/nslookup), iproute2 (ip/ss), iputils-ping, jq (JSON), nmap (port/service scanning), openssl (TLS inspection), tcpdump (packet capture), and traceroute (path tracing). Nothing you will not use.
WORKDIR /workspace
WORKDIR sets the working directory for everything that follows — and for the shell you land in when you run the container. Here we set /workspace, so when you start the container you begin in /workspace rather than /. It is the natural place to mount a volume later (covered in volumes and persistence) so your scan output survives after the container exits. If the directory does not exist, WORKDIR creates it.
CMD ["bash"]
CMD sets the default command that runs when someone starts a container from this image without specifying one. ["bash"] means “drop me into a Bash shell.” We use the JSON-array form (called exec form) because it runs bash directly as the container’s main process, without wrapping it in a shell — cleaner signal handling and a clearer process tree. Unlike RUN, CMD does not execute during the build; it only defines what happens at run time, and you can always override it on the command line.
Why layer caching matters
Each instruction in a Dockerfile produces a layer, and Docker caches layers. When you rebuild, Docker reuses cached layers for any instruction whose inputs have not changed, and only re-runs from the first line that did change downward. That is why the order of instructions matters: put the things that rarely change (your base image, your package install) near the top, and things that change often (copying in your own scripts, for example) near the bottom. In this small Dockerfile the payoff is simple — if you have not edited the file, a rebuild finishes almost instantly because every layer is a cache hit.
This is also why the single-RUN install block is written the way it is. It is one layer: change one package in that list and Docker rebuilds the whole install step (correct — you want a fresh, consistent set of tools), but leave it alone and the layer is reused untouched.
🛠️ DevOps Perspective — This Dockerfile is the deliverable. A reproducible, version-controlled toolbox beats a container you hand-tuned once and can never recreate. Commit the
Dockerfileto git and your whole team builds the identical environment from one source of truth. When you need true bit-for-bit reproducibility — say, for a CI job that must behave the same in six months — pin the base image to a digest (FROM kalilinux/kali-rolling@sha256:...) instead of the movinglatest, and pin package versions in the install line (nmap=7.94...). Rolling gives you fresh tools; digests and version pins give you a build you can reproduce exactly. Choose per use case.
Build it
From the directory that contains your Dockerfile, run:
docker build -t kali-devops-toolbox .
docker buildreads the Dockerfile and produces an image.-t kali-devops-toolboxtags the image with a friendly name so you can refer to it later instead of a random ID. (You can add a version too, e.g.-t kali-devops-toolbox:1.0.).is the build context — the directory Docker sends to the builder and where it looks for theDockerfile. The dot means “the current directory.”
You will watch Docker pull the base image (the first time), run your apt-get install, and finish with a line like naming to ... kali-devops-toolbox. Confirm it landed:
docker images kali-devops-toolbox
Run it
Start a container from your new image:
docker run --rm -it kali-devops-toolbox
--rmdeletes the container automatically when you exit, so you do not accumulate dead containers. The image stays; only the disposable running instance is cleaned up.-ikeeps STDIN open so you can type into the shell.-tallocates a TTY so the shell is interactive and formatted like a normal terminal.
Together -it gives you an interactive shell. Because your CMD is ["bash"], you land directly in Bash — inside /workspace, with every tool ready. Try a couple:
dig +short example.com
curl -sI https://example.com
nmap --version
No installing, no waiting. That is the difference between a hand-configured container and a built image.
🔐 Security Note — Only scan, inspect, or test systems you own or have explicit permission to assess.
example.comis a safe, public endpoint intended for exactly this kind of demonstration. Point these tools at your own lab services, not third parties.
💡 Note — Some tools in this toolbox (notably
tcpdumpand certainnmapscan modes) need extra Linux capabilities to capture raw packets. Rather than reaching for--privileged, which strips away most of the container’s isolation, grant only the narrow capability the task needs —docker run --cap-add=NET_RAW .... Least privilege is covered in the networking and packet-capture lessons of this series.
This is the start of your kali-devops-lab project
Do not think of this Dockerfile as a throwaway. It is the foundation of a small project you will grow across this series. As you go, you will build out a layout like:
kali-devops-lab/
└── kali/
└── Dockerfile ← the file you just wrote
In later lessons you will add a compose.yaml beside it to wire the toolbox into lab networks, mount volumes into /workspace so results persist, and eventually assemble a full multi-service security lab. Everything builds on this one image. Keep kali-devops-lab/kali/Dockerfile in version control and treat it as the canonical definition of your toolbox — the build-your-DevOps-toolbox lesson pulls the whole thing together.
Try It Yourself
- Create a fresh directory and add the
Dockerfileabove. - Build it:
docker build -t kali-devops-toolbox . - Add one more tool — say
netcat-openbsd— to the install list, rebuild, and watch which layers are cache hits and which rebuild. - Run
docker run --rm -it kali-devops-toolboxand verify your new tool is present. - Compare image sizes with and without
--no-install-recommends(docker images) to see how much the recommends actually add.
🧪 Try It — Delete the image (
docker rmi kali-devops-toolbox) and rebuild it. Because yourDockerfileis the source of truth, the toolbox comes back identical. That round-trip is what “reproducible” means in practice.
Common Problems
Build fails at apt-get update with hash or “Could not resolve” errors.
This is almost always DNS or network on the build host, or a temporarily stale mirror. Check the host can reach the internet, then rebuild. If it persists, re-run to pull a fresh package index — a mid-flight mirror update can cause a transient hash mismatch.
Unable to locate package <name> during install.
The package name is wrong or the index is stale. Because apt-get update and install are chained in one RUN, a fresh build always refreshes the index — but double-check the exact Debian/Kali package name (for example the DNS tools live in dnsutils, and modern netcat is netcat-openbsd). A typo in the list fails the whole layer.
pull access denied or manifest unknown / “image not found” on FROM.
Docker cannot find kalilinux/kali-rolling. Confirm the name is spelled correctly, that you are online, and that you can pull it directly with docker pull kalilinux/kali-rolling. Behind a proxy or on a locked-down build agent, the registry may simply be unreachable.
The build “hangs.”
Usually apt waiting on a prompt — confirm -y is present so installs are non-interactive. It can also just be a slow first-time pull of the base image; give it a moment on the initial build.
docker build says it can’t find the Dockerfile.
You are not in the directory that contains it, or the file is misnamed (it must be exactly Dockerfile, capital D, no extension). Run ls to confirm, and remember the trailing . in docker build -t name . points at that directory.
🔎 Troubleshooting Tip — Think in expected vs observed state. You expect a tool to be present after the build; if
command not foundappears at run time, the problem is at build time. Re-read the build output for the install step — a failed package leaves a red line there, even if the build otherwise “succeeded” enough to produce an image.
Where to go next
- Installing Kali tools — the manual, in-container approach this Dockerfile automates.
- Volumes and persistence — mount storage into
/workspaceso your output survives container exit. - Build your DevOps toolbox — grow this image into the full
kali-devops-labproject. - Docker Compose Generator — a visual tool to draft the
compose.yamlyou will add beside this Dockerfile. - Docker Production Readiness Auditor — check any image or Compose file against 50 production best-practice rules.
What You Learned
- A Dockerfile turns “install these tools by hand” into a repeatable, version-controlled build.
- What
FROM,RUN,WORKDIR, andCMDeach do — and why the install is a single chainedRUN. - Why
--no-install-recommendsplus same-layerapt-get clean && rm -rf /var/lib/apt/lists/*keeps the image small. - How layer caching makes rebuilds fast and why instruction order matters.
- How to build (
docker build -t kali-devops-toolbox .) and run (docker run --rm -it kali-devops-toolbox) your custom image. - That this Dockerfile is the reusable foundation of the
kali-devops-lab/kali/Dockerfileproject you extend across the series.
Recommended Reading
- View Book on Amazon Affiliate link
Kali Linux Revealed
The official guide to Kali Linux fundamentals, configuration, and administration.
- View Book on Amazon Affiliate link
The Ultimate Kali Linux Book
A broad Kali Linux reference covering installation, configuration, and its security tooling.
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