Tools: I Linted 100 Dockerfiles from GitHub — The Same 5 Mistakes Everywhere (2026)

Tools: I Linted 100 Dockerfiles from GitHub — The Same 5 Mistakes Everywhere (2026)

The Methodology

Mistake #1: Using :latest Tag (73% of Dockerfiles)

Mistake #2: Running as Root (68%)

Mistake #3: No Layer Caching Strategy (61%)

Mistake #4: apt-get Without Cleanup (54%)

Mistake #5: No HEALTHCHECK (82%)

The Full Results

Automate It I wrote a Dockerfile linter and ran it against 100 popular open-source Dockerfiles from GitHub. The results? The same 5 mistakes appeared in over 60% of them. I grabbed Dockerfiles from repos with 1,000+ stars across different languages (Python, Node.js, Go, Java). Ran my linter and categorized every issue. Why it matters: :latest is a moving target. Your build works today, breaks tomorrow when the base image updates. I've seen production outages from this exact issue. The fix: Always pin to a specific version. Use slim/alpine variants to reduce image size by 80%. Why it matters: If an attacker escapes the container, they have root access to the host. Container escapes happen more than you think. Why it matters: Docker caches layers. If you copy all your code before installing dependencies, changing one line of code invalidates the dependency cache. Builds go from 10 seconds to 5 minutes. Why it matters: The apt cache can add 100-300MB to your image. Since Docker layers are additive, cleaning up in a separate RUN does nothing — the data is already in a previous layer. Why it matters: Without a healthcheck, Docker thinks your container is healthy as long as the process is running. But your app could be deadlocked, out of memory, or returning 500s — and Docker won't know. I open-sourced the linter. Run it on your Dockerfiles: What Dockerfile mistakes have bitten you in production? I'd love to hear your war stories. Follow for more DevOps and security content. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# Bad FROM python:latest # Good FROM python:3.11-slim # Bad FROM python:latest # Good FROM python:3.11-slim # Bad FROM python:latest # Good FROM python:3.11-slim # Bad — runs everything as root FROM node:20 COPY . /app CMD ["node", "server.js"] # Good — creates and uses non-root user FROM node:20-slim RUN groupadd -r app && useradd -r -g app app COPY --chown=app:app . /app USER app CMD ["node", "server.js"] # Bad — runs everything as root FROM node:20 COPY . /app CMD ["node", "server.js"] # Good — creates and uses non-root user FROM node:20-slim RUN groupadd -r app && useradd -r -g app app COPY --chown=app:app . /app USER app CMD ["node", "server.js"] # Bad — runs everything as root FROM node:20 COPY . /app CMD ["node", "server.js"] # Good — creates and uses non-root user FROM node:20-slim RUN groupadd -r app && useradd -r -g app app COPY --chown=app:app . /app USER app CMD ["node", "server.js"] # Bad — reinstalls dependencies every time code changes FROM python:3.11-slim COPY . /app RUN -weight: 500;">pip -weight: 500;">install -r /app/requirements.txt # Good — dependencies cached unless requirements.txt changes FROM python:3.11-slim COPY requirements.txt /app/ RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r /app/requirements.txt COPY . /app # Bad — reinstalls dependencies every time code changes FROM python:3.11-slim COPY . /app RUN -weight: 500;">pip -weight: 500;">install -r /app/requirements.txt # Good — dependencies cached unless requirements.txt changes FROM python:3.11-slim COPY requirements.txt /app/ RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r /app/requirements.txt COPY . /app # Bad — reinstalls dependencies every time code changes FROM python:3.11-slim COPY . /app RUN -weight: 500;">pip -weight: 500;">install -r /app/requirements.txt # Good — dependencies cached unless requirements.txt changes FROM python:3.11-slim COPY requirements.txt /app/ RUN -weight: 500;">pip -weight: 500;">install --no-cache-dir -r /app/requirements.txt COPY . /app # Bad — leaves cache in the image RUN -weight: 500;">apt-get -weight: 500;">update && -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">curl -weight: 500;">wget # Good — clean up in the same layer RUN -weight: 500;">apt-get -weight: 500;">update && \ -weight: 500;">apt-get -weight: 500;">install -y --no--weight: 500;">install-recommends -weight: 500;">curl -weight: 500;">wget && \ rm -rf /var/lib/-weight: 500;">apt/lists/* # Bad — leaves cache in the image RUN -weight: 500;">apt-get -weight: 500;">update && -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">curl -weight: 500;">wget # Good — clean up in the same layer RUN -weight: 500;">apt-get -weight: 500;">update && \ -weight: 500;">apt-get -weight: 500;">install -y --no--weight: 500;">install-recommends -weight: 500;">curl -weight: 500;">wget && \ rm -rf /var/lib/-weight: 500;">apt/lists/* # Bad — leaves cache in the image RUN -weight: 500;">apt-get -weight: 500;">update && -weight: 500;">apt-get -weight: 500;">install -y -weight: 500;">curl -weight: 500;">wget # Good — clean up in the same layer RUN -weight: 500;">apt-get -weight: 500;">update && \ -weight: 500;">apt-get -weight: 500;">install -y --no--weight: 500;">install-recommends -weight: 500;">curl -weight: 500;">wget && \ rm -rf /var/lib/-weight: 500;">apt/lists/* # Bad — Docker has no idea if your app is actually working CMD ["python", "server.py"] # Good — Docker can detect and -weight: 500;">restart unhealthy containers HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:8080/health || exit 1 CMD ["python", "server.py"] # Bad — Docker has no idea if your app is actually working CMD ["python", "server.py"] # Good — Docker can detect and -weight: 500;">restart unhealthy containers HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:8080/health || exit 1 CMD ["python", "server.py"] # Bad — Docker has no idea if your app is actually working CMD ["python", "server.py"] # Good — Docker can detect and -weight: 500;">restart unhealthy containers HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD -weight: 500;">curl -f http://localhost:8080/health || exit 1 CMD ["python", "server.py"] -weight: 500;">git clone https://github.com/spinov001-art/dockerfile-linter python linter.py Dockerfile -weight: 500;">git clone https://github.com/spinov001-art/dockerfile-linter python linter.py Dockerfile -weight: 500;">git clone https://github.com/spinov001-art/dockerfile-linter python linter.py Dockerfile 🔴 HIGH: Line 1 — Using :latest tag Fix: Pin to specific version (e.g., python:3.11-slim) 🔴 HIGH: Running as root (no USER instruction) Fix: Add USER nonroot before CMD 🟡 MEDIUM: Line 8 — -weight: 500;">apt-get without cleanup Fix: Add && rm -rf /var/lib/-weight: 500;">apt/lists/* Score: 62/100 🔴 HIGH: Line 1 — Using :latest tag Fix: Pin to specific version (e.g., python:3.11-slim) 🔴 HIGH: Running as root (no USER instruction) Fix: Add USER nonroot before CMD 🟡 MEDIUM: Line 8 — -weight: 500;">apt-get without cleanup Fix: Add && rm -rf /var/lib/-weight: 500;">apt/lists/* Score: 62/100 🔴 HIGH: Line 1 — Using :latest tag Fix: Pin to specific version (e.g., python:3.11-slim) 🔴 HIGH: Running as root (no USER instruction) Fix: Add USER nonroot before CMD 🟡 MEDIUM: Line 8 — -weight: 500;">apt-get without cleanup Fix: Add && rm -rf /var/lib/-weight: 500;">apt/lists/* Score: 62/100 - name: Lint Dockerfile run: python linter.py Dockerfile --fail-on high - name: Lint Dockerfile run: python linter.py Dockerfile --fail-on high - name: Lint Dockerfile run: python linter.py Dockerfile --fail-on high