build: s6 tree partition + Dockerfile refactor (1/3) - #1
Merged
Conversation
Kiro spec at .kiro/specs/build-devx-overhaul/ defines 11 user stories, 51 acceptance criteria, a 20-stage Dockerfile structure, compose topology, GHCR cache strategy, s6 contract change, 12 correctness properties, risk register, and a 60-task dependency-sequenced implementation checklist. Plan at docs/superpowers/plans/2026-05-16-build-devx-overhaul.md sequences 9 phases (A-I) across 3 PR boundaries, defines 8 verification gates, and flags 10 execution-time risks. This commit lands the contract; subsequent commits on this branch implement Phases A and B (s6 tree partition + Dockerfile refactor) per the spec.
Move the sdrpp-server service definition, its services/contents.d/
registration, and its user/contents.d/ registration out of the canonical
docker/overlay/s6-overlay/ tree and into a sibling
docker/overlay/s6-overlay-sdrpp/ tree. Delete the canonical tree's
wavekit-api -> sdrpp-server hard dep; wavekit-api now depends only on
wavekit-init.
Phase A of build/devx-overhaul; sets up the Phase B Dockerfile to COPY
the canonical tree into final-base (no sdrpp residue) and additively COPY
the sdrpp tree into the final stage only. See
.kiro/specs/build-devx-overhaul/{requirements,design,tasks}.md and
docs/superpowers/plans/2026-05-16-build-devx-overhaul.md.
Replaces the 737-line monolithic Dockerfile with 20 stages per the Build & DX
Overhaul spec (Phase B). Each decoder builds on a thin `base-build` stage and
installs only its own apt deps; upstream sources are pinned to SHAs at the
top of the file. The 4 final targets (`final`, `final-core`, `final-sdrpp`,
`final-demod`) build cleanly via `docker buildx build --target <t> --load`.
Stage inventory:
- base-build / runtime-base (toolchain + runtime libs)
- 12 decoder builders: sdrpp, dsd-fme, multimon-ng, rtl433, acarsdec,
ais-catcher, direwolf, dumpvdl2, readsb, soapy-rtltcp, csdr, lora
- node-build (single pnpm install)
- 5 finals: final-base (common ancestor), final (+SDR++), final-core
(no additions), final-sdrpp (SDR++ only), final-demod (interactive)
Key changes:
- Pinned ARG <NAME>_REF for every upstream (initial values captured via
`git ls-remote`; bumps go through PR review). Re-uses lora-build's
existing pinned-checkout pattern across all decoders.
- `runtime-base` selects s6-overlay via TARGETARCH (was BUILDPLATFORM —
wrong for cross-arch builds). Drops the /var/lib/apt cache mount,
dedupes libsox-fmt-all, removes unused tini.
- `final-base` consolidates everything `final` and `final-core` shared.
`final-core` is a one-line stage; the mode partition is now achieved
by additive COPY from `docker/overlay/s6-overlay-sdrpp/` (Phase A) into
`final`, not by `rm -rf` post-hoc (Req 3.10).
- Hardcoded `COPY /usr/lib/x86_64-linux-gnu/libncurses*` deleted — the
runtime `libncurses6` apt package satisfies dsd-fme's linkage on
both amd64 and arm64 (Req 3.13).
- Old `FROM final AS default` stage removed; bake-driven default group
in Phase D supersedes it.
Notes / mitigations:
- R3 (node-build): a pure-isolated single install couldn't survive both
(a) the pnpm store cache mount dropping the global virtual store path
after the layer commits, and (b) `cli/node_modules` and
`packages/*/node_modules` leaking into the build context from the host
(.dockerignore only excludes root-level `node_modules`). Per the Phase
B brief, set `node-linker hoisted` BEFORE the single install. Project
.npmrc default (`isolated`) is unchanged so `pnpm dev` is unaffected.
Follow-up: fix .dockerignore to exclude `**/node_modules` so the
override can be lifted (Req 3.6).
- SDR++ install paths corrected to `/usr/bin/sdrpp` and `/usr/lib/sdrpp/`
(current upstream installs there, not `/usr/local/`; the old COPY
silently no-op'd). SDR++ runtime libs (libglfw3, libvolk2.5, libglvnd0,
libopengl0) added only to `final` and `final-sdrpp` so `final-core`
stays slim.
- `final-sdrpp` reconstructed: the previous `COPY .../s6-rc.d/base ...`
referenced a directory renamed to `wavekit-init/` in commit 7d18546.
Now uses per-file COPYs of the canonical structural pieces
(services/type, user/type, user/contents.d/services) plus the
sdrpp-server service from the sibling overlay.
- dsd-fme verification widened from `--version` to `command -v` because
the pinned HEAD no longer accepts `--version`. Binary presence on PATH
is the relevant smoke contract.
Gate B verification (PASS):
- final-core has no sdrpp residue (Property 8)
- wavekit-api dependencies: only `wavekit-init` (Property 9)
- final has the sdrpp-server tree (additive overlay landed)
- `node -e "require('fastify'); ..."` returns OK in final-core
- 15 top-of-file ARGs (1 S6_OVERLAY_VERSION + 14 decoder refs)
- touched-src warm build keeps every decoder *-build stage CACHED
Builds all 4 targets via buildx --load successfully.
Refs: .kiro/specs/build-devx-overhaul/{requirements,design,tasks}.md
docs/superpowers/plans/2026-05-16-build-devx-overhaul.md
…q 3.6 Phase B's node-build originally needed a `pnpm config set node-linker hoisted` reconfigure to work around two independent issues: 1. .dockerignore's bare `node_modules` pattern only matches the context-root directory, so cli/node_modules and packages/*/node_modules entered the build context and clobbered pnpm's fresh symlinks during COPY . . 2. .npmrc's `enable-global-virtual-store=true` puts the virtual store at ~/.local/share/pnpm/store/v10/links/, which collides with this stage's BuildKit cache mount target — symlinks dangle once the cache mount unmounts at layer commit. Fixing both root causes restores Requirement 3.6 strictly (.npmrc `node-linker=isolated` is the single source of truth for package layout): - .dockerignore: `node_modules` -> `**/node_modules` - Dockerfile: pass `--config.enable-global-virtual-store=false` to the install invocation (and the matching prune step), dropping the prior `pnpm config set node-linker hoisted` reconfigure entirely. The override is a per-invocation flag; .npmrc remains untouched so `pnpm dev` outside Docker uses the project default unchanged.
This was referenced May 16, 2026
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First of three stacked PRs implementing the build & DX overhaul spec at
.kiro/specs/build-devx-overhaul/. This PR lands Phases A + B: the s6 service-tree partition and the top-to-bottom Dockerfile refactor.refactor(s6): partition sdrpp-server into separate overlay tree) — moves the sdrpp-server service definition, itsservices/contents.d/anduser/contents.d/registrations out of the canonicaldocker/overlay/s6-overlay/tree into a siblingdocker/overlay/s6-overlay-sdrpp/tree. Also deletes the canonical tree'swavekit-api → sdrpp-serverhard dep (loosened per design §5; SDR-source reachability is now solely SourceManager's exponential-backoff contract). All 6 file moves recorded asR100(git history preserved).base-build, 12 decoder*-buildstages eachFROM base-buildwith own apt deps, single-installnode-build, thenfinal-base(shared byfinal/final-core) plusfinal-sdrppandfinal-demod. All 14 upstream decoder/lib refs pinned viaARG <NAME>_REF=<sha>at top of file; checkout pattern isgit fetch --depth 1 origin "${REF}" && git checkout --detach FETCH_HEAD.final-coreis now a one-lineLABEL mode=corestage with zerorm -rfpost-hoc surgery on the s6 tree.The third commit on this PR (
build(docker): exclude **/node_modules from build context; restore Req 3.6) fixes a.dockerignoredefect that previously required apnpm config set node-linker hoistedoverride innode-buildto work around two issues:.dockerignore's barenode_modulespattern only matched the context-root directory, socli/node_modulesandpackages/*/node_modulesentered the build context and clobbered pnpm's fresh symlinks duringCOPY . ...npmrc'senable-global-virtual-store=trueput the virtual store at~/.local/share/pnpm/store/v10/links/which collided with this stage's BuildKit cache mount target.Fixing both root causes (
**/node_modulesin dockerignore;--config.enable-global-virtual-store=falseon the install) lifted the hoisted-linker override entirely..npmrc'snode-linker=isolatedis now the single source of truth (Req 3.6 satisfied strictly).Properties verified locally (Non-negotiables 7, 8, 9)
docker run --rm wavekit:dev-core find /etc/s6-overlay -iname '*sdrpp*'→ empty.ls /usr/local/bin /usr/bin | grep -i sdrpp→ empty.docker run --rm wavekit:dev-core ls /etc/s6-overlay/s6-rc.d/wavekit-api/dependencies.d/→ exactlywavekit-init.docker run --rm wavekit:dev find /etc/s6-overlay -name sdrpp-server -type d→ exactly one match.final,final-core,final-sdrpp,final-demod) build clean viadocker buildx build --target <t> --load ..dsd-fmencurses linkage on amd64 resolves via Debian'slibncurses6package (the hardcodedCOPY --from=dsd-fme-build /usr/lib/x86_64-linux-gnu/libncurses*line was removed entirely per Req 3.13's preferred path).R2 / R3 notes
ldd /usr/local/bin/dsd-fmeon amd64 resolves to the runtime-baselibncurses6. Debian'slibncurses6is multi-arch so the same install pattern works on arm64; full arm64 build was started but not completed end-to-end (QEMU wall-cost). If arm64 ever shows a missing symbol the fix is aTARGETARCH-conditional COPY.65bc16bbody for the full diagnostic chain. Final state: zeropnpm config setcalls in the Dockerfile; isolated linker honored from.npmrc.Test plan
lint-typecheck-testjob green (Node 22 + Corepack)docker-buildjob green onci-coretarget (multi-arch; cache-from only on PR — first run will be cold, ~20-30 min, subsequent ~2-5 min once cache seeded)docker/overlay/s6-overlay/is sdrpp-free anddocker/overlay/s6-overlay-sdrpp/contains the 6 expected filesfinal-corehas norm -rftargeting/etc/s6-overlay/(mode partition achieved by additive COPY only)Follow-ons (separate PRs)
Spec
.kiro/specs/build-devx-overhaul/requirements.md— 11 user stories, 51 acceptance criteria.kiro/specs/build-devx-overhaul/design.md— 14 anti-patterns retired, 20-stage Dockerfile structure, s6 contract change, 12 correctness properties.kiro/specs/build-devx-overhaul/tasks.md— 60-task implementation checklist (A.1–B.2.12 checked off in this PR)