You already lock your npm packages, Go modules, and Rust crates. But every FROM alpine:latest, every git checkout main is still an unlocked, mutable dependency. A supply chain attack can swap what a tag points to overnight. .dagger/lock pins container image tags, git branches, and HTTP fetches to their resolved values, committed to version control like any other lockfile. Your pipeline either reproduces exactly or fails clearly.
…
Run --lock=live once to record what every lookup resolves to. Use --lock=pinned in CI to reuse pinned entries and resolve any new lookups. Use --lock=frozen for hardened pipelines: anything not already locked is a hard error, not a silent substitution.
dagger --lock=live call build # record resolved values
dagger --lock=pinned call build # reuse pins, resolve new lookups
dagger --lock=frozen call build # lockfile only, fail on miss
dagger lock update # refresh all locked entries
The largest engine change since the project began. Dagger is replacing BuildKit's solver with its own native engine, unlocking robust remote caching, cache observability, and more flexible module execution.
…
Every container operation delegated to BuildKit is being reimplemented natively in Dagger's DAG engine. Internally called 'Project Theseus' — after the Ship of Theseus — the team has spent over a year replacing BuildKit's internals one plank at a time, shipping continuously at every step. The key was a transitional facade layer called DagOp: each operation (git, HTTP, container exec, file and directory operations, exports) was individually lifted out of BuildKit and into Dagger's own engine. Over 50 PRs later, the facade has served its purpose and is being removed. In its place: a caching engine built on e-graphs — a data structure from compiler optimization research that tracks equivalences between operations, maximizing cache hit rates beyond what any conventional build cache can achieve. The new cache has a single, understandable code path for key calculation and lookup. A long-standing caching bug was fixed without anyone investigating it — the scenario that confused BuildKit's two intertwined caching systems is simply handled correctly by a system simple enough to reason about.
Generated files that fall out of sync with their source are now caught automatically by dagger check. Any function marked as a generator is re-run during checks — if the output would change, the check fails. No setup required. Use --no-generate to skip generators.
Dagger now understands tests. The interactive TUI has a tests view: press a key to drill into the test tree and see status, durations, and logs. Plain and dots modes print a short test summary instead. It works with dagger check out of the box, with nothing to set up.
--x-release=<ref> tells your Dagger CLI to download another release and hand the command off to it, with nothing to install. Pass head to run the latest dev build, or a version like v0.20.8 to run that exact release.
…
As an env variable: export DAGGER_X_RELEASE=<version>
dagger generate no longer spikes to 30GB+ RSS on large module graphs. Long withDirectory chains no longer re-materialize at every step, cutting wall time by 22–29% on real workloads.
When a bound service exits, dependent operations are cancelled and the failure is surfaced instead of hanging. Enum arguments now show their default values in --help. Git checkouts no longer get epoch-zero timestamps that confuse Maven and similar tools. Remote git tree fetches after a cache prune no longer fail. Python SDK type aliases and union annotations resolve correctly. The synthetic with field no longer clutters dagger functions output. Nested client session races and toolchain cache identity bugs are fixed.