16.1.2.2 Build Tool Missing
A focused guide to Build Tool Missing, connecting core concepts with practical Docker and container operations.
Build tool missing errors occur when a Dockerfile instruction invokes a command, compiler, interpreter, or utility that does not actually exist inside the image at the point that instruction runs, producing a clear "command not found" or equivalent failure that is usually fast to diagnose once the underlying assumption about what a given base image includes is corrected.
The most direct symptom
A missing tool produces an unambiguous shell-level error, distinct from an application-level failure, that names the missing command directly:
FROM alpine:3
RUN make
/bin/sh: make: not found
This kind of error is one of the more straightforward build failures to resolve, since the missing tool's name is given explicitly; the fix is almost always either installing the missing package or reconsidering whether that base image is the right choice for what the build actually needs to do.
Minimal base images and missing common utilities
Minimal images, particularly Alpine-based ones built around musl and BusyBox rather than the GNU toolchain common on most full Linux distributions, frequently lack utilities that might be assumed universally present, bash, curl, git, or full GNU coreutils with their typical flag support:
FROM alpine:3
RUN curl -sf https://example.com/install.sh | sh
/bin/sh: curl: not found
RUN apk add --no-cache curl
Installing the specific missing package resolves this directly, but it is also worth recognizing that some scripts and commands assume GNU-specific flag behavior that BusyBox's lighter implementations of common utilities do not fully support, which can produce a more subtle "command found but behaves differently" failure rather than an outright missing-tool error.
Build-stage tools versus runtime tools
A common and entirely intentional pattern in multi-stage builds is having compilers, build tools, and SDKs present only in an early build stage, deliberately absent from the final runtime stage to keep the production image smaller and with a reduced attack surface:
FROM node:20 AS build
RUN npm run build
FROM node:20-alpine
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
A "command not found" error encountered when trying to run a build tool against the final, minimal runtime image is often not actually a bug, it is the intended, deliberate absence of build tooling from a stage that was never meant to include it; the fix in this case is running the build tool against the correct, earlier stage rather than adding the tool to the final image.
Tools assumed present from the host environment
A script or Makefile written and tested primarily on a developer's own machine can implicitly assume tools that happen to be installed there, but that are not actually part of any base image being used inside the container, which only becomes apparent once the same script runs inside a genuinely minimal containerized environment for the first time:
build:
jq -r '.version' package.json
/bin/sh: jq: not found
Auditing a script or Makefile for every external tool it invokes, then explicitly confirming each one is actually installed in the image being used to run it, catches this category of gap before it becomes a build failure, particularly when adapting a script originally written for an unconstrained developer machine into something meant to run inside a deliberately minimal container.
Shell differences affecting tool invocation
RUN instructions execute through /bin/sh by default, and on many minimal images, /bin/sh is a much more limited shell (such as BusyBox's ash) than bash, which means a script relying on bash-specific syntax can fail with a confusing parse error rather than a clear "tool not found" message, even though the underlying issue is also fundamentally about an assumed tool, bash itself, not actually being present or being aliased to a more limited shell:
RUN [[ -f /app/config.json ]] && echo "exists"
/bin/sh: [[: not found
Explicitly invoking bash for instructions that genuinely need bash-specific syntax, rather than relying on the default shell, resolves this:
RUN bash -c '[[ -f /app/config.json ]] && echo "exists"'
This requires bash itself to actually be installed in the image first, which circles back to the same underlying missing-tool category of problem if it is not.
Verifying tool availability before relying on it
Rather than discovering a missing tool only when a build instruction that depends on it fails, an explicit, early check can fail fast with a clearer, more direct error specifically calling out the missing dependency:
RUN command -v make >/dev/null 2>&1 || (echo "ERROR: make is required but not installed" && exit 1)
This is most useful in a Dockerfile maintained by a team where the specific tooling requirements might not be obvious to everyone making future changes, providing a clearer signal than an indirect failure several instructions later that happens to depend on the same missing tool.
Choosing a base image with the right tool set built in
For build stages with substantial tooling needs, choosing a base image that already includes the relevant toolchain, rather than starting from the most minimal possible image and incrementally installing every individual tool needed, is often simpler to maintain and less prone to missing a tool that turns out to be needed later:
FROM golang:1.22 AS build
FROM alpine:3 AS build
RUN apk add --no-cache go
The first approach starts from an image already configured with the expected toolchain and its common dependencies; the second requires explicitly anticipating and installing every piece needed individually, which is more error-prone as a build's tooling requirements grow or change over time.
Common mistakes
- Assuming a minimal base image includes common utilities like
curl,git, orbashwithout verifying their actual presence. - Treating a missing build tool in a final, minimal runtime stage as a bug, rather than recognizing it may be the deliberate, intended result of a multi-stage build excluding build-only tooling from that stage.
- Carrying over scripts or Makefiles written for an unconstrained developer machine without auditing every external tool they invoke against what is actually present in the containerized build environment.
- Writing bash-specific syntax in a
RUNinstruction without accounting for the more limited default shell many minimal images use. - Starting from the most minimal possible base image for a build stage with substantial tooling needs, rather than choosing one that already includes the expected toolchain.
Build tool missing errors are generally the most straightforward category of build failure to diagnose, since the error message names the missing command directly, and resolving them is a matter of either installing the specific missing package, invoking the tool against the correct build stage, or reconsidering whether a more appropriately equipped base image would reduce the need to manage individual tool installation explicitly.