← Alle Neuigkeiten
docker5 Min. Lesezeit

Five Dockerfile patterns that will bite you

Von Seaworthy · 12. Juni 2026

Assorted-color intermodal shipping containers stacked at a cargo port

Photo by Frank McKenna on Unsplash

Most Dockerfiles I've seen in AI-generated projects share the same five mistakes. Not obscure edge cases: basic stuff that gets flagged in security audits, causes production incidents, or just silently makes your containers worse than they need to be. Each one takes less than two minutes to fix once you know it's there.

Running as root

The AI writes RUN apt-get install -y ... and never adds a non-root user. By default, your process runs as UID 0 inside the container. If anything in your app can execute arbitrary code (think deserialization bugs, template injection, shell commands built from user input), the attacker now has root inside a container that probably has a mounted volume or access to the Docker socket.

Add this near the bottom of your Dockerfile, before CMD:

RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser

That's it. Two lines. Your app almost certainly doesn't need root to run. If it does, that's a separate problem worth investigating.

COPY . . with no .dockerignore

The AI copies your whole project directory into the image. That means .env files, .git history, local secrets, editor config, test fixtures, node_modules if it exists locally. All of it, baked into the image, potentially pushed to a registry.

Your .dockerignore should exist and should be aggressive:

.git
.env
.env.*
node_modules
*.log
.DS_Store
coverage
.nyc_output
dist

Check what actually ends up in your image with docker run --rm your-image find / -name ".env" 2>/dev/null. You might be surprised.

The .git directory is the one that catches people most often. It contains your full commit history, which may include secrets you rotated months ago but never actually scrubbed from history.

Using :latest tags for base images

FROM node:latest

This looks fine until you come back six months later and node:latest is now a different major version, your build breaks, and you have no idea why because nothing in your Dockerfile changed. Or worse: the base image gets updated with a breaking change and your "working" image silently starts behaving differently after the next build.

Pin the version. Be specific:

FROM node:22.3.0-alpine3.20

Yes, you'll need to update it manually. That's the point. Updating a base image should be a deliberate decision, not something that happens automatically because you were lazy with the tag.

The :alpine variants are worth using for the size reduction, but pin the full version including the Alpine version. node:22-alpine is still a moving target.

No HEALTHCHECK

Docker has no idea if your application is actually working unless you tell it how to check. Without a HEALTHCHECK, a container that starts but immediately crashes and loops, or starts but never accepts connections, looks healthy to Docker and to any orchestrator reading Docker's health state.

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

If you're running an HTTP server, a simple /health endpoint that returns 200 is all you need. If you're not running an HTTP server, use whatever makes sense: a Redis ping, a database query, a file existence check.

The --start-period flag is worth setting correctly. Give your app enough time to actually start before health checks begin. If your app takes 8 seconds to start and you're checking every 5 seconds from the beginning, it'll be marked unhealthy before it's had a chance to come up.

Running multiple processes in one container

This one comes up a lot when people want to run a background worker alongside their web server, or bundle Nginx with their Node app. The temptation is to use a shell script or a process manager to run both.

The problem: Docker is designed around one process per container. When you run multiple processes, the container's exit code becomes meaningless (which process failed?), log output gets interleaved, and the lifecycle management that Docker, Kubernetes, and other orchestrators rely on breaks down. If your worker crashes, Docker doesn't know. Your container looks healthy. The worker is just gone.

Split them into separate containers and run them as separate services. Use docker-compose or whatever orchestration you have. If you genuinely need a process supervisor (rare but legitimate in some base image scenarios), use something like tini as PID 1 and know what you're trading off.

# Use tini as init process if you have a legitimate multi-process need
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["your-app"]

But honestly, for most applications: one container, one process, separate services for separate concerns.


None of these require deep Docker expertise to fix. They're just defaults that the tools and the AI don't fight against, so they accumulate. A USER appuser line and a .dockerignore file take under a minute each. Pinning a base image version is a copy-paste job. The HEALTHCHECK takes maybe five minutes if you need to add a health endpoint first.

If you're using Seaworthy, it flags all five of these automatically during a scan.

Dieser Artikel wurde von KI generiert und fasst öffentlich verfügbare Quellen zusammen.

Von den Machern von Seaworthy

Seaworthy scannt dein Repository nach den Problemen, die in Artikeln wie diesem behandelt werden.

Sicherheitslücken, offengelegte Geheimnisse und Fehlkonfigurationen — erkannt, bevor du bereitstellst. Kostenlos, kein Konto nötig.

$ npx seaworthycode