← Blog
// DEVSECOPS · CONTENEDORES

Harden your Dockerfile: the anti-patterns that give an insecure container away

Published Aug 18, 2026 · 8 min read
DevSecOpsDockerKubernetes

A container that starts is not a secure container. Most Dockerfiles pass CI, serve the app… and at the same time run as root, drag secrets in their layers and trust images that shift under your feet. These are the anti-patterns a linter checks, why they matter and how to fix them.

1. Running as root (the most common)

By default a container runs as root. If an attacker gets execution inside the container and then finds an escape (or a mounted volume), they inherit root on the host. The defense is one line:

RUN adduser --system --no-create-home app
USER app

Declare USER near the end, after installing dependencies (which do need privileges).

2. Unpinned base image (:latest or no tag)

FROM node or FROM node:latest mean "whatever exists today". Tomorrow the build differs and you don't know what changed: reproducibility is broken and the surface moves on its own. Pin a concrete tag —or better, an immutable digest:

FROM node:20.11-alpine
# or, bulletproof:
FROM node:20.11-alpine@sha256:e4b2f...

3. Secrets in ENV / ARG

A token in ENV DB_PASSWORD=... doesn't "vanish" at runtime: it's baked into the image layers and its history. Anyone with the image extracts it with docker history. Don't put secrets in the Dockerfile: use build secrets (--secret) or inject them at runtime.

4. curl | sh and remote ADD

RUN curl https://get.example.com | sh runs remote code unverified: if that server is compromised, so is your build. Same with ADD https://…, which downloads and trusts. Download, verify the checksum, then run; and use COPY (not ADD) for local files.

5. Fat image = more surface

apt-get install without --no-install-recommends drags extra packages (more CVEs to patch). Install only what you need and clean up in the same layer:

RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*

6. No HEALTHCHECK

Without a healthcheck, the orchestrator can't tell whether your app is alive or hung. A container that's "up" but broken keeps receiving traffic. Add one.

🐳 Want to see it on your own file? Paste your Dockerfile, docker-compose or Kubernetes manifest into the Dockerfile / IaC linter and it gives you an A–F grade with these anti-patterns flagged. All in your browser; the config is never sent anywhere.

And at deploy time: compose and Kubernetes

Hardening doesn't end at the Dockerfile. In docker-compose and K8s, the usual suspects are:

A decent reference Dockerfile

FROM node:20.11-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY . .
USER node
HEALTHCHECK CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "server.js"]

Pinned base, no secrets, non-root user, healthcheck and production dependencies only. Nothing exotic: it's five habits.

⚠️ The linter doesn't replace an image scanner (Trivy, Grype) or a signing/SBOM pipeline. It's the first layer: it catches what's visible in the file before you build.

Checklist

A good Dockerfile isn't longer: it's more boring. And boring, in security, is exactly what you want.

Share: LinkedIn X
Sergio Belmonte Morales
Sergio Belmonte Morales
Cybersecurity Analyst · SOC · Sentinel/KQL specialist