Files
pwap/backend/Dockerfile
T
pezkuwichain 80d273ff11 ci/cd(backend): offline test suite + Dockerize + audit-grade deploy (b + c)
Closes the "backend has zero CI + manual SSH deploy" audit finding for the indexer.

Part B — CI-runnable tests (the integration-tests/*.live.test.js need a live chain and
are excluded, not stubbed): extract injectable cores (indexer.js: DB+HTTP+decode, no
@pezkuwi import; council.js: createApp factory for the council/KYC routes). New node:test
suite (19/19, fully offline) — in-memory sqlite, dependency-injected chain stub, an
in-memory fake-supabase, and REAL @pezkuwi keyring signatures proving 401 bad-sig /
400 msg-mismatch / 200 valid / 409 dup / 403 non-member / threshold auto-execute. Backend
job now runs npm ci + npm test and is in ci-gate (failing tests block merge). Fixed a
latent bug: block indexing now awaits each insert (was fire-and-forget forEach). Added
runtime deps server.js imported but were missing from package.json (@supabase/supabase-js,
pino, pino-http) — server.js could not have `npm ci`-run before.

Part C — Dockerfile (non-root, HEALTHCHECK /health, sqlite state on a /data VOLUME kept
out of the image) + backend deploy pipeline mirroring the web one: build+push to GHCR,
cosign keyless sign + verify, ssh deploy with the DB volume preserved across deploys,
/health poll, auto-rollback to previous SHA, same Telegram CEO approval gate, main/tags
only, never fork PRs. New secrets documented in backend/DEPLOY.md (BACKEND_VPS_HOST/USER/
SSH_KEY). DB_PATH env (default ./transactions.db) lets prod point at the volume.

Note for owner: if server.js (council/KYC bootstrap) is legacy/unused, its newly-added
deps can be dropped instead.
2026-07-25 11:01:47 -07:00

48 lines
1.8 KiB
Docker

# pwap indexer service — runnable Node container (not a static-dist image).
#
# Two stages on the SAME debian base so the natively-compiled sqlite3 binding
# built in stage 1 is ABI-compatible with the runtime in stage 2 (do NOT mix
# alpine/musl here — sqlite3 would fail to load).
#
# STATEFUL DATA: the indexer's sqlite file is the service's memory. It is kept
# OUT of the image, under /data, which MUST be a mounted volume. A deploy
# replaces the container/image but never the volume — see backend/DEPLOY.md.
# ─── Stage 1: install production deps (compiles sqlite3) ────────
FROM node:20-bookworm AS builder
WORKDIR /app
COPY package.json package-lock.json ./
# --omit=dev: the test-only devDeps (supertest, @pezkuwi/keyring) never ship.
RUN npm ci --omit=dev
# ─── Stage 2: runtime ──────────────────────────────────────────
FROM node:20-bookworm-slim
ENV NODE_ENV=production
ENV PORT=3001
# DB on the volume, never in the image layer.
ENV DB_PATH=/data/transactions.db
WORKDIR /app
# curl only, for the HEALTHCHECK probe.
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src
# /data holds the stateful sqlite DB — declared as a volume and owned by the
# non-root runtime user so the container can write to the mount.
RUN mkdir -p /data && chown -R node:node /app /data
VOLUME ["/data"]
USER node
EXPOSE 3001
# Probe the in-container /health endpoint added to the indexer API.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD sh -c 'curl -fsS "http://localhost:${PORT:-3001}/health" || exit 1'
CMD ["node", "src/index.js"]