How to Choose the Right Base Image for Multi-Stage Builds?¶
When choosing base images in a multi-stage Docker build, it depends on: - The projectβs programming language (Node.js, Python, Java, Go, etc.) - The role of each stage (Build stage vs. Final stage) - Performance & security needs (Size, dependencies, vulnerabilities)
General Rule: Build Stage vs. Final Stage¶
| Stage | Purpose | Typical Base Images |
|---|---|---|
| Build Stage | Compiles the application, installs dependencies | Alpine, Debian, Ubuntu, official language images (node, golang, python, maven, etc.) |
| Final Stage | Runs the application efficiently | Alpine, Distroless, Nginx, Scratch (minimal, optimized) |
How to Decide?¶
π Is the project compiled (like Go, Java)?¶
- Use a heavy image in the build stage (e.g., golang, maven, node)
- Use a lightweight runtime in the final stage (e.g., scratch, distroless, nginx)
π Does the project need an interpreter (like Python, Node.js)?¶
- Use a full OS base in the final stage (e.g., python:slim, node:alpine)
π Does the project serve static files?¶
- Use nginx in the final stage
π Is security & size a priority?¶
- Use alpine or distroless
Project-Specific Examples¶
Example 1: Node.js (React/Vue) Frontend with Nginx¶
Why? Node.js needed for npm build, but runtime should be lightweight (nginx).
Dockerfile:
# Build stage
FROM node:alpine AS build
WORKDIR /app
COPY . .
RUN npm install && npm run build
# Final stage (only static files)
FROM nginx:alpine AS final
COPY --from=build /app/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
Example 2: Java Spring Boot with OpenJDK¶
Why? Maven used for build, but only JDK runtime needed in final stage.
Dockerfile:
# Build stage
FROM maven:3.8.6-openjdk-17 AS build
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# Final stage (runtime only)
FROM openjdk:17-jdk-slim AS final
WORKDIR /app
COPY --from=build /app/target/myapp.jar /app.jar
CMD ["java", "-jar", "/app.jar"]
Example 3: Python Flask App¶
Why? Python used for both build and runtime but optimized with python:slim.
Dockerfile:
# Build and runtime in one (smallest possible image)
FROM python:3.11-slim AS final
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python3", "app.py"]
Example 4: Golang App (Fully Compiled)¶
Why? Go compiles to a single binary, so final stage needs nothing except execution.
Dockerfile:
# Build stage
FROM golang:1.21-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o myapp
# Final stage (smallest possible)
FROM scratch AS final
COPY --from=build /app/myapp /myapp
CMD ["/myapp"]
Key Takeaways¶
- Pick a build image based on project dependencies (Node.js, Maven, Golang, etc.)
- Pick a final image based on runtime needs (Alpine, Distroless, Nginx, Scratch, Slim, etc.)
- Optimize for size & security
- Always remove unnecessary dependencies from the final image