Was sind Multi-Stage Builds?
Bei normalen Dockerfiles landet der gesamte Build-Kontext im finalen Image – Compiler, npm/node_modules, Quellcode, Test-Tools. Das macht Images unnötig groß und erhöht die Angriffsfläche.
Multi-Stage Builds lösen das: Sie definieren mehrere FROM-Stufen. Nur was Sie explizit kopieren landet im finalen Image.
Node.js Multi-Stage Build
# Stage 1: Dependencies installieren und bauen
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Build (TypeScript kompilieren)
FROM builder AS compile
RUN npm ci
COPY tsconfig.json .
COPY src/ ./src/
RUN npm run build
# Stage 3: Finales Production-Image
FROM node:20-alpine AS production
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
# Nur node_modules (production) und dist/ kopieren
COPY --from=builder /app/node_modules ./node_modules
COPY --from=compile /app/dist ./dist
COPY package.json .
USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]
Größenvergleich:
- Ohne Multi-Stage: ~800 MB
- Mit Multi-Stage: ~120 MB
Go Multi-Stage Build
Go ist prädestiniert für Multi-Stage – statisch gelinkte Binaries:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
# Finales Image: NUR die Binary!
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
Image-Größe: ~10 MB (verglichen mit ~700 MB mit Standard-Go-Image)!
Java Spring Boot
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN ./mvnw dependency:resolve -q
COPY src ./src
RUN ./mvnw package -DskipTests
# Layered JAR extrahieren (für besseren Cache)
RUN java -Djarmode=layertools -jar target/*.jar extract
FROM eclipse-temurin:21-jre AS production
WORKDIR /app
RUN addgroup --system app && adduser --system --group app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
USER app
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
Spezifische Stages bauen
# Nur bis zur "test" Stage bauen (CI-Pipeline)
docker build --target test -t myapp:test .
# Production Stage
docker build --target production -t myapp:latest .
Build-Cache optimal nutzen
# Dependencies ZUERST kopieren (ändert sich selten → Cache-Hit)
COPY package.json package-lock.json ./
RUN npm ci
# Quellcode DANACH (ändert sich oft → invalidiert Cache ab hier)
COPY src/ ./src/
RUN npm run build
BuildKit Build-Secrets
# Secret wird nicht im Image gespeichert!
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install @private/package
DOCKER_BUILDKIT=1 docker build \
--secret id=npmrc,src=.npmrc \
-t myapp:latest .
FAQ
Kann ich Stufen beliebig benennen?
Ja, mit AS stagename. Ohne Namen sind sie als 0, 1, 2 adressierbar.
Was passiert mit Zwischenstufen nach dem Build?
Sie bleiben im Build-Cache für schnellere Folge-Builds. Mit docker buildx prune werden sie bereinigt.
Fazit
Multi-Stage Builds sind Pflicht für Produktions-Images – kleinere Images, schnellere Deployments und bessere Sicherheit.
DevOps-Optimierungen für KMU in Heidelberg, Mannheim und der Rhein-Neckar-Region. Anfragen.