ADR-0003 — Filesystem content-addressed blob store behind a driver boundary
Status: accepted for V1, 2026-11-09. Deviation from the brief, which preferred S3-compatible storage.
Context
The registry runs on one node of the owner's cluster. There is no S3-compatible service there, and adding one (MinIO) would be a second stateful system to secure, back up and monitor for a store that holds small archives (20 MiB cap).
Decision
- Release archives live under
DATA_DIR/blobs/sha256/<first two hex>/<sha256>.tar.gz. The key is derived from content, so identical archives share one blob and a key can never be made to point at different bytes. - Uploads are streamed to
DATA_DIR/staging/<upload id>.partwith a hard byte cap while hashing. After validation the file is promoted with an atomicrenameand set read-only (mode 0440). DATA_DIRis outside the repository and outside the web root. Blobs are served only through the download route, which setsContent-Type: application/gzip,Content-Disposition: attachmentandX-Content-Type-Options: nosniff.- Object keys and upload ids are validated against strict patterns before any path is built.
- All access goes through
apps/web/src/server/storage.ts(receiveToStaging,promoteStaging,openBlob,readBlob,discardStaging). That module is the driver boundary.
Consequences
- No storage credentials exist, so none can leak.
- Durability equals the node's disk plus the backup job. Loss of the node between backups loses blobs published since the last backup.
- Migration path to S3: implement the same five functions against an S3 client, copy
blobs/preserving keys (they are already content addresses), switch the driver. Database rows store the key, not a filesystem path, so no data migration is needed.