Free DevOps Tool

Dockerfile Linter

A Docker linter that reads your Dockerfile without building it. Paste it in and get best-practice feedback, security warnings and the changes that actually shrink the image. Free, no login needed.

Best PracticesSecurity ChecksFree & OnlineNo Sign-up

Here are some examples for you.

RuleWhat it checksSeverity
no-latest-tagFROM uses :latest tagerror
no-userNo USER before CMD/ENTRYPOINTerror
no-secrets-in-envSecrets hardcoded in ENVerror
apt-get-no-yapt-get install missing -y flagerror
apt-get-update-aloneapt-get update in its own RUN layerwarning
chain-runMultiple RUN apt-get install layerswarning
copy-before-depsCOPY . . before dependency installwarning
prefer-copyADD used instead of COPYwarning
large-base-imageLarge base image (ubuntu/debian/centos…)info
use-npm-cinpm install instead of npm ciinfo
no-healthcheckNo HEALTHCHECK instructioninfo

How to use the Dockerfile linter

Four steps. The Dockerfile is analysed in your browser, so nothing you paste is uploaded.

  1. Paste the Dockerfile

    The whole file, including comments and any multi-stage FROM lines. It is analysed in your browser, so a Dockerfile with internal registry paths or build arguments in it never leaves the page.

  2. Fix the errors before the warnings

    Findings come back as error, warning or info. An error is something that will break the build or ship a security problem, such as apt-get install without -y or a secret in an ENV. A warning costs you image size or build cache. Info is a suggestion worth taking when convenient.

  3. Rebuild and compare the size

    Run docker build -t myapp . then docker images myapp after each change. Chaining RUN steps and switching to an Alpine or distroless base usually shows up immediately, often several hundred megabytes on a first pass.

  4. Check what actually ended up in the image

    Run docker history myapp to see each layer and its size, and docker run --rm myapp id to confirm the container is not running as root. The linter reads the file; these two commands tell you what the file produced.

Every Dockerfile rule this checks, and why it matters

Thirteen checks. Each one is here because of what it costs when it is wrong: a build that hangs, a secret in a published layer, a cache that rebuilds from scratch on every commit, or a few hundred megabytes shipped to every host.

FROM ... :latest

The base image has no pinned version.

latest is a moving tag, so the image you built today and the one CI builds next month can differ with nothing in your repository to show it. When that build breaks there is no diff to look at. Pin a version, and pin a digest for anything you actually care about reproducing.

FROM with no tag at all

Docker fills in latest for you.

Same problem as above, with the added trap that nothing in the file hints at it. Reviewers read FROM node as deliberate rather than as an omission.

apt-get install without -y

The package manager expects an interactive confirmation.

There is no terminal attached during a build, so it hangs or aborts rather than prompting. This is an error rather than a style point because the build genuinely does not complete.

apt-get update in its own RUN

The package index is refreshed in one layer and used in a later one.

Docker caches the update layer. Weeks later the install layer runs against a stale index and pulls versions that no longer exist, or silently installs old ones. Always chain them: RUN apt-get update && apt-get install -y ....

Several separate RUN apt-get install lines

Each RUN creates a layer.

Every layer is stored and shipped, and deleting a file in a later layer does not reclaim the space the earlier one used. Chain related installs into one RUN and clean the apt lists in the same command with && rm -rf /var/lib/apt/lists/*.

COPY . . before installing dependencies

The whole source tree is copied in before the dependency install runs.

Docker invalidates the cache from the first changed layer onward, so touching any source file re-runs the entire dependency install. Copy the manifest first (COPY package*.json ./), install, then copy the rest. On a large project this is usually the single biggest build-time win available.

npm install rather than npm ci

The install is resolved from package.json rather than the lockfile.

npm install may update the lockfile and resolve different versions than a teammate got. npm ci installs exactly what the lockfile says, fails if the two disagree, and is faster. Use it anywhere the build is not meant to change dependencies.

No USER before CMD or ENTRYPOINT

The container runs as root.

Root in the container is root on the host kernel for anything that escapes isolation, and a bind-mounted volume is written with root ownership. Create a user and switch to it: RUN useradd -m appuser then USER appuser. An explicit USER root is flagged for the same reason.

ADD where COPY would do

ADD also unpacks local archives and fetches URLs.

That implicit behaviour surprises people: a .tar.gz you meant to copy gets extracted instead. Use COPY for files, and for a remote file prefer a RUN curl you can verify with a checksum.

A secret in ENV

A password, token or key is set as a build-time environment variable.

It is baked into the layer and stays readable in the image history for anyone who pulls it, even if a later line unsets it. Deleting the variable does not delete the layer. Pass secrets at runtime, or use BuildKit secret mounts for build-time ones.

pip install without --no-cache-dir

pip keeps its download cache inside the image.

Tens of megabytes of wheels you will never reinstall, shipped to every host that pulls the image. --no-cache-dir costs nothing because the build machine downloads them once anyway.

A full distribution as the base image

The base is ubuntu, debian, centos or fedora rather than a slim variant.

You ship a package manager, a shell and hundreds of libraries your application never calls, which is both size and attack surface. An Alpine or -slim variant is usually a drop-in; a distroless base is the stricter option when nothing needs a shell.

No HEALTHCHECK

Nothing tells Docker whether the process inside is actually working.

Without it, a container whose process is alive but wedged still counts as running, so restart policies never fire. This is info rather than a warning because under Kubernetes the readiness and liveness probes do this job instead, and a HEALTHCHECK is then redundant.

Dockerfile questions people actually ask

What is a Docker linter?

A Docker linter reads a Dockerfile without building it and reports instructions that will break the build, leak a secret, waste the layer cache or ship more image than you need. Because it is static analysis it is instant and costs nothing, which makes it worth running before the build rather than after a slow one fails.

What are the most important Dockerfile best practices?

Four, in the order they pay off: pin the base image to a version rather than latest; copy the dependency manifest and install before copying the source, so the cache survives a code change; add a USER so the container is not root; and keep secrets out of ENV. Everything else is size and speed, which matter but do not cause incidents.

Why is my Docker image so large?

Usually the base image, then the package manager cache, then files deleted in a later layer. A deleted file still occupies space in the layer that created it, so rm after the fact does not help. Clean up in the same RUN that created the mess, use a slim base, and use a multi-stage build so build tools never reach the final image.

What is the difference between COPY and ADD?

COPY copies files and does nothing else. ADD also extracts local tar archives automatically and can fetch a URL. That extra behaviour is implicit and occasionally surprising, so the convention is COPY unless you specifically want extraction.

Does this linter replace hadolint?

No. Hadolint is the tool to wire into CI, and it also runs ShellCheck over your RUN lines. This is for the moment you have a Dockerfile in front of you and want an answer without installing anything. Use this to learn the rules and hadolint to enforce them.

Is my Dockerfile uploaded anywhere?

No. The analysis runs in your browser. Nothing is sent to a server or stored, so a Dockerfile with internal registry hostnames or build arguments in it is safe to paste.

Go deeper on Docker images

Learn Docker: Step by Step

Ready to go deeper? Our free Docker tutorial series takes you from zero to production-ready containers.