Repository navigation
v10: rewritten in Rust
The server is now a single static Rust binary. The image is ~33 MB instead of ~243 MB, and idle memory drops from ~280 MB to a few MB. Against v9.9 on SQLite + filesystem: uploads are 6.7× faster, first downloads 22×, lookups 17×, and small save+restore cycles go from 4/s to over 1000/s.
The Twirp cache API, block uploads, downloads, the management REST API, /health, /metrics and all environment variables stay compatible. @actions/cache and BuildKit need no change.
⚠️ Before you upgrade
- The database migration is one-way. On first start, v10 takes over the v9 schema.
- A v9.10 database keeps its data.
- A database older than v9.10 is dropped and its storage is cleared. Upgrade to v9.10 first if you want to keep entries.
- After the migration, v9 can't run against the database. Back it up if you may need to roll back. Cache data can be rebuilt, so the worst case is a round of cache misses.
- Uploads need the Upload Capability (
?sig=) in the URL (#308). Uploads already in progress during the upgrade, and uploads created by v9 replicas while the rollout runs, are refused with 403. Those saves fail once, and the next run saves again. - Direct downloads with
SKIP_TOKEN_VALIDATION=trueno longer start (#309). Turn token validation back on, or setUNSAFE_ALLOW_DIRECT_DOWNLOADS_WITHOUT_TOKEN_VALIDATION=true(local testing only).
💥 Breaking changes
- Removed:
/management-api/_rpcand the TypeScript SDK- the
-cagedimage tags BENCHMARK,NITRO_PORT,NITRO_HOSTSTORAGE_S3_MAX_SOCKETS,STORAGE_S3_CONTROL_MAX_SOCKETS,STORAGE_S3_CONTROL_QUEUE_TIMEOUT_MSnodejs_*metrics;process_*metrics differ slightly
- Auth:
- Verified tokens must carry
exp. acpermissions are read as bit flags: a write-only scope no longer grants reads.- A malformed
acclaim gets 401.
- Verified tokens must carry
- Config:
- Invalid config exits with code 2 and a clear message.
- An empty variable counts as unset.
- Every variable is also a CLI flag (
--help), and a flag overrides its variable.
- Cleanup: every job runs once at startup, then once per period, no longer on local wall-clock boundaries.
- Eager Merge on S3/GCS: Parts now stream through the server instead of being composed inside the bucket.
- Object storage: Parts upload through presigned URLs, so the credentials must be able to sign. Without them, uploads fall back to multipart.
- SQLite: runs in WAL mode with
synchronous=normal. - Twirp:
- Error bodies are
{"code","msg"}. - JSON responses always include empty fields.
FinalizeCacheEntryUploadaccepts only POST.
- Error bodies are
- Range: suffix ranges (
bytes=-N) get 206 instead of the whole object. - Management API:
DELETE /management-api/storage-locations/{id}answers 409 while the location is being downloaded.
✨ New
DIRECT_DOWNLOAD_URL_TTL_SECONDS(1 to 3600, default 600) sets how long signed direct-download URLs stay valid.ACTIONS_TOKEN_AUDIENCErequires tokens to carry one of the listed audiences.
🐛 Fixes
DELETE /management-api/cache-entriesnow respects therepoIdfilter.- Deleting a storage location no longer removes data before the database commit or while a download is reading it.
- A negative
CACHE_CLEANUP_OLDER_THAN_DAYSstops startup. It used to delete every entry daily. - The debug log no longer prints decoded token claims.
⎈ Helm chart 2.0.0
- Removed values:
image.variantconfig.storage.s3.maxSockets,controlMaxSockets,controlQueueTimeoutMs
- New value:
config.directDownloadUrlTtlSeconds. - The image has no shell. Replace
execpreStop hooks such assleep 30withpreStop: {sleep: {seconds: 30}}(Kubernetes 1.30+).