From a17dd5a4efeae52d9c88799c68d9840e7bc1a43e Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Fri, 18 Sep 2026 16:04:17 -0500 Subject: [PATCH] Keep secrets and runtime data out of the Docker images The build context is the repository root and there was no .dockerignore, so 'COPY backend/ ./' swept backend/venv (327 MB), backend/media (the actual library, 224 MB), backend/logs, backend/staticfiles and backend/.env (SECRET_KEY plus the database password) into the backend image: 1.43 GB per architecture, including secrets headed for the registry. The frontend build stage also copied the host's node_modules over the fresh install. - Root .dockerignore excludes .git, virtualenvs, __pycache__, db.sqlite3, logs/staticfiles, .env files, media/, node_modules, dist and the deploy runtime state (data/, tailscale-state/). - The backend Dockerfile now asserts the context is clean (.env, venv, media/library, db.sqlite3 all absent) before collectstatic, so a missing ignore file fails the build instead of leaking. - Rebuilt: backend 1.43 GB -> 876 MB ('COPY backend/' is now 268 kB), frontend stays at 65 MB. Verified by booting the compose stack with the new image: migrations applied, /health ok, no .env or venv inside, and /app/media is the mounted (empty) volume; the scheduler runs too. - Removed the stale local images that still contained the library and the dev .env. --- .dockerignore | 35 +++++++++++++++++++++++++++++++++++ deploy/J621-Backend | 8 ++++++++ deploy/README.md | 9 +++++++++ 3 files changed, 52 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c6ed63c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Build context is the repository root (see deploy/J621-Backend and +# deploy/J621-Frontend). Keep the context small and, more importantly, keep +# secrets and runtime data out of the images. + +# Version control / tooling +.git +.gitignore +.dockerignore + +# Python: the dev virtualenv, caches, and anything generated at runtime +backend/venv/ +**/__pycache__/ +**/*.py[cod] +backend/db.sqlite3 +backend/logs/ +backend/staticfiles/ + +# Secrets and media: the .env holds SECRET_KEY and DB passwords, media/ is +# the actual library. The deploy composes mount these at runtime instead. +backend/.env +backend/.env.* +backend/media/ + +# Frontend build artefacts (npm ci installs fresh from the lockfile) +frontend/node_modules/ +frontend/dist/ + +# Deploy runtime state and env +deploy/.env +deploy/.env.* +deploy/data/ +deploy/tailscale-state/ + +# Misc +*.log diff --git a/deploy/J621-Backend b/deploy/J621-Backend index b336542..923d11d 100644 --- a/deploy/J621-Backend +++ b/deploy/J621-Backend @@ -30,6 +30,14 @@ COPY backend/ ./ COPY deploy/backend-entrypoint.sh /usr/local/bin/j621-entrypoint COPY deploy/scheduler-entrypoint.sh /usr/local/bin/j621-scheduler +# Guard: fail the build if the context leaked secrets or runtime data +# (.dockerignore excludes them — see the repository root). +RUN test ! -e /app/.env \ + && test ! -d /app/venv \ + && test ! -d /app/media/library \ + && test ! -e /app/db.sqlite3 \ + && echo "build context clean" + RUN chmod +x /usr/local/bin/j621-entrypoint /usr/local/bin/j621-scheduler \ && mkdir -p /app/media /app/logs \ && python manage.py collectstatic --noinput diff --git a/deploy/README.md b/deploy/README.md index 695ac64..927c925 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -114,6 +114,15 @@ ffmpeg for video thumbnails. The `GIT_HASH` build arg is baked into the backend image so the shell's version pill shows the commit (images have no `.git` directory); the push scripts pass it automatically. +Both build from the repository root, which is filtered by `.dockerignore`: +`backend/venv`, `backend/media`, `backend/logs`, `backend/staticfiles`, +`backend/.env`, `frontend/node_modules`, `frontend/dist` and the deploy +runtime state never enter the images — the database, media and logs come +from the compose volumes and `deploy/.env` at runtime. The backend build +also asserts that (`.env`, `venv`, `media/library`, `db.sqlite3` absent), so +a missing ignore file fails the build instead of shipping secrets. Rebuild +(and `docker image prune`) if you built before that guard existed. + Push multi-arch images to the Gitea registry: ```bash