diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index a42ccb8..8b99809 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -8,10 +8,11 @@ # server answering `GET /health` with `{"status":"ok"}` (HTTP 200) on port # 3000, so the app container stays up and the health endpoint succeeds. The # Fastify 5 application shell (and the real HTTP API) lands in a later story; -# DB volume persistence (T04) is a Compose-level concern (see compose.yaml — -# this image is unchanged), while read-only root filesystem (T06) and -# multi-arch build targets (T07) remain later E00-S02 tasks — all out of scope -# here. Since T05 the runtime stage drops root privileges (runs as the +# DB volume persistence (T04) and read-only root filesystem (T06) are +# Compose-level concerns (see compose.yaml — the `db-data` volume mount and the +# app service's `read_only: true` + `/tmp` tmpfs; this image is unchanged), +# while multi-arch build targets (T07) remains a later E00-S02 task — out of +# scope here. Since T05 the runtime stage drops root privileges (runs as the # image's non-root `node` user). # # Image base: node:24.19.0-bookworm-slim (glibc Debian) per Technology-Stack diff --git a/compose.yaml b/compose.yaml index 4cb33c8..a703515 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,4 +1,4 @@ -# EPPP Docker Compose baseline — [E00-S02-T01..T05] +# EPPP Docker Compose baseline — [E00-S02-T01..T06] # # `docker compose up -d` starts both the database (PostgreSQL) and the # application (@personal-blog/server). Rollback: `docker compose down`. @@ -23,8 +23,15 @@ # so the app container does not run with root privileges. No Compose-level # `user:` override is needed: the image's USER is inherited by the container. # -# Explicitly out of scope for T01..T05 (land in later E00-S02 tasks): -# - read-only root filesystem (T06), multi-arch build targets (T07) +# Read-only root filesystem (T06): the `app` service sets `read_only: true`, so +# the container's root filesystem is mounted read-only — a write anywhere on it +# is denied. Writable paths are limited to declared mounts and tmpfs: the app +# declares a `tmpfs` at `/tmp` and no writable volume/bind mounts, so `/tmp` is +# the only writable path. Rollback: drop `read_only`/`tmpfs` from the `app` +# service. +# +# Explicitly out of scope for T01..T06 (land in later E00-S02 tasks): +# - multi-arch build targets (T07), secrets not embedded (T08) # # All values have defaults so `docker compose up -d` works from a clean clone # without a .env file (a committed .env.example template lands in E00-S04). @@ -68,6 +75,14 @@ services: depends_on: db: condition: service_healthy + # T06: read-only root filesystem — the container's root filesystem is + # mounted read-only (`read_only: true`), so writes are denied everywhere + # except the declared mounts/tmpfs below. The app writes nothing else, so + # the only writable path is the declared `tmpfs` at `/tmp` (no writable + # volumes or bind mounts on this service). + read_only: true + tmpfs: + - /tmp # Named volumes shared across `docker compose` lifecycles. `db-data` (T04) # holds the PostgreSQL data directory and is preserved across restart and