Build production-ready multi-stage Dockerfile for the backend #134

Open
opened 2026-03-28 08:51:38 +01:00 by marcel · 0 comments
Owner

Why

backend/Dockerfile currently runs ./mvnw spring-boot:run against a bind-mounted source tree. This means:

  • The container cannot run without the host filesystem — it is not a self-contained image.
  • It uses a full JDK at runtime (eclipse-temurin:21-jdk, ~600 MB) rather than a minimal JRE.
  • Maven downloads dependencies at container startup, making cold starts slow and fragile.
  • It is unsuitable for any deployment beyond local dev.

What to do

Replace backend/Dockerfile with a two-stage build:

Stage 1 — Build (JDK + Maven): compile the source, resolve all dependencies, produce the fat JAR.

Stage 2 — Runtime (JRE only): copy the JAR from stage 1, nothing else. No Maven, no source, no JDK tools.

# Stage 1: build
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY .mvn/ .mvn/
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve -q   # cache layer — only re-runs if pom.xml changes
COPY src ./src
RUN ./mvnw clean package -DskipTests

# Stage 2: runtime
FROM eclipse-temurin:21-jre AS runtime
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Additional hardening

  • Add a non-root user in the runtime stage (RUN addgroup --system app && adduser --system --ingroup app app, then USER app).
  • Pass JVM tuning flags via JAVA_OPTS environment variable so they can be overridden at deploy time without rebuilding the image.
  • The ENTRYPOINT should use exec form (JSON array) to ensure signals (SIGTERM) are passed directly to the JVM, enabling graceful shutdown.

CI impact

The e2e-tests job in ci.yml currently builds the JAR with mvnw clean package -DskipTests and runs it directly with java -jar. That job does not use Docker for the backend — so no CI changes are needed for the backend Dockerfile immediately.

However, once a production image build + push step is added to CI (future), this Dockerfile will be the one used.

Acceptance criteria

  • docker build -t familienarchiv-backend . from backend/ produces an image with no Maven or JDK tooling in the final layer.
  • docker run --env-file .env familienarchiv-backend starts successfully and /actuator/health returns UP.
  • Final image size is under 350 MB.
## Why `backend/Dockerfile` currently runs `./mvnw spring-boot:run` against a bind-mounted source tree. This means: - The container **cannot run without the host filesystem** — it is not a self-contained image. - It uses a full **JDK** at runtime (eclipse-temurin:21-jdk, ~600 MB) rather than a minimal JRE. - Maven downloads dependencies at container startup, making cold starts slow and fragile. - It is **unsuitable for any deployment** beyond local dev. ## What to do Replace `backend/Dockerfile` with a two-stage build: **Stage 1 — Build** (JDK + Maven): compile the source, resolve all dependencies, produce the fat JAR. **Stage 2 — Runtime** (JRE only): copy the JAR from stage 1, nothing else. No Maven, no source, no JDK tools. ```dockerfile # Stage 1: build FROM eclipse-temurin:21-jdk AS build WORKDIR /app COPY .mvn/ .mvn/ COPY mvnw pom.xml ./ RUN ./mvnw dependency:resolve -q # cache layer — only re-runs if pom.xml changes COPY src ./src RUN ./mvnw clean package -DskipTests # Stage 2: runtime FROM eclipse-temurin:21-jre AS runtime WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] ``` ## Additional hardening - Add a non-root user in the runtime stage (`RUN addgroup --system app && adduser --system --ingroup app app`, then `USER app`). - Pass JVM tuning flags via `JAVA_OPTS` environment variable so they can be overridden at deploy time without rebuilding the image. - The `ENTRYPOINT` should use exec form (JSON array) to ensure signals (SIGTERM) are passed directly to the JVM, enabling graceful shutdown. ## CI impact The `e2e-tests` job in `ci.yml` currently builds the JAR with `mvnw clean package -DskipTests` and runs it directly with `java -jar`. That job does **not** use Docker for the backend — so no CI changes are needed for the backend Dockerfile immediately. However, once a production image build + push step is added to CI (future), this Dockerfile will be the one used. ## Acceptance criteria - `docker build -t familienarchiv-backend .` from `backend/` produces an image with no Maven or JDK tooling in the final layer. - `docker run --env-file .env familienarchiv-backend` starts successfully and `/actuator/health` returns `UP`. - Final image size is under 350 MB.
marcel added the devopsphase-2: container-images labels 2026-03-28 10:46:40 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#134