Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner ~14 min· topic 4 of 5

Topic 2.4

Dockerfile Best Practices

In one line

A checklist of habits that separate a Dockerfile a senior engineer would approve in code review from one that 'just works' on your laptop.

0/5 · 0%

Key ideas

  1. 01

    Use SPECIFIC base image tags, never bare latest (Topic 1.4) — FROM eclipse-temurin:17.0.9_9-jre (or at least 17-jre) is reproducible; FROM eclipse-temurin:latest is a ticking time bomb of surprise breakage.

  2. 02

    Prefer smaller base images (-alpine or -slim variants) when your app doesn't need the full OS toolset — smaller images pull faster, deploy faster, and have a smaller attack surface (fewer packages that could have vulnerabilities). Weigh this against Alpine's musl-libc occasionally causing subtle compatibility issues with certain compiled dependencies.

  3. 03

    Never run as the root user inside a container if you can avoid it — create and switch to a non-root user with RUN useradd -m appuser and USER appuser. If an attacker compromises your app inside the container, running as non-root limits what they can do even within that container's boundary (Phase 8 covers this in full depth).

  4. 04

    Combine related RUN instructions with && into fewer layers where sensible — e.g. RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* in ONE instruction, not three separate RUNs, because otherwise the package LISTS downloaded by apt-get update get baked into their own layer and never actually get cleaned up even if a later separate RUN tries to remove them (each RUN's cleanup only affects its OWN layer).

  5. 05

    Use a HEALTHCHECK instruction so Docker (and orchestrators built on top of it) can tell whether your app is actually WORKING, not just whether the process is technically still running — a hung app that never crashes but never responds either is invisible without one.

  6. 06

    Set explicit resource-conscious defaults where it makes sense (e.g. JVM heap flags for a memory-constrained container) rather than letting the JVM assume it has the WHOLE host machine's memory available, which it will do by default even inside a container with a much smaller memory limit.

  7. 07

    Keep ONE process/concern per container as the general rule (Docker's own philosophy) — a container running a Spring Boot app should just run that app, not also a cron daemon, a log rotator, and an SSH server bolted on; compose multiple containers together instead (Phase 5).

In your stack

  • →

    A production-grade Spring Boot Dockerfile checklist in one place: pin the base image tag, run as a non-root user, set -XX:MaxRAMPercentage so the JVM respects the container's memory limit instead of the host's total memory, and add a HEALTHCHECK hitting Spring Boot Actuator's /actuator/health endpoint.

Code & diagrams

production-dockerfilemarkdown

Every line here maps directly to one of this topic's bullet points.

FROM eclipse-temurin:17.0.9_9-jre

# Create and switch to a non-root user — never run as root if avoidable
RUN useradd -m -u 1000 appuser
USER appuser

WORKDIR /app
COPY --chown=appuser:appuser target/myapp.jar app.jar

EXPOSE 8080

# Let Docker (and any orchestrator) know whether the app is actually healthy,
# not just whether the process happens to still be alive
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1

# Respect the CONTAINER's memory limit, not the host machine's total RAM
CMD ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Explain it without notes

01

Why is RUN apt-get update && apt-get install -y curl in ONE instruction meaningfully different from the same two commands as TWO separate RUN instructions?

02

Why does a JVM running inside a container with a 512MB memory limit sometimes get OOM-killed even though -Xmx was never explicitly set very high?

Practice

01

Take any Dockerfile you've written so far and add a non-root USER instruction to it, rebuild, and confirm with docker exec <container> whoami that it's no longer running as root.

02

Add a HEALTHCHECK to a Dockerfile for any app you have (even a simple one hitting / with wget/curl), rebuild, run it, and watch docker ps show a 'healthy'/'unhealthy' status over time.

Trade-offs

  • ↔

    Every best practice here (non-root user, healthchecks, pinned versions, combined RUN layers) adds a small amount of Dockerfile verbosity and setup time — worthwhile for anything beyond a quick local experiment, and effectively mandatory for anything that will ever run in production or be shared with a team.

Done when you can

  • My Dockerfiles pin specific base image versions, never bare 'latest'.

  • My containers run as a non-root user by default.

  • I combine related RUN commands to avoid baking temporary files into permanent layers.

  • My production-bound Dockerfiles include a HEALTHCHECK.