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.
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:
| ID | Severity | Check | hadolint |
|---|---|---|---|
DF001 | Warning | FROM uses the :latest tag | DL3007 |
DF002 | Warning | FROM image has no tag or digest | DL3006 |
DF003 | Warning | Final stage never sets USER, so the container runs as root | – |
DF004 | Warning | Last USER of the final stage is root | DL3002 |
DF010 | Warning | apt-get install without -y (the build aborts at the prompt) | DL3014 |
DF011 | Info | apt-get install without --no-install-recommends | DL3015 |
DF012 | Info | apt lists not removed with rm -rf /var/lib/apt/lists/* in the same RUN | DL3009 |
DF013 | Warning | apt-get update in a RUN without an install (stale cached package index) | – |
DF014 | Warning | apt used instead of apt-get in a script | DL3027 |
DF015 | Info | apk add without --no-cache | DL3019 |
DF016 | Info | pip install without --no-cache-dir | DL3042 |
DF017 | Info | npm install instead of npm ci for lockfile installs | – |
DF020 | Info | cd inside RUN instead of WORKDIR | DL3003 |
DF021 | Warning | Relative WORKDIR path | DL3000 |
DF022 | Warning | More than one CMD or ENTRYPOINT in a stage (only the last applies) | DL4003 / DL4004 |
DF023 | Warning | Shell-form CMD / ENTRYPOINT (signal handling) | DL3025 |
DF024 | Error | Exec form that is not valid JSON, e.g. single quotes | – |
DF030 | Warning | Secret-looking name in ENV or ARG (PASSWORD, SECRET, TOKEN, API_KEY …) | – |
DF031 | Info | COPY . . before installing dependencies (breaks layer caching) | – |
DF032 | Error | Invalid EXPOSE port or protocol | DL3011 |
DF033 | Warning | sudo in RUN | DL3004 |
DF034 | Info | No HEALTHCHECK in the final stage | – |
DF035 | Warning | Deprecated MAINTAINER (use LABEL) | DL4000 |
DF036 | Warning | ADD for local files where COPY suffices | DL3020 |
DF040–DF044 | Error / Warning | Structure: unknown instruction, missing or misplaced FROM, duplicate stage names, COPY --from an undefined stage | DL3024 (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.Matchrules plus**; a leading!re-includes a path. - For build-time secrets use
RUN --mount=type=secret,id=npmrc …withdocker build --secret id=npmrc,src=.npmrcinstead ofARGorENV.
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.
Related Tools
All DevOps Tools →Docker Compose Validator
Validate docker-compose.yml files: YAML syntax, service keys...
Kubernetes YAML Linter
Lint Kubernetes manifests for errors and best practices: mis...
Helm Values Tool
Compare and merge Helm values.yaml files the way Helm does,...
Page Numbers on PDF
Add custom-positioned page numbers across your PDF pages.
Edit PDF Pages
Reorder pages and remove selected pages from a PDF file.
Compare PDF Differences
Compare two PDFs and inspect text-level differences quickly.