Dockerfile & docker-compose Validator

Paste a Dockerfile or a docker-compose.yml to catch syntax errors, broken references and common best-practice mistakes before you build. Every issue has a line number and an explanation. Click an issue to jump to its line.

Dockerfile

Issues

    Annotated file

    ●Error●Warning●Info

    What this validator checks

    The Dockerfile tab uses a hand-written parser that follows the Dockerfile reference: parser directives (# syntax=, # escape=), line continuations, comments inside continued lines, heredocs (RUN <<EOF) and the difference between the JSON exec form and the shell form. On top of the parser runs a linter in the spirit of hadolint and BuildKit's built-in build checks. Where a rule matches a hadolint rule, its code (such as DL3007) is shown so you can look it up or silence it in a real CI pipeline.

    The docker-compose tab parses YAML with js-yaml, so syntax errors show the exact line and column. It then checks the structure against a curated list of keys from the Compose Specification: top-level sections, service keys, port and volume syntax, references between services, networks, volumes and secrets, and a few security warnings. Misspelled keys get a "did you mean" suggestion.

    Severity levels

    Dockerfile rules explained

    Untagged or :latest base image (DL3006, DL3007)latest moves. The same Dockerfile can build a different image next week, and a major version bump can break your build without any change on your side. Pin a version such as python:3.12-slim, or a digest (@sha256:…) for fully reproducible builds.
    apt-get install without -y (DL3014)Builds are non-interactive. Without -y apt waits for a confirmation that never comes and the build aborts.
    No --no-install-recommends (DL3015)Debian and Ubuntu install "recommended" packages by default, which often adds tens of megabytes you never use.
    apt lists not removed (DL3009)apt-get update downloads package indexes into /var/lib/apt/lists. Unless you delete them in the same RUN, they stay in the layer forever.
    apt-get update in its own RUNDocker caches each layer. A lone RUN apt-get update is cached, so a later apt-get install can use an index that is months old and fail or install outdated packages. Always chain update && install.
    ADD for local files (DL3020)ADD also downloads URLs and auto-extracts tar archives, which is surprising when you only meant to copy. COPY does just one thing.
    cd in RUN (DL3003)A cd only lasts for that one RUN. WORKDIR sets the directory for every following instruction and documents it.
    Shell-form CMD/ENTRYPOINT (DL3025)The shell form runs your app as a child of /bin/sh -c. The shell is PID 1 and does not forward SIGTERM, so docker stop waits 10 seconds and then kills the process. The JSON form runs your program directly.
    Multiple CMD/ENTRYPOINT (DL4003, DL4004)Only the last one in a stage takes effect. The earlier ones are dead code that misleads readers.
    No USER (DL3002)Containers run as root unless told otherwise. If an attacker breaks out of your app, root inside the container makes every other weakness worse. Create an unprivileged user and switch to it in the final stage.
    Secrets in ENV/ARGValues of ENV and ARG are stored in the image metadata and visible to anyone with docker history or docker inspect. Use RUN --mount=type=secret at build time and inject runtime secrets from your orchestrator.
    sudo (DL3004)Build steps already run as root (or the current USER). sudo adds an extra package, a setuid binary and unpredictable TTY behaviour. Switch USER instead.
    COPY . . before installing dependenciesAny change to any source file invalidates the cache for every following layer, so dependencies are reinstalled on every build. Copy only the manifest (package.json, requirements.txt, go.mod…) first, install, then copy the rest.
    MAINTAINER (DL4000)Deprecated since Docker 1.13. Use LABEL org.opencontainers.image.authors="…".
    Relative WORKDIR (DL3000)A relative path is resolved against the previous WORKDIR, which depends on the base image. Absolute paths are unambiguous.

    Compose rules explained

    Best practices for small, secure images

    Multi-stage builds

    A multi-stage Dockerfile has several FROM lines. Early stages contain compilers, dev dependencies and build tools. The final stage starts from a small runtime image and copies only the finished artefacts with COPY --from=build. The result has no compiler, no source code and no build secrets, and is often ten times smaller. The "Good example" button loads a three-stage Node.js build that shows the pattern. Name your stages with AS so COPY --from stays readable, and build a specific stage with docker build --target.

    Limitations

    This is a static linter. It reads the text you paste and never runs Docker. It does not pull images, check that files in COPY exist, run your shell commands, expand ${VARIABLES} or resolve extends, include and YAML merge keys across files. A clean result here is a good sign, not a guarantee. Run docker build --check . and docker compose config for the authoritative answer, and add hadolint to CI for a full rule set.

    Is my file uploaded?

    No. Parsing and linting happen in your browser with JavaScript. Your Dockerfile and Compose file, including any secrets in them, never leave your machine.