bundle.lock.json
bundle.lock.json pins resolved .deb package versions and SHA256 hashes.
It is committed to the repo, generated on first build, checked on subsequent
builds, and rewritten with the current resolution.
Why it exists
Without a lockfile, "build the bundle from this spec" could resolve a
different .deb dependency set any time an Ubuntu mirror refreshed its
Packages.gz. That is the opposite of what an offline installer needs.
With a lockfile:
- Two builds of the same spec + lockfile make
.debchanges explicit. - Upstream
.debdrift is reported as a warning during build. - Auditors can review the lockfile diff across releases to see exactly what changed in the resolved package set.
When it changes
Intentionally:
- You bumped a version in
bundle.yamland regenerated. - You added or removed a
.debentry. - An upstream pin in a dependency moved (a transitive
.debdep changed version).
Unintentionally:
- An Ubuntu mirror re-synced and a package changed SHA256 (rare but real — usually indicates an Ubuntu security refresh).
The build loop
On build:
flowchart TD
start([build-bundle])
exists{"bundle.lock.json<br/>exists?"}
resolve["Resolve + fetch<br/>.deb packages"]
write["Write bundle.lock.json"]
verify["Compare resolved .debs<br/>against lockfile"]
drift{"Drift?"}
warn(["log warning"])
assemble["Assemble bundle.tar.zst"]
start --> exists
exists -->|no| resolve --> write --> assemble
exists -->|yes| verify --> drift
drift -->|yes| warn --> write --> assemble
drift -->|no| assemble
First build of a new spec writes the lock. Every build after that compares the new resolution with the existing lock and rewrites the file.
Opting into a regeneration
Two options:
# Option A: delete and let the next build re-resolve.
rm specs/bundle.lock.json
make bundle
# Option B (future): an explicit flag.
./dist/build-bundle --spec specs/bundle.yaml --output dist/bundle.tar.zst --regenerate-lock
0.1.x ships option A. --regenerate-lock is a planned convenience flag.
What's in it
The lockfile is JSON keyed by suite/architecture, then package name:
{
"schema_version": 1,
"debs": {
"noble/amd64": {
"ansible": {
"version": "2.14.16-1",
"sha256": "..."
}
}
}
}
Exact field names are defined by the Go types in internal/builder/lockfile.go.
Reviewing a lockfile diff
When you commit a PR that changes bundle.lock.json, reviewers should
look for:
- Version bumps you expected — the ones driven by the spec change.
- Version bumps you didn't expect — a transitive
.debmoving is usually fine; a top-level one suggests the spec change had a side effect. - Hash-only changes — same name, same version, new hash. Almost always an upstream re-upload. Investigate.
The lockfile diff is the changelog for what's inside the bundle. Treat PRs
that change it with the same scrutiny as go.sum changes.