Skip to content

fix(containers): production frontend image, non-root, no dev-mode masking - #10

Merged
OneByJorah merged 1 commit into
masterfrom
hermes/2026-10-09-harden-containers
Oct 9, 2026
Merged

OneByJorah merged 1 commit into
masterfrom
hermes/2026-10-09-harden-containers

Conversation

@OneByJorah

Copy link
Copy Markdown
Owner

Context

Docker is now installed on the build host, so the container path could finally be exercised. It was badly broken — and had never worked.

Fixes

1. The "production" frontend image ran npm run dev — a hot-reloading development server shipped as the production artifact, from an image running as root (trivy DS-0002, HIGH).
Replaced with a three-stage Next.js standalone build (deps → builder → runner) running as the unprivileged node user.

2. docker-compose.yml masked the image three separate ways — this is why the stack never worked:

  • command: ["npm", "run", "dev"] overrode the image's CMD
  • ./frontend:/app bind-mount hid the built application
  • an anonymous /app/node_modules volume shadowed the installed modules

Result: the container crash-looped with sh: next: not found. All three removed. The backend had the same ./backend:/app masking.

3. backend/Dockerfile could not build at all. Installing libssl-dev failed outright — the index pins libssl3t64 3.5.7-1~deb13u2 while u3 is what gets offered. apt is now gone entirely: no compiler is needed, since every dependency in requirements.txt ships a manylinux wheel. Base bumped to python:3.12-slim to match the repo's declared target, and the missing HEALTHCHECK added (DS-0026), using the bundled interpreter so no curl is required.

4. No .dockerignore in either context — the 539 MB node_modules, the .git directory and any local .env would have been copied into the images. Both added.

5. Compose ports bound to 127.0.0.1 instead of 0.0.0.0.

Verification (real containers, real commands)

Check Result
docker compose up -d backend healthy, frontend healthy in 10s
curl 127.0.0.1:8000/health 200
curl 127.0.0.1:3000/ 200
docker exec nethermind-backend id uid=1000(netcore)
docker exec nethermind-frontend id uid=1000(node)
trivy config both Dockerfiles 0 findings (was 1 HIGH + 2 LOW)
pytest tests/ -q 11 passed
gitleaks clean
after compose down -v 0 containers left

Before this branch: frontend unhealthy/restarting with exit 127, and the backend image would not build.

Publishing mode is draft — review only, nothing released.

…king

First time the container path could be exercised (docker now installed) — it was
badly broken.

- frontend/Dockerfile ran 'npm run dev': a hot-reloading DEVELOPMENT server shipped
  as the production artifact, in an image that ran as root. Replaced with a
  three-stage Next.js standalone build (deps -> builder -> runner) that runs as the
  unprivileged 'node' user (trivy DS-0002 HIGH cleared).
- docker-compose.yml masked the image three ways: 'command: [npm run dev]' overrode
  the image CMD, './frontend:/app' hid the built app, and an anonymous
  '/app/node_modules' volume shadowed the installed modules. In practice the
  frontend container crash-looped with 'sh: next: not found'. All three removed;
  the backend had the same './backend:/app' masking.
- backend/Dockerfile: dropped apt entirely. Installing libssl-dev failed outright
  ('libssl3t64 u2' pinned by the index vs u3 offered). No compiler is needed — every
  dependency ships a manylinux wheel. Now python:3.12-slim matching the repo target,
  plus the missing HEALTHCHECK (DS-0026) that uses the bundled interpreter.
- Added .dockerignore to both contexts: without them the 539MB node_modules, the
  .git dir and any local .env would be copied into the image.
- compose ports bound to 127.0.0.1 instead of 0.0.0.0.
- Removed the 'nethermind' username/naming from the built images in favour of netcore.

Verified with real containers:
- docker compose up -d -> backend healthy and frontend healthy within 10s
- curl 127.0.0.1:8000/health 200; curl 127.0.0.1:3000/ 200
- docker exec nethermind-backend id  -> uid=1000(netcore)
- docker exec nethermind-frontend id -> uid=1000(node)
- trivy config on both Dockerfiles -> 0 findings (was 1 HIGH + 2 LOW)
- pytest 11 passed; gitleaks clean; compose down -v afterwards, 0 containers left
@OneByJorah
OneByJorah merged commit 8a769c4 into master Oct 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant