Dockerfile1 FROM openjdk:17-slim 4 ARG JAR_FILE 7 WORKDIR /app
openjdk:17-slim: The slim variant excludes unnecessary packages (compilers, docs) — smaller attack surface and faster download. ARG JAR_FILE is a build-time argument passed via --build-arg JAR_FILE=... in docker build, typically set by build.gradle.kts to point to the built fat JAR.
9 RUN mkdir -p /app
12 COPY ${JAR_FILE} /app/application.jar ← fat JAR from build
13 COPY docker/entrypoint.sh /app/entrypoint.sh ← startup script
16 RUN chmod +x /app/entrypoint.sh ← make executableTwo files copied: the Spring Boot fat JAR (renamed to application.jar) and the entrypoint.sh script (made executable). The ARG JAR_FILE from line 4 is substituted here at build time.
19 EXPOSE 8080
8080 is the default Spring Boot port. Note that EXPOSE is documentation only — it doesn't actually publish the port. The actual port mapping is done in docker-compose.yml line 12 (ports: - "8080:8080").
22 RUN addgroup --system projectforge && \
adduser --system --ingroup projectforge projectforge
27 RUN apt-get update && apt-get install -y procps && \
rm -rf /var/lib/apt/lists/*Security: runs as a non-root user (projectforge:projectforge). The --system flag creates a system account (no home directory, no login shell, UID < 1000). procps package provides pgrep, needed by entrypoint.sh line 14 for the checkStopped() function. The rm -rf /var/lib/apt/lists/* cleanup is standard Docker practice — removes package cache to keep the image small.
31 RUN mkdir -p /ProjectForge && chown -R projectforge:projectforge /ProjectForge 32 VOLUME /ProjectForge
The VOLUME declaration tells Docker this directory holds persistent data — the projectforge.properties config file, the embedded HSQLDB database, and logs. Without a volume, all data would be lost on container restart. The directory is owned by the non-root user so the Java process can write to it.
34 COPY docker/environment.sh /ProjectForge
Copies the template from docker/environment.sh into the volume. Since /ProjectForge is a VOLUME, files copied here at build time will be visible only on first run — if an external volume is mounted, it shadows this copy. This is intentional: the template is there for first-time setup, but afterwards the admin's customized version takes over.
37 USER projectforge:projectforge ← switch to non-root 40 ENTRYPOINT ["/app/entrypoint.sh"] ← JSON exec form 41 # ENTRYPOINT ["java", "-jar", "/app/application.jar"] ← bypass (commented) 43 LABEL maintainer="Micromata"
Line 40: Uses the JSON exec form of ENTRYPOINT (array syntax) — this does not invoke a shell, so signals pass directly to entrypoint.sh. Line 41 is commented out: uncomment it to bypass the shutdown logic in entrypoint.sh and run Java directly (useful for debugging).
procps (for pgrep) added beyond the base Java image.projectforge:projectforge — good Docker security practice./app (immutable), environment.sh → /ProjectForge (persistent).openjdk:17-slim — the full JDK isn't needed at runtime.
Builds the ProjectForge Docker image — a 43-line Dockerfile that creates a minimal, rootless Java 17 container with a volume for persistent data. The image contains 4 components connected by entrypoint.sh: