A RUN line like curl https://… | sh downloads a script and executes it in the same step, usually as root, without checking what it received. If the server, domain or CDN is compromised, every image you build runs the attacker's code, and nothing in the image records what ran.
openrouting — dockerfile explainer
$ explain Dockerfile! RUN pipes a downloaded script into a shell highline 2fix: see below$
What the free Dockerfile explainer reports on the risky example below.
Why it matters
The script is fetched fresh on every uncached build, so two builds of the same Dockerfile can run different code. The image history shows the command, not the script it executed.
Build steps run as root unless an earlier USER says otherwise, and they often have access to build secrets and the network. That makes the build an attractive place to inject code.
Install scripts frequently fetch further binaries themselves, so one unchecked download can pull in several more.
Risky and safer Dockerfile
RiskyDockerfile
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -fsSL https://example.com/install.sh | sh
USER 10001
HEALTHCHECK CMD ["tool", "--version"]
CMD ["tool"]
SaferDockerfile
FROM debian:12-slim
ARG TOOL_VERSION=2.3.1
ARG TOOL_SHA256=<published sha256 for this version>
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -fsSLo /tmp/tool.tar.gz "https://example.com/releases/tool-${TOOL_VERSION}.tar.gz" \
&& echo "${TOOL_SHA256} /tmp/tool.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/tool.tar.gz -C /usr/local/bin \
&& rm -rf /tmp/tool.tar.gz /var/lib/apt/lists/*
USER 10001
HEALTHCHECK CMD ["tool", "--version"]
CMD ["tool"]
How to fix it
Download a specific released version, not a moving install script.
Check it against the checksum the project publishes (sha256sum -c) before using it, so the build fails if the file changes.
Prefer the distribution's package manager or an official image that already contains the tool.
If you must run a vendor script, download it, pin and verify its checksum, read it, then execute the saved file.
When it's acceptable
Local experiments that never become a shared or production image.
Even for trusted vendors, the risk is the delivery path rather than the vendor, so a pinned and verified download is still the better default.
Check your own Dockerfile
The risky example is loaded below. Paste your own file to check it for this and every other Dockerfile rule. It runs in your browser; nothing is uploaded.
Runs apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && curl -fsSL https://example.com/inst… while building the image and saves the result as a new layer.
This runs whatever the server returns at build time, as root, with no record of what it was. If the server, domain or CDN is compromised, so is every image you build.
Safer: Download a pinned version, verify its published checksum (sha256sum -c), then install it.
Sets the default command to tool. It runs when the container starts, and anything passed to docker run replaces it.
Questions
Why is curl | bash in a Dockerfile dangerous?
It executes whatever the server returns at build time, usually as root, with no check that it is the script you expect. A compromised server, domain or CDN would put its code into every image you build.
How do I install a tool in a Dockerfile without curl | sh?
Download a pinned release, verify it against the published sha256 checksum with sha256sum -c, then extract or install it in the same RUN. Using the distribution's package manager or an official image is simpler still.
Does the explainer catch wget and bash -c "$(curl …)" too?
Yes. The rule flags curl or wget piped into sh, bash, zsh, python, perl, ruby or node, and the command-substitution forms such as bash -c "$(curl …)".