envd: a Python-defined build file for AI/ML containers, with BuildKit caching
🏕️ Reproducible development environment for humans and agents
At a glance
- What is it?
- TensorChord's envd replaces the Dockerfile with a build.envd Python function and runs it through BuildKit, so pip and apt layers can be reused across builds. It is aimed at ML engineers who keep rebuilding CUDA, conda and Jupyter stacks, and it is a poorer fit for teams that need plain, portable image definitions.
- Who is it for?
- Adopt envd if your team rebuilds the same Python, conda and CUDA stacks repeatedly and you want pip and apt caches shared between builds, plus one file that runs both on a laptop and on a Kubernetes cluster. Do not adopt it if your images must be reproducible from a Dockerfile that any CI system can build without an envd binary, or if you need the full Dockerfile instruction set.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 53 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem envd targets: Dockerfiles that break on ML dependencies
The README frames the pain directly: Python, CUDA, BASH scripts and Dockerfiles are described as constantly breaking. That is a familiar failure pattern for anyone maintaining an ML image. A Dockerfile is a linear script of shell commands, so a conda install, a CUDA toolkit version and a Jupyter configuration end up as opaque RUN lines with no structure a tool can reason about. When one of them changes, the layer cache invalidates and the build re-downloads everything below it.
envd's answer is to move the definition into a Python function. Instead of RUN lines, you call typed helpers such as install.conda(), install.python(), install.python_packages(name=["numpy"]), shell("fish") and config.jupyter(). The argument is that these calls carry semantic meaning that a string of shell text does not, and a BuildKit frontend can use that meaning to cache more precisely. The audience is narrow but real: ML engineers and platform teams who provision the same environment for several people, and who care more about build time and cross-machine consistency than about hand-tuning a Dockerfile.
How build.envd turns into an OCI image
The mechanism visible in the material has three parts. First, a build.envd file defines a build() function. The README's example calls base(dev=True), then install helpers, then shell() and config helpers. Second, the envd CLI reads that function and hands it to BuildKit, which the README names as the build backend. Third, the output is a container image that the README states is compatible with the OCI image specification, so it can be pushed to a registry such as Harbor or Docker Hub and pulled by anything that speaks OCI.
The interesting part is where caching happens. The README claims envd supports pip index caches and apt caches through BuildKit, and contrasts this with Dockerfile v1, which it says cannot take advantage of PyPI caching for faster installation. Whether that contrast holds for a given Docker version and frontend is something you would need to check against your own toolchain, but the design intent is clear: package manager caches are treated as first-class build inputs rather than as something you bolt on with a manual mount.
There is also a second axis of configuration, the context. The README shows envd context use local followed by envd up, then envd context use cluster followed by the same envd up. The build definition does not change between the two; only the context does. That is the architectural claim worth noting: the same build.envd is meant to produce a working environment on a laptop or inside a Kubernetes cluster.
Getting it running: Docker 20.10.0, build.envd, envd up
The stated requirement is Docker 20.10.0 or above. The README does not give an install command for the envd CLI itself in the material available here, so treat the binary installation step as something to confirm from the project documentation rather than something you can copy from this article.
Once the CLI is present, the workflow is a file plus a command. Create build.envd with a build() function. The README's minimal example is:
def build(): base(dev=True) install.conda() install.python() install.python_packages(name=["numpy",]) shell("fish") config.jupyter()
Then run envd up. To switch where that environment runs, use envd context use local or envd context use cluster before envd up. The cluster path is documented separately at envd.tensorchord.ai under the teams section, which the README links to; the details of that setup are not in the material here.
Reuse across projects goes through include(). The README shows envdlib = include("https://github.com/tensorchord/envdlib") at the top of the file, then a call such as envdlib.tensorboard(host_port=8888) inside build(). The included function is ordinary Python: the README's expanded example shows tensorboard() calling install.python_packages(["tensorboard"]), runtime.mount(host_path=..., envd_path=...), runtime.daemon(commands=[[...]]) and runtime.expose(envd_port=..., host_port=..., service="tensorboard"). That is the reuse story: a Git repository of Python functions, imported by URL, rather than a base image you inherit from.
Where envd gets awkward: the DSL boundary and the Docker requirement
The helper vocabulary is the constraint. install.conda(), install.python(), install.python_packages(), shell() and config.jupyter() cover common ML setups, and runtime.mount, runtime.daemon and runtime.expose cover ports and mounts. Anything outside that vocabulary has to be expressed as a shell command, which puts you back in Dockerfile territory but inside a Python function. The README's own example of a custom helper, the envdlib tensorboard function, ends up calling install.python_packages and then a raw command list for the daemon. So the abstraction is a convenience layer over shell, not a replacement for it, and the moment your setup diverges from the helpers you are maintaining both a Python file and the shell strings inside it.
The second constraint is Docker. The requirement is Docker 20.10.0 or above, and BuildKit is the build backend. A team that has standardized on a rootless builder, on Podman, or on a hosted build service that expects a Dockerfile will find that envd does not slot in without changing that part of the pipeline. The README says nothing about how the cluster context provisions BuildKit, which is the piece I would want documented before rolling this out to a shared Kubernetes cluster.
The third issue is portability of the definition. An OCI image is portable; a build.envd file is portable only to machines that have the envd CLI. If a downstream consumer wants to rebuild your image from source, they need the tool, not just Docker. That is a real cost when images outlive the enthusiasm of the team that created them.
How envd differs from a plain Dockerfile plus BuildKit
The obvious alternative is a Dockerfile built with docker buildx and the BuildKit backend, which is what most ML teams already have. The difference is not the builder, since envd uses BuildKit too. The difference is the frontend and the unit of reuse.
With a Dockerfile, cache reuse for pip and apt is something you configure: you mount a cache directory, you order your layers so that the dependency manifest is copied before the source, and you accept that a change to one RUN line invalidates everything after it. The README's claim is that envd handles pip index caches and apt caches through BuildKit without that manual ordering, and that Dockerfile v1 cannot use PyPI caching in the same way. If that holds in your environment, it is a genuine difference in day-to-day build times for a dependency-heavy ML image.
The second difference is reuse. A Dockerfile reuses by inheriting a base image or by copy-pasting instructions, which the README calls out explicitly. envd reuses by importing a Git repository of Python functions with include(). That is a different granularity: you share a tensorboard setup or a mount configuration, not a whole image layer stack. Whether that is better depends on whether your shared pieces are small configuration fragments or large prebuilt bases. For teams whose shared piece is a 6 GB CUDA base image, inheritance is the more natural fit and envd's function-level reuse does not replace it.
Maintenance, releases and the Apache-2.0 licence
The repository is not archived, the primary language is Go, and the most recent release listed is v1.3.4 from February 2026, preceded by v1.3.3 in January 2026 and v1.3.2 in November 2025. That cadence, roughly a release every one to two months across the listed versions, is the kind of signal that matters more than any popularity metric. A CLI that builds your images is on the critical path of your team's setup, so you want to know that it is being cut regularly and that the release notes describe what changed. The material here does not include those notes, so the upgrade cost per release is not something I can state.
The licence is Apache-2.0. That is a permissive licence, which generally means you can use, modify and redistribute the tool, including in commercial settings, provided you keep the licence and notices intact. It does not come with a warranty, and it says nothing about the licence of the images you build with envd or of the repositories you include() from. If you import a helper from a third-party Git repository, that repository's licence governs that code, not Apache-2.0. I am not a lawyer and this is not legal advice; if your organisation has a policy on bundled build tooling, route the licence text through it.
The practical maintenance question is different. Because envd wraps BuildKit and Docker, an upgrade to envd is usually also an upgrade to how your builds are executed. Pin the envd version in whatever installs it on your build machines, and read the release notes for v1.3.x before moving a shared cluster context forward.
Who should pick envd up, and what to check first
envd is a reasonable choice for a small platform team that provisions ML environments for several engineers, rebuilds them often, and wants the same definition to work on a laptop and on a Kubernetes cluster. The include() mechanism is the part I would evaluate hardest, because a shared repository of build functions is where the value compounds and where the coupling also compounds.
It is the wrong tool if your build pipeline is standardized on Dockerfiles consumed by a hosted builder, if you need the full instruction set and do not want to fall back to shell strings inside Python, or if the people who will rebuild your images downstream have Docker but not envd.
Before adopting, verify the Docker version on every machine that will run envd up, and confirm that the context you point at actually has BuildKit configured for cache reuse rather than running a default builder. The README's caching claims are the core of the pitch, and they are the claim most dependent on your infrastructure. Start by building one real environment with build.envd and running it twice with a dependency change in between; if the second build does not reuse the pip or apt cache, the main reason to choose envd over a Dockerfile plus buildx has not materialised for you.
Editorial conclusion
Adopt envd if your team rebuilds the same Python, conda and CUDA stacks repeatedly and you want pip and apt caches shared between builds, plus one file that runs both on a laptop and on a Kubernetes cluster. Do not adopt it if your images must be reproducible from a Dockerfile that any CI system can build without an envd binary, or if you need the full Dockerfile instruction set. Before committing, verify three things: that your Docker is 20.10.0 or above, that the buildkit and buildkitd setup your context points at actually caches the package managers you use, and that the packages you need are reachable through the install and config helpers in build.envd rather than only through a raw shell command.
Community notes