Container Base Images¶
26 cards — 🟢 6 easy | 🟡 9 medium | 🔴 5 hard
🟢 Easy (6)¶
1. What are the main container base image options and their sizes?
Show answer
Alpine: ~7MB (musl, ash shell, apk)Debian-slim: ~75MB (glibc, bash, apt)
Ubuntu: ~75MB (glibc, bash, apt, snap)
Distroless: ~20MB (glibc, no shell, no pkg mgr)
scratch: 0MB (literally nothing)
UBI: ~95-200MB (glibc, RHEL compat)
Chainguard/Wolfi: ~15MB (glibc, minimal, hardened)
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
2. Why should containers run as non-root and how do you set this up?
Show answer
Root in container = root on host if container escapes.Debian: RUN useradd -r -s /sbin/nologin -u 1000 appuser && USER appuser
Alpine: RUN adduser -D -u 1000 appuser && USER appuser
Distroless: use :nonroot tag variant.
K8s enforces with runAsNonRoot: true in securityContext.
Remember: always run containers as non-root. Add USER 1000:1000 in Dockerfile. Prevents container escape from gaining host root access.
3. Why should you never use :latest tag in production Dockerfiles?
Show answer
:latest today ≠ :latest tomorrow. A rebuild might pull a different version with breaking changes.Pin to specific version: python:3.12.4-slim-bookworm
Even floating tags like python:3.12-slim can change (3.12.4 → 3.12.5).
Rebuild intentionally, not accidentally.
Gotcha: never use :latest in production — it's mutable and unpredictable. Pin to specific versions (python:3.11.7-slim) for reproducible builds.
4. What is the quick decision tree for choosing a container base image?
Show answer
Need a shell? No → Need glibc? No → scratch (static binary). Yes → distroless/Chainguard.Need a shell? Yes → Need glibc? No → Alpine. Yes → RHEL required? Yes → UBI minimal. No → Debian-slim.
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
5. Why is .dockerignore important and what should be in it?
Show answer
Without .dockerignore, COPY . . sends everything to the daemon: .git/ (hundreds of MB), node_modules/, .env (SECRETS!), tests/, .venv/.Result: bloated image, slow builds, leaked secrets.
Minimum: .git, .github, .venv, __pycache__, node_modules, .env, *.md, tests/
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
6. What is the practical impact of container image size?
Show answer
Every 100MB removed: faster pulls (cold starts on new nodes), less disk usage across fleet, fewer CVEs to scan/patch, smaller attack surface.Example Python app: ubuntu ~450MB, python:slim ~150MB, python:alpine ~60MB, distroless ~50MB.
Size matters most for: cold start latency, CI build times, security scan surface.
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
🟡 Medium (9)¶
1. When should you use Alpine as a container base image?
Show answer
Good for: Go/Rust static binaries, simple apps without C extensions, size-critical deployments, CI tools.Avoid when: Python with numpy/pandas (C extensions), apps needing glibc, complex DNS requirements in Kubernetes (musl ndots issue), need for easy debugging.
Remember: Alpine = ~5 MB base image using musl libc and busybox. Smallest common base, but musl can cause compatibility issues with glibc-compiled binaries.
Gotcha: musl libc DNS resolution differs from glibc — it queries all nameservers in parallel, which can cause issues in some Kubernetes setups.
2. When should you use Debian-slim as a container base image?
Show answer
Best all-around choice when: you need glibc (Python C extensions, Java, .NET), you need apt for installing packages, you want a shell for debugging, Alpine's musl is causing issues.~75MB vs ~125MB full Debian. Good size-to-compatibility ratio.
Remember: slim variants (python:3.11-slim) remove docs, man pages, and dev tools. Good balance between size and compatibility. Based on Debian, uses glibc.
3. What is a distroless container image and what are its tradeoffs?
Show answer
Distroless has no shell, no package manager, no OS tools — only your binary + runtime libraries.Pros: minimal attack surface, no shell exploits, fewer CVEs, forces good build practices.
Cons: cannot exec into container for debugging, need debug variant for troubleshooting, harder to test locally.
Remember: distroless = no shell, no package manager, no OS tools. Only your app binary + runtime dependencies. Smallest attack surface, but harder to debug.
4. What is the multi-stage build pattern and why is it important?
Show answer
Stage 1 (build): large image with compilers, headers, build tools. Compile your app.Stage 2 (runtime): small image with only runtime deps. COPY --from=build the binary/artifacts.
Result: build tools never appear in production image.
Reduces size by 50-90% and removes build-time attack surface.
Remember: multi-stage builds use multiple FROM statements. Build stage has compilers/tools, final stage has only the runtime. Keeps images small and secure.
Example: Stage 1: FROM golang:1.21 AS builder (compile). Stage 2: FROM alpine:3.19 (copy binary only). Build tools never ship to production.
5. How do you optimize Docker layer caching?
Show answer
Copy dependency files FIRST, install deps, THEN copy source code:COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
This way, code changes don't bust the dependency cache.
Also: combine RUN commands, clean up in the same layer (rm -rf /var/lib/apt/lists/*).
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
6. What are the Red Hat UBI variants and when to use each?
Show answer
ubi9/ubi: full (~215MB, dnf, systemd) — general purposeubi9/ubi-minimal: smaller (~95MB, microdnf) — most production apps
ubi9/ubi-micro: tiny (~28MB, no pkg mgr) — like distroless
ubi9/ubi-init: with systemd — multi-process containers
Use for: OpenShift, enterprise compliance, FIPS/STIG requirements.
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
7. What are Chainguard images and why use them?
Show answer
Hardened, minimal, signed container images with SBOMs.Zero known CVEs (aggressively patched).
Wolfi-based: glibc (no musl issues) but Alpine-style minimal.
Provenance and attestation built-in.
Use when: supply chain security matters, compliance requires SBOM, you want glibc + minimal size.
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
8. Why and how often should you rebuild container images?
Show answer
Base image CVEs are discovered daily. Your 3-month-old image has unpatched vulnerabilities even if your code hasn't changed.Rebuild weekly with --no-cache to pull latest base.
Automate with CI (weekly cron job).
Scan with Trivy after each build.
Use Dependabot/Renovate to track base image updates.
Remember: rebuild base images regularly to pick up security patches. Set up automated builds triggered by upstream image updates. Stale images accumulate vulnerabilities.
9. Why do timezone operations fail in Alpine/distroless containers?
Show answer
Alpine and distroless don't include timezone data.time.LoadLocation("America/New_York") fails.
Fix: Alpine: apk add --no-cache tzdata. Debian-slim: apt install tzdata.
Set ENV TZ=America/New_York.
For distroless: copy /usr/share/zoneinfo from build stage.
Remember: Alpine = ~5 MB base image using musl libc and busybox. Smallest common base, but musl can cause compatibility issues with glibc-compiled binaries.
Gotcha: musl libc DNS resolution differs from glibc — it queries all nameservers in parallel, which can cause issues in some Kubernetes setups.
🔴 Hard (5)¶
1. What compatibility issues does Alpine's musl libc cause?
Show answer
Binaries compiled on glibc distros won't run on Alpine.Python C extensions: build fails without musl-dev headers.
DNS: no nsswitch.conf, different resolver behavior.
Go with CGO: linking errors (fix: CGO_ENABLED=0).
Node native modules: need python3, make, g++.
Locale: missing locales, encoding errors.
Threads: different stack defaults.
Remember: Alpine = ~5 MB base image using musl libc and busybox. Smallest common base, but musl can cause compatibility issues with glibc-compiled binaries.
Gotcha: musl libc DNS resolution differs from glibc — it queries all nameservers in parallel, which can cause issues in some Kubernetes setups.
2. What is the scratch base image and what does it require?
Show answer
scratch is literally empty (0 bytes). Only for statically compiled binaries (Go with CGO_ENABLED=0, Rust).You must copy in everything: CA certs for HTTPS, timezone data, /etc/passwd for user lookups, /tmp if needed.
No libc, no shell, no DNS resolver — your binary must be fully self-contained.
Remember: FROM scratch = empty image, no OS at all. Only works for statically compiled binaries (Go, Rust). Absolute smallest possible image.
3. Why is Alpine a poor choice for Python apps with C extensions?
Show answer
No pre-built wheels exist for musl libc — only glibc.Packages like pandas, numpy, cryptography must compile from source.
Build takes 10-30 minutes vs seconds on Debian-slim.
Requires gcc, musl-dev, linux-headers, etc.
Use python:3.12-slim instead for Python apps with C extensions.
Remember: Alpine = ~5 MB base image using musl libc and busybox. Smallest common base, but musl can cause compatibility issues with glibc-compiled binaries.
Gotcha: musl libc DNS resolution differs from glibc — it queries all nameservers in parallel, which can cause issues in some Kubernetes setups.
4. Why does Alpine cause DNS issues in Kubernetes?
Show answer
Alpine's musl resolver doesn't handle ndots:5 (K8s default) well. It queries all search domains for every DNS lookup, causing 10x more DNS queries.Can overwhelm CoreDNS under load.
Fix: set dnsConfig.options ndots=1 in Pod spec.
Or use Debian-slim and avoid the issue entirely.
Remember: Alpine = ~5 MB base image using musl libc and busybox. Smallest common base, but musl can cause compatibility issues with glibc-compiled binaries.
Gotcha: musl libc DNS resolution differs from glibc — it queries all nameservers in parallel, which can cause issues in some Kubernetes setups.
5. How do you debug a distroless or scratch container with no shell?
Show answer
1. kubectl debug -it pod/myapp --image=busybox (ephemeral debug container)2. kubectl debug -it pod/myapp --image=nicolaka/netshoot (network debugging)
3. kubectl cp pod/myapp:/app/logs/error.log ./error.log
4. kubectl logs pod/myapp
5. Build a debug stage in Dockerfile (--target debug)
6. Use distroless :debug variant temporarily
Remember: distroless = no shell, no package manager, no OS tools. Only your app binary + runtime dependencies. Smallest attack surface, but harder to debug.