K8sCalc

docker

Dockerfile Generator

Generate a production-ready Dockerfile for Node.js, Python, Go, Rust, or Java. Supports multi-stage builds, non-root users, and minimal base images.

Dockerfile Best Practices

A well-written Dockerfile produces a small, secure, cacheable image. Here are the key patterns used in this generator.

Layer Caching

Docker caches each layer. Put rarely-changing layers first:

dockerfile
# Good — package.json copied before source code
COPY package*.json ./
RUN npm ci
COPY . .  # ← only this layer invalidates on code change

# Bad — all layers rebuild on every code change COPY . . RUN npm ci ```

Multi-Stage Builds

dockerfile
FROM node:20-alpine AS builder
RUN npm ci && npm run build

FROM node:20-alpine AS runner COPY --from=builder /app/.next ./.next # Build tools stay in builder, not in runner ```

Non-Root User

dockerfile
RUN addgroup --system --gid 1001 appgroup  && adduser  --system --uid 1001 appuser
USER appuser

Signal Handling (exec form)

dockerfile
# Good — process receives SIGTERM directly
CMD ["node", "server.js"]

# Bad — SIGTERM goes to shell, not node CMD node server.js ```

Frequently Asked Questions

Why use multi-stage builds?

Multi-stage builds separate the build environment (compiler, dev dependencies) from the runtime image. A Node.js app might need 500 MB of build tools but only 50 MB to run. Multi-stage reduces the final image size by 5–10× and removes build tools that could be exploited.

Why run as a non-root user?

By default Docker containers run as root inside the container. If an attacker escapes the container, they have root access to the host. Running as a non-root user (UID 1001) limits blast radius. Kubernetes also enforces non-root via PodSecurityContext.

What's the difference between CMD and ENTRYPOINT?

ENTRYPOINT sets the executable that always runs. CMD sets default arguments that can be overridden at runtime. Best practice: use ENTRYPOINT for the binary and CMD for default flags. For shell scripts use CMD ["node", "server.js"] (exec form, not shell form) to avoid PID 1 issues with signals.

Why does Go use 'FROM scratch'?

Go compiles to a fully static binary (CGO_ENABLED=0). The scratch image is literally empty — no shell, no libc, nothing. This produces the smallest possible image (~5–10 MB) and the smallest attack surface. The only caveat: you need to copy CA certificates for TLS.