#6: Dockerfile

Dockerfile

Path: ./Dockerfile · Type: Docker build instructions · Lines: 43 · Source: GitHub

43 lines · 16 code · 13 comments · 14 blank

What it does

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:

Dockerfile components: ┌─────────────────────────────────────────────┐ │ /app/application.jar ← line 12 │ │ /app/entrypoint.sh ← line 13 │ │ /ProjectForge/ ← line 31 (VOLUME) │ │ ├─ environment.sh ← line 34 │ │ └─ projectforge.properties (runtime) │ └─────────────────────────────────────────────┘

Section-by-section

Lines 1–7: Base image and setup

1  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.

Lines 9–16: Copying files

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 executable

Two 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.

Line 19: Port exposure

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").

Lines 22–27: Non-root user + procps

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.

Lines 30–32: Volume

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.

Line 34: Environment template

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.

Lines 37–43: Entrypoint

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).

Key takeaways