# Use Minimal Base Images
## Rule
Production containers MUST use minimal base images. Never use full OS images (ubuntu, debian) as the runtime base. Use distroless, Alpine, or scratch.
## Image Selection Guide
| Use Case | Recommended Base | Size |
|----------|-----------------|------|
| Node.js | `gcr.io/distroless/nodejs20-debian12` | ~120 MB |
| Python | `python:3.12-slim` | ~150 MB |
| Go | `scratch` or `gcr.io/distroless/static` | ~2-15 MB |
| Rust | `scratch` or `gcr.io/distroless/cc` | ~2-20 MB |
| Java | `gcr.io/distroless/java21-debian12` | ~220 MB |
| General | `alpine:3.19` | ~7 MB |
## Good Examples
```dockerfile
# Node.js — distroless (no shell, no package manager)
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist ./dist
CMD ["dist/index.js"]
# Go — scratch (literally empty)
FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
# Python — slim variant
FROM python:3.12-slim
# 150 MB vs 1 GB for full python:3.12
```
## Bad Examples
```dockerfile
# BAD: Full Ubuntu image (~77 MB base + packages)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nodejs
# Hundreds of unnecessary packages, dozens of CVEs
# BAD: Full Node.js image (~1.1 GB)
FROM node:20
# Includes: npm, yarn, git, build-essential, python3...
```
## Alpine vs Distroless vs Scratch
| Feature | Alpine | Distroless | Scratch |
|---------|--------|------------|---------|
| Shell access | Yes (ash) | No | No |
| Package manager | Yes (apk) | No | No |
| Debugging | Easy | Hard | Hardest |
| CVE surface | Small | Minimal | None |
| Best for | Dev/debug | Production | Static binaries |
## Enforcement
- Hadolint custom rules to flag ubuntu/debian base images in production
- CI policy to reject images over a size threshold
- Regular CVE scanning with Trivy to compare image variants