Skip to content

P1: Harden Docker image — tini PID 1, HEALTHCHECK, pinned digest (#171) - #189

Open
dkijania wants to merge 1 commit into
mainfrom
feat/container-hardening
Open

P1: Harden Docker image — tini PID 1, HEALTHCHECK, pinned digest (#171)#189
dkijania wants to merge 1 commit into
mainfrom
feat/container-hardening

Conversation

@dkijania

Copy link
Copy Markdown
Contributor

What & why

Part of the production-readiness epic (#163). Closes #171.

The runtime container ran npm start as PID 1, which forwards signals poorly — so SIGTERM didn't cleanly reach node and the new graceful shutdown (#170) couldn't run. It also had no container healthcheck and used a floating node:20-alpine tag.

Changes

  • tini as PID 1: run node build/src/index.js directly under tini, so node receives SIGTERM (graceful shutdown) and zombies are reaped.
  • HEALTHCHECK: hits the built-in /healthcheck endpoint (honours $PORT, defaults to 8080).
  • Pinned base image by digest on both stages (node:20-alpine@sha256:fb4cd12c…) for reproducible, tamper-evident builds — bump via Dependabot (P1: Supply chain — Dependabot, npm audit gate, image scan, SBOM #175).
  • .dockerignore to keep the build context lean.

Verification (built and run locally)

  • Image builds cleanly.
  • Runs as non-root (uid=1001 nodeuser).
  • tini 0.19.0 is the init/entrypoint; node v20.20.2 present.
  • Default CMD runs node build/src/index.js under tini — reaches buildContext (errors only on the expected missing PG_CONN), confirming the entrypoint chain.

No app code changed; lint and prettier --debug-check . pass.

🤖 Generated with Claude Code

The runtime container ran `npm start` as PID 1, which forwards signals poorly —
so SIGTERM didn't cleanly reach node and the new graceful shutdown couldn't run.
It also had no container healthcheck and used a floating `node:20-alpine` tag.

- Run `node build/src/index.js` directly under `tini` as PID 1, so the process
  receives SIGTERM and shuts down gracefully (and zombies are reaped).
- Add a `HEALTHCHECK` hitting the built-in `/healthcheck` endpoint (honours
  $PORT).
- Pin both stages to the `node:20-alpine` image digest for reproducible,
  tamper-evident builds (bump via Dependabot — #175).
- Add a `.dockerignore` to keep the build context lean.

Verified locally: the image builds; runs as non-root (uid 1001); tini 0.19.0 is
PID 1; `node build/src/index.js` is the entrypoint chain (reaches buildContext).

Closes #171.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSuak9smCHbp4N17xjjLF6
@dkijania dkijania added production-readiness Work toward making the API production-ready / publicly available P1 Strongly recommended before GA labels Jun 28, 2026
dkijania added a commit that referenced this pull request Jul 17, 2026
Pins both Dockerfile stages to node:22-alpine@sha256:16e22a55… (v22.23.1,
verified by pull and build). Without this, moving to a floating 22 tag
would silently drop the digest pin #189 adds, reverting its
reproducible/tamper-evident build hardening depending on merge order.
Once #192 lands, Dependabot's docker ecosystem keeps the digest fresh.

Bumps @types/node to ^22 so the types match the runtime rather than
staying on 20.

Adds tests pinning the GraphQL-over-HTTP behaviour the mina-explorer
depends on: validation errors return 200 (its client throws on non-2xx
before reading the body) and carry the literal "Cannot query field" (its
fallbacks key on that string). Both were verified by hand across the
4 → 5 upgrade and are unchanged, but they rest on an implicit
content-negotiation default — yoga only returns 400 under an Accept
header that client never sends — so a later bump could flip them
unnoticed. Now a standing guard rather than a one-time check.

Addresses review feedback on #194.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 Strongly recommended before GA production-readiness Work toward making the API production-ready / publicly available

Projects

None yet

Development

Successfully merging this pull request may close these issues.

P1: Container hardening — node as PID 1 / tini, HEALTHCHECK, pin digest

1 participant