From 0938da8a73f8403386f486234c6c95faf7020740 Mon Sep 17 00:00:00 2001 From: implementer Date: Sat, 29 Aug 2026 01:28:26 +0000 Subject: [PATCH] feat: keep secrets out of the app image (E00-S02-T08) - .dockerignore: exclude env + credential files (.npmrc, .netrc, .aws, .ssh, secrets/, *.pem, *.key, *.p12, *.pfx, *.jks, id_rsa, id_ed25519, ...) from the build context so a local secret file cannot be embedded in the image - Dockerfile: document the T08 guarantee (no secret ARG/ENV, fixed non-secret COPY paths, runtime credentials via Compose environment) - compose.yaml: T08 in scope; runtime credentials stay in service environment, never in the image --- .dockerignore | 18 ++++++++++++++++++ apps/server/Dockerfile | 9 +++++++++ compose.yaml | 12 +++++++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 95e1211..227d2ea 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,5 +14,23 @@ coverage .env .env.* +# Secrets & credentials (E00-S02-T08) — never part of the build context, so a +# secret-bearing file cannot be embedded in the image even if a developer has +# one locally. Keep this list in sync with tests/secrets-not-embedded.test.mjs. +.npmrc +.netrc +.credentials +.aws +.ssh +secrets +secrets/ +*.pem +*.key +*.p12 +*.pfx +*.jks +id_rsa +id_ed25519 + # Logs *.log diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 18a879a..d93d424 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -17,6 +17,15 @@ # T05 the runtime stage drops root privileges (runs as the image's non-root # `node` user). # +# T08: the image embeds no secrets. The Dockerfile declares no secret-bearing +# ARG/ENV instruction (the only ENV is `NODE_ENV=production`) and every COPY +# copies a fixed, non-secret path (manifests, source, compiled dist) — never +# `.env` or credential files; `.dockerignore` additionally excludes env and +# credential files from the build context, so a local secret file cannot be +# embedded even by mistake. Runtime credentials (e.g. DATABASE_URL) are +# injected by Compose at run time (compose.yaml `app.environment`), never baked +# into the image. Tests: tests/secrets-not-embedded.test.mjs. +# # Image base: node:24.19.0-bookworm-slim (glibc Debian) per Technology-Stack # §5.4 — argon2 is a native dependency and musl/Alpine causes native-module # build surprises, so the image must stay on a glibc base. diff --git a/compose.yaml b/compose.yaml index 08903b4..8d648e5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,4 +1,4 @@ -# EPPP Docker Compose baseline — [E00-S02-T01..T07] +# EPPP Docker Compose baseline — [E00-S02-T01..T08] # # `docker compose up -d` starts both the database (PostgreSQL) and the # application (@personal-blog/server). Rollback: `docker compose down`. @@ -37,8 +37,14 @@ # so local runs and the T01..T06 real-stack probes are unaffected. Rollback: # drop the `platforms` list from the `app` build config. # -# Explicitly out of scope for T01..T07 (land in a later E00-S02 task): -# - secrets not embedded (T08) +# Secrets not embedded (T08): the app image carries no secrets — the committed +# apps/server/Dockerfile declares no secret-bearing ARG/ENV instruction and +# copies only fixed, non-secret paths, and the committed .dockerignore excludes +# env and credential files from the build context. Runtime credentials are +# injected here, at run time, via service `environment` values (the app's +# DATABASE_URL, the db service's POSTGRES_* defaults) — they live in +# Compose/deploy config, never in the image. Rollback: rebuild the image after +# removing any embedded secret. Tests: tests/secrets-not-embedded.test.mjs. # # 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).