# tensorchord/envd: a Python-defined build file for AI/ML dev containers

> envd turns a build.envd Python function into an OCI-compatible development container, with BuildKit caching and the same workflow on a laptop or a Kubernetes cluster. The trade-off is a new abstraction layer and a young v1 language.

**tensorchord/envd** — 🏕️ Reproducible development environment for humans and agents

- Repository: https://github.com/tensorchord/envd
- Website: https://envd.tensorchord.ai/
- Stars: 2,236 · Forks: 168
- Language: Go
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/tensorchord-envd

## The problem envd targets: AI/ML environments that break on rebuild

The README opens with the pain it is aimed at: "With everything from Python to CUDA, BASH scripts, and Dockerfiles constantly breaking, it can feel like a nightmare." That is a fair description of the usual workflow. A machine learning project needs a Python version, a set of pip packages, sometimes conda, sometimes CUDA, sometimes a system package such as a compiler. Teams capture that in a Dockerfile, then drift: one person edits the apt block, another pins a different torch build, and the image on the training node no longer matches the image on a laptop.

envd's answer is a single declaration file, build.envd, written in Python, plus one command, envd up. The intended audience is narrow but real: ML engineers and MLOps teams who already write Python and do not want to learn a Dockerfile dialect or a configuration DSL. The README makes that explicit, saying envd lets you provision an environment "without learning a new language or DSL." If your team is comfortable with Dockerfiles and has no reproducibility complaints, the pitch is weaker.

## How envd works: a Python build function compiled to an OCI image

A build.envd file defines a build() function. Inside it you call functions such as base(), install.conda(), install.python(), install.python_packages(), shell(), config.jupyter() and runtime.expose(). The README's example shows exactly this shape:

```python
def build():
    base(dev=True)
    install.conda()
    install.python()
    install.python_packages(name = [
        "numpy",
    ])
    shell("fish")
    config.jupyter()
```

Those calls are not ordinary Python at runtime. The repository depends on go.starlark.net, and the Go CLI in cmd/ and pkg/ evaluates the file to produce build instructions. The actual image build is delegated to BuildKit, which the go.mod pins as github.com/moby/buildkit. 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.

Two consequences follow from that architecture. First, caching is handled by BuildKit rather than by hand-written Dockerfile layers, and the README claims pip index caches and apt caches are reused across builds. Second, the same declaration can run in two places. The README shows envd context use local followed by envd up, then envd context use cluster followed by the same envd up. The context switch is the whole difference; the build file does not change.

## Installing envd and running a first environment

The README lists one hard requirement: Docker 20.10.0 or above. The Python package is published on PyPI as envd, so the install path for most users is pip. The repository also ships a setup.py that builds the Go binary from source when bin/envd is missing, which means a source install needs a Go toolchain and make, since setup.py shells out to make build-release.

```bash
pip install envd
```

After installation, create a directory and a build.envd file. The README's own snippet is the shortest thing that does something useful; it installs conda, Python and numpy, and configures Jupyter.

```python
def build():
    base(dev=True)
    install.conda()
    install.python()
    install.python_packages(name = [
        "numpy",
    ])
    shell("fish")
    config.jupyter()
```

Then run the build and enter the environment. The README's central command is envd up, and the context commands decide whether that lands on your local Docker daemon or on a cluster.

```bash
envd context use local
envd up
```

The first build pulls base layers and resolves packages, so expect it to take minutes rather than seconds. Later builds reuse the BuildKit cache. If you want the same environment on a cluster, the README's sequence is envd context use cluster followed by envd up, with the Kubernetes documentation linked from the project site.

## Reusing environment code with include() and envdlib

The feature that separates envd from a plain Dockerfile is include(). It imports a build function from a Git repository, so a team can keep shared environment fragments in one place instead of copying apt and pip lines between projects. The README example imports https://github.com/tensorchord/envdlib and calls envdlib.tensorboard(host_port=8888).

```python
envdlib = include("https://github.com/tensorchord/envdlib")

def build():
    base(dev=True)
    install.conda()
    install.python()
    envdlib.tensorboard(host_port=8888)
```

The tensorboard function defined in that repository is worth reading because it shows the full surface of the language in about twenty lines: it installs a pip package, mounts a host directory into the container with runtime.mount, starts a daemon with runtime.daemon, and publishes a port with runtime.expose. That combination (install, mount, daemon, expose) is the vocabulary you will use for most services, whether that is TensorBoard, a model server or a notebook.

The trade-off is a dependency on an external repository at build time. If the included Git repository moves or its function signature changes, your build breaks in a way that a pinned Dockerfile would not. The README does not document how include() resolves branches or tags, so pinning behaviour is something to check in the source before you rely on it across a team.

## Where envd gets in the way

envd asks you to describe your environment in a language it controls. When a package needs a step that install.python_packages or install.conda cannot express, you are working around the abstraction rather than with it. The examples directory gives a sense of the intended surface: examples/python-basic, examples/conda, examples/pytorch2, examples/julia-basic, examples/r-basic, examples/llm-inference, examples/resnet-serving-v1. Those are the paths the maintainers exercise. Anything outside them is less travelled.

The second limitation is the v1 language split. The repository keeps examples/envd-lang-v1 alongside the current examples, which tells you the build file syntax has changed at least once and old files may need rewriting. Before adopting envd for a long-lived project, read the v1 example and confirm which syntax the version you install expects. The README does not present a migration guide.

Third, envd is not a general container tool. It builds development and serving environments for AI/ML work. If you need multi-service orchestration with health checks and dependency ordering between a database, a queue and an API, that is what Docker Compose or Kubernetes manifests are for. envd will not replace them; it sits in front of a single environment image.

## envd compared with Docker Compose and Nix

The closest comparison is a hand-written Dockerfile plus Docker Compose. The difference is where the logic lives. In a Dockerfile, package installation is a sequence of shell commands and the cache is invalidated by layer ordering; envd moves that into function calls and lets BuildKit manage the cache, which the README says includes pip index caches and apt caches. envd also produces a container you can attach to with a shell and a Jupyter server from the same file, which a Dockerfile does not do on its own.

Nix takes the opposite approach. It models the environment as a pure derivation and guarantees reproducibility by construction, at the cost of learning the Nix language and accepting a steeper debugging experience. envd keeps Python as the surface language and accepts that reproducibility depends on the packages you name and the base image you choose, not on a formal derivation. If your team already runs Nix and is happy with it, envd offers convenience rather than a capability you lack.

The Kubernetes path is envd's own differentiator. The README states that envd can run on a hybrid platform from local machines to Kubernetes clusters, and that the same envd up works after envd context use cluster. Compose has no equivalent single-command story for that transition, and that is the strongest reason to pick envd over a Compose file if your team trains on a cluster.

## Maintenance, licensing and what an upgrade costs

envd is licensed under Apache-2.0, with the licence headers present in setup.py and the Makefile. That permits commercial use and modification, and it does not impose copyleft obligations on your own code. It says nothing about the licences of the base images and packages you install through envd, which remain your responsibility; the base-images/ directory in the repository is where the project's own base images live.

The repository is not archived, and the last push was on 2026-07-25. The most recent release listed is v1.3.4 from 2026-02-07, following v1.3.3 in January 2026 and v1.3.2 in November 2025. Commits continue between releases, so the release cadence understates activity, but the version numbers move slowly and the changelog is the place to check what changed before upgrading.

Upgrade cost is dominated by the build file language, not by the CLI. Because the language has a v1 and a current form, a major bump can require editing every build.envd in your repositories. The Go module targets Go 1.25.1, so building from source needs a recent toolchain. For most users the practical upgrade path is pip install --upgrade envd, then rebuild one environment and compare it against the previous image before rolling the change out.

## Conclusion

Adopt envd if your team rebuilds CUDA, conda and Python stacks by hand and wants one declaration file plus envd up on both laptops and a cluster. Skip it if your team already has a working Docker Compose or Nix setup and no appetite for a second build language. Before committing, verify that your Docker daemon is 20.10.0 or above, that the v1 language in examples/envd-lang-v1 covers the packages you need, and that your registry can hold the OCI images envd produces.

## FAQ

### What is tensorchord/envd?

It is a command-line tool that creates container-based development environments for AI/ML work. You declare packages in a Python file called build.envd and run envd up to build the environment.

### How do I install envd?

The package is on PyPI, so pip install envd is the usual route. The README lists Docker 20.10.0 or above as a requirement, and installing from source builds the Go binary through make build-release.

### Does envd work with Kubernetes as well as local Docker?

Yes. The README shows envd context use local followed by envd up for local runs, and envd context use cluster followed by envd up for cluster runs, with the same build file in both cases.

## Sources

- [License: Apache-2.0](https://github.com/tensorchord/envd/blob/main/LICENSE)
- [Project website](https://envd.tensorchord.ai/)
- [README](https://github.com/tensorchord/envd/blob/main/README.md)
- [Releases](https://github.com/tensorchord/envd/releases)
- [tensorchord/envd on GitHub](https://github.com/tensorchord/envd)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tensorchord-envd
