Dockerfile doctor
Paste a Dockerfile. The doctor checks for floating base images, running as root, secrets baked into layers, package cache bloat, ADD misuse, curl piped to a shell and a shell-form entrypoint that swallows SIGTERM.
Everything runs in your browser. Nothing you paste is uploaded. Also try the paste-anything box or the AI-output verifier.
What the Dockerfile doctor checks
A Dockerfile is easy to get working and hard to get right. The container that runs fine on a laptop is often a root process, hundreds of megabytes larger than needed, with a password visible in docker history and a JVM that ignores shutdown signals. These rules capture the lessons from container hardening guides and the practices behind small, reproducible images.
There are 18 rules in this doctor: 4 high severity, 4 medium and 10 low. Each rule has a stable anchor, so you can link a teammate straight to the explanation, for example /doctor/dockerfile/#rule-from-latest.
All 18 rules
MediumBase image is :latest or untagged#
Why it matters. The image changes underneath you, so a rebuild next week can break or add vulnerabilities with no code change.
Fix. Pin an exact tag, for example eclipse-temurin:21.0.4_7-jre, or a digest. Spring Boot Dockerfile generator
FROM node
HighNo USER instruction, the container runs as root#
Why it matters. A vulnerability in the app gives an attacker root inside the container and an easier path to the host.
Fix. Create a user (RUN useradd -r app) and add USER app before the entrypoint. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre COPY app.jar /app.jar CMD ["java","-jar","/app.jar"]
HighFinal USER is root#
Why it matters. The container explicitly runs as root.
Fix. End the Dockerfile with a non-root USER. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre USER root
Mediumapt-get update in its own layer#
Why it matters. Docker caches the update layer, so a later install uses a stale package index and fails with 404 or installs old versions.
Fix. Combine in one RUN: apt-get update && apt-get install -y --no-install-recommends pkg && rm -rf /var/lib/apt/lists/*. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre RUN apt-get update RUN apt-get install -y curl
Lowapt lists are not removed#
Why it matters. The package index stays in the layer and adds tens of MB to every image pull.
Fix. Finish the RUN with && rm -rf /var/lib/apt/lists/* and use --no-install-recommends. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre RUN apt-get update && apt-get install -y curl
Lowapk add without --no-cache#
Why it matters. The Alpine index cache is kept in the layer.
Fix. Use apk add --no-cache pkg. Spring Boot Dockerfile generator
FROM alpine:3.20 RUN apk add curl
LowADD used where COPY is enough#
Why it matters. ADD also unpacks archives and downloads URLs, which surprises people and is harder to cache and verify.
Fix. Use COPY for local files; use curl with a checksum for downloads. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre ADD app.jar /app.jar
HighDownloads and executes a script in the build#
Why it matters. The build runs whatever the server returns that day, unverified.
Fix. Download to a file, check its sha256, then run it.
FROM eclipse-temurin:21-jre RUN curl -sSL https://x.sh | sh
HighSecret baked into the image#
Why it matters. ENV and ARG values are stored in image layers and visible in docker history to anyone who can pull it.
Fix. Use BuildKit secrets (RUN --mount=type=secret) at build time and runtime environment variables or a secret store at run time.
FROM eclipse-temurin:21-jre ENV DB_PASSWORD=hunter2
Lowsudo inside a Dockerfile#
Why it matters. Build steps already run as root until a USER is set, so sudo is unnecessary and installs a setuid binary.
Fix. Remove sudo.
FROM eclipse-temurin:21-jre RUN sudo apt-get update
Lowcd inside RUN#
Why it matters. The directory change does not carry to the next instruction, which is a frequent source of "file not found".
Fix. Use WORKDIR.
FROM eclipse-temurin:21-jre RUN cd /app && ls
LowNo HEALTHCHECK#
Why it matters. Docker and Compose cannot tell a hung process from a healthy one (Kubernetes uses its own probes).
Fix. Add HEALTHCHECK CMD curl -f http://localhost:8080/actuator/health || exit 1, or rely on probes if you only deploy to Kubernetes. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre USER 1000 CMD ["java","-jar","/app.jar"]
MediumExposes SSH#
Why it matters. Running sshd in a container is an anti-pattern: it needs keys, patching and a process manager.
Fix. Use docker exec or kubectl exec for access.
FROM eclipse-temurin:21-jre EXPOSE 22
Lowpip install without --no-cache-dir#
Why it matters. pip keeps its download cache in the layer.
Fix. Use pip install --no-cache-dir.
FROM python:3.12-slim RUN pip install flask
Mediumchmod 777#
Why it matters. World-writable files let any process in the container modify the app.
Fix. Use the narrowest mode (755 for directories and binaries, 644 for files) and chown to the app user.
FROM eclipse-temurin:21-jre RUN chmod -R 777 /app
LowCOPY . before dependency install#
Why it matters. Any source change invalidates the dependency layer, so every build downloads all dependencies again.
Fix. Copy pom.xml or package.json first, install dependencies, then copy the sources. Spring Boot Dockerfile generator
FROM maven:3.9-eclipse-temurin-21 COPY . . RUN mvn package
LowShell-form CMD or ENTRYPOINT#
Why it matters. The JVM becomes a child of /bin/sh, so SIGTERM never reaches it and graceful shutdown waits for the kill timeout.
Fix. Use the exec form: ENTRYPOINT ["java","-jar","app.jar"]. Spring Boot Dockerfile generator
FROM eclipse-temurin:21-jre CMD java -jar /app.jar
LowFull JDK image used to run the app#
Why it matters. JDK images are several hundred MB larger and contain compilers an attacker can use.
Fix. Build in a JDK stage and run on a -jre image (multi-stage). Spring Boot Dockerfile generator
FROM eclipse-temurin:21 COPY app.jar /app.jar CMD ["java","-jar","/app.jar"]
Frequently asked questions
Does it understand multi-stage builds?
Yes. It parses every FROM stage, and rules such as "runs as root" and "JDK image used to run the app" look at the final stage.
Why is shell-form CMD a problem for Java?
The shell becomes PID 1 and the JVM its child, so SIGTERM from Kubernetes or Docker never reaches your application and graceful shutdown waits for the kill timeout. The exec form passes signals straight to java.
Is ADD ever the right choice?
Yes, for extracting a local tar archive into the image. For plain files, COPY is clearer and cacheable.
Can it scan the image for vulnerabilities?
No. It reads only the Dockerfile text. Use an image scanner such as Trivy or Grype on the built image.