DevOps Tools

Dockerfile Linter

Paste a Dockerfile to check it against Docker best practices: unpinned base images, containers running as root, package manager caches left in layers, shell-form CMD, ADD vs COPY, leaked secrets and cache-busting COPY order. Click a finding to jump to the line.

Private by design: anything you enter is processed in your browser only. It is never uploaded to or logged on our servers. Your Dockerfile is parsed and linted with JavaScript on this page; nothing is built or sent anywhere. Share links carry the Dockerfile inside the link itself.

Lints as you type. Handles line continuations, comments, parser directives, heredocs and multi-stage builds.

–
Best-practice score
Paste a Dockerfile to start.

    About this Dockerfile linter

    A Dockerfile that builds is not necessarily a good one. Small choices decide whether an image is reproducible, how large it is, how quickly it rebuilds and how much damage a compromised container can do. This linter reads your Dockerfile the way the Docker builder does (joining \ continuation lines, skipping comments, honouring the # escape= parser directive and tracking each FROM … AS name stage) and then checks every instruction against widely accepted best practices.

    Each finding has a severity, a rule ID, the line number and a suggested fix. Errors are things that are broken or will fail, such as an invalid EXPOSE port or a CMD that looks like exec form but is not valid JSON. Warnings affect security or reproducibility, for example a :latest base image, running as root or a secret in ENV. Info items are optimisations such as smaller layers and better caching. Where a rule matches a check in the popular hadolint linter, the reference table below lists the hadolint ID too.

    The checks are static, so the linter cannot see what a base image already contains. If a base image already sets a non-root USER or defines a HEALTHCHECK, you can ignore those findings. Shell analysis of RUN commands looks for common package managers (apt-get, apk, pip, npm) and does not execute anything.

    Building a full stack? Check your docker-compose.yml with the Docker Compose validator, lint deployment manifests with the Kubernetes YAML linter, and work out file modes for COPY --chmod with the chmod calculator.

    Reference Guide

    Rules checked by this linter:

    IDSeverityCheckhadolint
    DF001WarningFROM uses the :latest tagDL3007
    DF002WarningFROM image has no tag or digestDL3006
    DF003WarningFinal stage never sets USER, so the container runs as root–
    DF004WarningLast USER of the final stage is rootDL3002
    DF010Warningapt-get install without -y (the build aborts at the prompt)DL3014
    DF011Infoapt-get install without --no-install-recommendsDL3015
    DF012Infoapt lists not removed with rm -rf /var/lib/apt/lists/* in the same RUNDL3009
    DF013Warningapt-get update in a RUN without an install (stale cached package index)–
    DF014Warningapt used instead of apt-get in a scriptDL3027
    DF015Infoapk add without --no-cacheDL3019
    DF016Infopip install without --no-cache-dirDL3042
    DF017Infonpm install instead of npm ci for lockfile installs–
    DF020Infocd inside RUN instead of WORKDIRDL3003
    DF021WarningRelative WORKDIR pathDL3000
    DF022WarningMore than one CMD or ENTRYPOINT in a stage (only the last applies)DL4003 / DL4004
    DF023WarningShell-form CMD / ENTRYPOINT (signal handling)DL3025
    DF024ErrorExec form that is not valid JSON, e.g. single quotes–
    DF030WarningSecret-looking name in ENV or ARG (PASSWORD, SECRET, TOKEN, API_KEY …)–
    DF031InfoCOPY . . before installing dependencies (breaks layer caching)–
    DF032ErrorInvalid EXPOSE port or protocolDL3011
    DF033Warningsudo in RUNDL3004
    DF034InfoNo HEALTHCHECK in the final stage–
    DF035WarningDeprecated MAINTAINER (use LABEL)DL4000
    DF036WarningADD for local files where COPY sufficesDL3020
    DF040–DF044Error / WarningStructure: unknown instruction, missing or misplaced FROM, duplicate stage names, COPY --from an undefined stageDL3024 (duplicate alias)

    ADD vs COPY: COPY just copies files from the build context. ADD also downloads remote URLs, clones Git repositories and auto-extracts local tar archives (.tar, .tar.gz, .tar.xz …). Those are legitimate uses of ADD and are not flagged. For plain files, COPY is clearer and avoids surprise extraction.

    Multi-stage build pattern (small, non-root runtime image):

    # syntax=docker/dockerfile:1
    FROM node:20-bookworm-slim AS build
    WORKDIR /app
    COPY package.json package-lock.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    FROM node:20-bookworm-slim
    ENV NODE_ENV=production
    WORKDIR /app
    COPY --from=build /app/package.json /app/package-lock.json ./
    RUN npm ci --omit=dev && npm cache clean --force
    COPY --from=build /app/dist ./dist
    USER node
    EXPOSE 3000
    HEALTHCHECK CMD node -e "fetch('http://localhost:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
    CMD ["node", "dist/server.js"]

    .dockerignore tips: everything in the build context is sent to the builder and can end up in the image with COPY . .. A .dockerignore next to the Dockerfile keeps builds fast and secrets out:

    • Exclude VCS and dependency folders: .git, node_modules, vendor, __pycache__, .venv.
    • Exclude secrets and local config: .env, *.pem, *.key, .aws.
    • Exclude build output and logs: dist, build, coverage, *.log.
    • Patterns use Go filepath.Match rules plus **; a leading ! re-includes a path.
    • For build-time secrets use RUN --mount=type=secret,id=npmrc … with docker build --secret id=npmrc,src=.npmrc instead of ARG or ENV.

    Example input

    # syntax=docker/dockerfile:1
    
    # ---- build stage ----
    FROM node:latest AS build
    MAINTAINER jane@example.com
    WORKDIR app
    COPY . .
    RUN npm install && npm run build
    
    # ---- runtime stage ----
    FROM node:20-bookworm-slim
    ENV NODE_ENV=production \
        API_TOKEN=changeme
    RUN apt-get update
    RUN apt-get install -y curl
    WORKDIR /srv/app
    COPY --from=build /app/dist ./dist
    COPY --from=build /app/package*.json ./
    RUN npm ci --omit=dev
    ADD config.json ./config.json
    EXPOSE 3000
    CMD node dist/server.js

    This sample loads automatically when you open the tool. Use Load Sample to bring it back.

    Frequently Asked Questions

    It checks for common security and efficiency problems such as unpinned base images, containers running as root, package manager caches left in the image, ADD used instead of COPY, and instructions ordered in a way that breaks layer caching.

    latest changes whenever the image is updated, so the same Dockerfile can build different images on different days. Pin a version tag, or a digest for fully reproducible builds.

    No. Linting runs in your browser and the file never leaves your device.