Garden (Polyglot / CI) ​
A Kubernetes-native development and CI automation tool: you describe every part of your system — builds, deploys, tests, and ad-hoc runs — as declarative Build, Deploy, Test, and Run actions in garden.yml files (spread across the repo and even across repositories), and Garden assembles them into a dependency-aware Stack Graph that it executes with graph-aware, version-hashed result caching so the same image is never built twice and the same test never re-runs twice.
| Field | Value |
|---|---|
| Language | TypeScript (~97% of garden-io/garden); Garden Core ships as a standalone binary |
| License | Mozilla Public License 2.0 (MPL-2.0) |
| Repository | garden-io/garden |
| Documentation | docs.garden.io · Stack Graph · Graph execution internals |
| Category | Container / CI-Oriented |
| Workspace model | Action graph (Stack Graph) — config files scanned repo-wide (and across remote sources) into one graph of Build/Deploy/Test/Run actions; no single member array |
| First released | 0.1, 2018 (open-sourced); the action-based garden.io/v2 model is the 0.13+ "Bonsai/Acorn" line |
| Latest release | 0.14.20 (February 27, 2026) |
Latest release:
0.14.20, published February 27, 2026. The current config schema isapiVersion: garden.io/v2. Garden's pivot from the older modules + services/tasks/tests model (0.12"Acorn") to the flat action model (Build/Deploy/Test/Run,0.13"Bonsai" onward) is the most important recent change — this deep-dive describes the action model. Garden Core is "a standalone binary that can run from CI or from a developer's machine" (repo); the optional Garden Cloud layer adds the Remote Container Builder and team-wide test/run caches.
IMPORTANT
This is garden-io/garden, the Kubernetes development/CI automation tool — not any of the unrelated "garden" projects (gardening games, the gardener Kubernetes cluster-management project, etc.). Within this survey it is the Kubernetes-native sibling of the container/CI tools Dagger (a GraphQL/BuildKit pipeline engine) and Earthly (a Dockerfile-derived target DSL).
Overview ​
What it solves ​
Like its category siblings Dagger and Earthly, Garden is not a package manager or a language build system. It does not resolve library dependencies, produce a lockfile, install a node_modules, or compile your code directly — it sits one level up, orchestrating the builds, deploys, and tests of a multi-component system. Where Garden differs from Dagger and Earthly is its center of gravity: it is Kubernetes-native. Its reason for existing is to make a production-like Kubernetes environment cheap to spin up on demand and identical across a developer laptop, CI, and production. The README states the scope plainly:
"Automation for Kubernetes development and testing. Spin up production-like environments for development, testing, and CI on demand. Use the same configuration and workflows at every step of the process. Speed up your builds and test runs via shared result caching." —
garden-io/gardenREADME
The pain it targets is the dev/CI/prod configuration triplication common to Kubernetes shops: a docker-compose.yml for local dev, a pile of Dockerfiles and kubectl/helm invocations wired into a CI YAML, and a third set of manifests for production — three descriptions of one system that drift apart. Garden replaces that with a single declarative description (the Stack Graph) that is "portable" across environments, plus an engine that caches builds and test results so large stacks stay fast. Self-described:
"Garden is a DevOps automation tool for developing and testing Kubernetes apps faster." — What is Garden
Design philosophy ​
The core idea is the Stack Graph: a single dependency-aware graph that ties together every action needed to build, deploy, and test the whole system, collected from config scattered across the repo (and across repos) rather than centralized.
"Garden collects all of these descriptions, even across multiple repositories, into the Stack Graph—an executable blueprint for going from zero to a running system in a single command." — What is Garden
Three consequences flow from this and shape everything below:
- Everything is an action of one of four kinds. A unit of work is a
Build,Deploy,Test, orRunaction declared in YAML. "Each of the four action kinds (Build, Deploy, Test, Run) has a corresponding command that you can run with the Garden CLI" (What is Garden). The graph's edges are dependencies between actions, written as<kind>.<name>strings. - The graph is version-hashed, so caching is dependency-aware. Garden computes a Garden version for every action from its source files, configuration, and the versions of its upstream dependencies. Because of this, "the same image never needs to be built twice or the same test run twice" (What is Garden) — a test re-runs only if its own sources or an upstream dependency changed.
- Pluggable execution, Kubernetes-first. How an action runs is delegated to a plugin. The
kubernetes/local-kubernetesplugins are the flagship (build images, apply manifests, run pods);container,exec,helm,terraform, andpulumiplugins cover the rest. The graph is plugin-agnostic; the plugins make it concrete.
Within this survey Garden is the canonical Kubernetes-native container/CI data point; compare it with Dagger (pipelines as code over a BuildKit/GraphQL DAG, no workspace manifest) and Earthly (Earthfile targets), and with the heavyweight polyglot engines Bazel/Buck2 whose action graph + remote caching it echoes at a much higher, container-shaped granularity. For why dub has no analogue of any of this, see the D landscape notes.
How it works ​
A Garden project is a tree of YAML config files. One file declares the project (kind: Project); the rest declare actions (kind: Build/Deploy/Test/Run), which Garden discovers by scanning the repository.
The project config and config discovery ​
A project is rooted by a project.garden.yml (any *.garden.yml / garden.yml works) at the repo root:
apiVersion: garden.io/v2
kind: Project
name: my-project
environments:
- name: dev
- name: ci
providers:
- name: local-kubernetes
environments: [dev]
- name: kubernetes
environments: [ci]
context: my-ctxGarden then scans the repository for action configs. They need not live in one file: "These actions can be spread across the repo in their own config files, often located next to the thing they describe" (Basics). Crucially, discovery is not a member array — there is no members = ["libs/*"] glob enumerating sub-packages the way Cargo's [workspace], go.work, or pnpm's pnpm-workspace.yaml have one. Garden "is very flexible and will work with whatever structure you currently have. It even works across git repositories!" (Basics) — the topology is the graph of actions it finds, not a declared list of directories.
Actions and the <kind>.<name> dependency grammar ​
An action declares its kind, a plugin type, a name unique within its kind, and its dependencies. A container Build:
kind: Build
type: container
name: api
include:
- 'src/**/*'
- 'Dockerfile'
spec:
dockerfile: DockerfileA Deploy that depends on that build and on a database deploy, and a Test that depends on the deploy being up:
kind: Deploy
type: kubernetes
name: api
dependencies: [build.api, deploy.db]
---
kind: Test
type: container
name: api-integ
build: api
dependencies: [deploy.api]
spec:
args: ['npm', 'run', 'test:integ']Dependencies are the literal strings build.api, deploy.db, deploy.api — "a <kind>.<name> string, where kind is one of build, deploy, run or test". Actions can also reference each other's outputs via template strings such as ${actions.build.api.outputs.deployment-image-name}, and Garden "automatically detects" those implicit references during preprocessing and adds the corresponding graph edges (Graph execution). Because tests are first-class graph nodes, Garden "will re-run the test if any of the upstream services under test are modified" (What is Garden) — and only then.
The GraphSolver: from config to executed graph ​
Internally the engine is the GraphSolver, organized as "Solver > Nodes > Tasks" (Graph execution). Each action becomes a task of one of four classes — BuildTask, DeployTask, TestTask, RunTask — plus internal ResolveActionTask/ResolveProviderTask tasks that resolve config and provider state first. The solver wraps each task in a node and executes:
| Node type | Runs | Purpose |
|---|---|---|
RequestTaskNode | (root) | A task the user requested on the CLI |
StatusTaskNode | the task's getStatus method | "Is an up-to-date result already present?" |
ProcessTaskNode | the task's process method | Actually build / deploy / test / run |
The defining mechanism is the getStatus / process split. Before doing any work, the solver runs getStatus:
"If an up-to-date result is available, the
getStatusmethod will return a status withstate: 'ready', which indicates to the solver that the task doesn't need to be processed." — Graph execution
That is the cache check, and it is dependency-aware because "the action version is calculated in a dependency-aware fashion" (Graph execution). The execution flow is explicitly:
- wrap tasks in nodes;
- skip nodes whose
getStatusreportsready; - build a dependency graph from the pending nodes;
- process leaf nodes up to a concurrency limit;
- mark nodes complete and save results.
Dependencies come in two flavors: status dependencies (resolveStatusDependencies, needed before a status check — e.g. an action must be resolved before its status is known) and process dependencies (resolveProcessDependencies, needed before execution), following the principle to "use the cheapest / least processed type of task that's required to satisfy the dependency" (Graph execution). The solver's loop/ensurePendingNodes mutate the active node set in synchronous code "by design — by only updating the active nodes in synchronous code, we prevent race conditions" (Graph execution).
The five dimensions below locate this model relative to the rest of the catalog.
1. Workspace declaration & topology ​
Garden has no member-enumerating workspace root. There is no glob array of sub-packages; the project config (kind: Project) declares environments and providers, not members. Instead, topology is the discovered action graph: Garden scans the repository for *.garden.yml files, reads every Build/Deploy/Test/Run action, and wires them together by their <kind>.<name> dependency strings and implicit output references into the Stack Graph.
Two properties make this distinctive among the catalog:
- Co-located, scattered config. Action configs "can be spread across the repo in their own config files, often located next to the thing they describe" (Basics). A monorepo's structure is implicit in where the configs are and how they depend on each other, not in a central manifest.
- Cross-repository projects via remote sources. Garden explicitly supports a project whose components live in different git repositories: "It even works across git repositories! You can e.g. have your service source code in one repo and manifests in another" (Basics). A project lists remote sources — each a
nameplus arepositoryUrlpinned with a#branch-or-tagsuffix — and Garden clones them into one logical Stack Graph. Thegarden link source/garden link actioncommands swap a remote source for a local checkout so you can edit it in place (Remote sources).
# Project config — pulling in components from other repositories
kind: Project
name: my-project
sources:
- name: web-services
repositoryUrl: https://github.com/org/web-services.git#main
- name: db-services
repositoryUrl: https://github.com/org/db-services.git#v1.2.0NOTE
This is the opposite end of the spectrum from a go.work use list or a Cargo members glob. Garden does not declare a closed set of members; it discovers actions wherever their configs are and unifies them — including across repos — into one graph. The trade-off is that "what is in my workspace?" has no single-file answer; you read the graph (garden get graph, garden summary).
2. Dependency handling & isolation ​
Two dependency notions coexist and must not be conflated:
- Action dependencies (Garden's own). The
dependencies: [build.api, deploy.db]edges and implicit${actions.*.outputs.*}references that define graph order. These are the only "dependencies" Garden itself resolves — between actions, not between library packages. - Language/library dependencies (your app's). Garden does not resolve these. Your
npm/pip/go/cargopackages are installed inside the build, by whatever tool theDockerfileor build action invokes. Isolation is therefore the container/Kubernetes boundary, not a hoistednode_modules, an isolated symlink tree (pnpm), or a virtual content-addressed store (Yarn Berry). There is no equivalent of Yarn'sworkspace:protocol for libraries — the "local-first cross-reference" Garden offers is between actions (one action depending on another, or a remote source linked to a local path), not between versioned packages.
Build isolation is configurable per the kubernetes plugin's buildMode. By default the local Docker daemon builds images; you can instead build in the cluster to share caches across the team:
"By default your local Docker daemon is used, but you can set it to
cluster-buildkitorkanikoto sync files to the cluster, and build container images there. This removes the need to run Docker locally, and allows you to share layer and image caches between multiple developers, as well as between your development and CI workflows." — In-cluster building
buildMode | What it does | Cache sharing |
|---|---|---|
local-docker | Build with the local Docker daemon, then push to the registry | Local only |
cluster-buildkit | One BuildKit deployment per project namespace, builds in-cluster | Shared across devs + CI (recommended) |
kaniko | One Kaniko pod per build, in-cluster | Shared via registry/layer cache |
3. Task orchestration & scheduling ​
Orchestration is Garden's strongest dimension, and it is genuinely graph-structured (unlike Dagger, where the DAG is implicit in data flow, or a turbo.json task list). The Stack Graph is the task DAG:
- Explicit, typed DAG. Every action is a node; every
dependenciesentry and every${actions.*}output reference is an edge. TheGraphSolvertopologically orders them and "processes leaf nodes up to a concurrency limit" — concurrent execution of independent legs is built in, bounded by a configurable parallelism cap (Graph execution). - Change detection via version hashing, not git-diff. Garden computes a Garden version for each action: "these are the Garden versions that are computed for each action in the Stack Graph at runtime, based on source files and configuration for each action" (FAQ), folding in upstream dependency versions. A
Deploy's version can differ from itsBuild's because "the Deploy's version also factors in the runtime configuration for that deploy, which often differs between environments" (FAQ).include/excludeglobs on an action scope exactly which files feed its hash, so unrelated edits don't bust it. - Status-first execution = affected-detection emerges from caching. Because every task runs
getStatusbeforeprocess, agarden deployorgarden testover the whole graph skips every action whose version already has areadyresult. There is no separate--affected/--since <ref>git query (Turborepo, Nx); the "affected set" is whatever the version hash says is stale. This is the same emergent-affected property Dagger gets from content-addressing, but driven by an explicit, inspectable action-version rather than opaque op hashes.
4. Caching & remote execution ​
Caching is, again, the point — and it operates at several layers:
Result caching (intrinsic). Build, deploy, test, and run results are cached by Garden version. "Garden caches test results and only re-runs the test if the module the test belongs to, or upstream dependents, have changed" — so "the same image never needs to be built twice or the same test run twice" (What is Garden).
Image-layer caching. Builds reuse Docker/BuildKit layer caches; with
cluster-buildkit/kanikothose layer caches live in the cluster and are shared across the whole team and CI.Remote Container Builder (Garden Cloud). The optional managed builder offloads builds to remote compute with a persistent NVMe layer cache: "Each built layer of your Dockerfile is stored on low-latency, high-throughput NVMe storage so that your entire team can benefit from shared build caches" (Remote Container Builder). It "is enabled by default once you've logged in, so no further configuration is required," and can be scoped per environment under the
containerprovider:yamlproviders: - name: container environments: [remote-dev, ci] gardenContainerBuilder: enabled: trueTeam-wide test/run caches. Historically the status/result cache for kubernetes test and run actions lived in cluster
ConfigMaps, which "could not be shared" across clusters and grew unwieldy. Garden Cloud moved this to a hosted store so test/run caches are shared across clusters (Cloud announcement).
NOTE
Garden's remote story is shared layer/result caching plus a managed remote builder, not the Remote Execution API (REAPI) that Bazel/Buck2 backends like Buildbarn and NativeLink speak. Like Dagger, Garden farms container builds and caches action results; it does not farm arbitrary fine-grained actions to a content-addressed REAPI cluster. The granularity is the whole Build/Test, not a single compiler invocation.
5. CLI / UX ergonomics ​
The command boundary is action-kind-centric: each of the four kinds has a verb, and you select which actions of that kind by name (or run all of them).
- Per-kind verbs.
garden build,garden deploy,garden test,garden run— each builds/deploys/tests/runs all actions of that kind (in dependency order) if given no names, or a subset if named:garden deploy api web,garden build api,garden test api-integ -i(Using the CLI). Dependencies of the selected actions are pulled in automatically (adeployfirst runs the builds it needs). - Name-based selection, not
--filter/-p/:target. Selection is positional action names, optionally with glob patterns over names, rather than a package-filter flag. There is no--since <git-ref>affected query; the status-cache makes a full-graph run skip unchanged work instead. - Sub-graph control flags.
--skip <names>excludes specific actions;--skip-dependenciesruns only the named actions without their deploy/test/run dependencies;--with-dependantsadditionally processes the downstream dependents of the selected actions ("useful when you know you need to redeploy dependants") (Commands). - Environment/namespace targeting.
--env <namespace>.<environment>(e.g.garden deploy --env my-ns.dev) selects which environment's providers and config apply — the axis Garden uses where other tools use package filters. - The interactive dev console.
garden devopens an interactive console in which you "execute Garden commands in interactive mode, like build, deploy, run, test," with sync mode live-syncing file changes into running deploys for "blazing fast feedback while developing" (What is Garden, Commands).
# Deploy two specific services into the dev environment, with their dependencies:
garden deploy api web --env local.dev
# Run all tests, skipping a slow one; reuse cached results for unchanged actions:
garden test --skip api-e2e
# Open the interactive dev console with live-sync into running deploys:
garden devThere is no --filter pkg... / -p / :target / --since vocabulary. The selection unit is which named actions of which kind, plus the environment axis — a direct consequence of the action-kind model and of caching (not a git query) being the mechanism that bounds work to what actually changed.
Strengths ​
- Dev ≡ CI ≡ prod by construction. One declarative Stack Graph drives every environment; sync mode plus on-demand production-like Kubernetes environments collapse the dev/CI/prod config triplication that plagues Kubernetes shops.
- Explicit, typed, dependency-aware graph. Unlike Dagger's implicit data-flow DAG, the action graph is declared and inspectable, with first-class
Build/Deploy/Test/Runsemantics andgetStatus-driven skipping. - Graph-aware, version-hashed caching. Action versions fold in upstream dependencies, so builds and tests never repeat needlessly — emergent affected-detection without a git-diff query, plus team-shared in-cluster layer caches and a managed NVMe-backed remote builder.
- Config can be scattered and cross-repo. Actions live next to what they describe, and remote sources unify multiple git repositories into one Stack Graph — a reach no member-array workspace tool offers.
- Pluggable execution.
kubernetes/helm/terraform/pulumi/exec/containerplugins mean the same graph orchestrates infra, container builds, and tests. - Tests as graph citizens. Integration tests can declare
dependencieson deploys, so Garden brings up exactly the stack a test needs and caches its result.
Weaknesses ​
- Not a package manager or build system. No library-dependency resolution, no lockfile, no workspace member manifest — orthogonal to Cargo/dub/pnpm. For
dub's workspace problem it offers an orchestration/caching model, not manifest primitives. - Kubernetes-centric gravity. The flagship value (in-cluster builds, sync mode, production-like envs) assumes Kubernetes; outside a cluster you fall back to the generic
exec/containerplugins and lose much of the appeal. - No declarative member set / no
--since. "What's in the workspace" has no single-file answer (you query the graph), and there is no git-ref affected query — change scoping is the version hash, which is powerful but less explicit. - Remote cache ≠REAPI. Caching is shared layers + hosted result caches + a managed builder, not a distributed REAPI action farm like Bazel/Buck2 backends.
- Best caching/builder features are Garden Cloud. The Remote Container Builder and cross-cluster team test/run caches live behind the hosted Garden Cloud (with a metered free tier); the open-source Core caches locally/in-cluster.
- YAML at scale. Large projects accumulate many
garden.ymlfiles and a powerful but intricate templating engine;garden summaryexists specifically to diagnose slow init on projects with "lots of actions and modules and/or lots of files."
Key design decisions and trade-offs ​
| Decision | Rationale | Trade-off |
|---|---|---|
Four action kinds (Build/Deploy/Test/Run) as the graph | First-class, typed verbs map cleanly to CLI commands and to getStatus/process semantics | A new concept to learn; everything must be shoehorned into one of four kinds |
| Config discovered repo-wide (vs a member array) | Configs live next to what they describe; works with any layout, even across git repos | No single-file "what's in my workspace"; topology must be queried (get graph/summary) |
| Cross-repo remote sources unified into one Stack Graph | A project can span many repositories (code here, manifests there) yet build/test as one system | Extra clone/version-pinning machinery; harder to reason about than an in-tree member list |
getStatus before process, version-hashed dependency-aware | Affected work emerges from caching; the same image/test never repeats; no git-diff query needed | "What will run" is computed from version hashes, not an explicit --since set — less transparent |
| Pluggable execution, Kubernetes-first | One graph orchestrates infra (terraform/pulumi), builds (container), and tests (exec) | Greatest value assumes Kubernetes; off-cluster usage loses sync mode and in-cluster caching |
In-cluster build modes (cluster-buildkit/kaniko) | Share layer/image caches across the team and CI without a local Docker daemon | Requires cluster build infrastructure; build resource limits become a cluster-capacity concern |
| Remote Container Builder + hosted caches via Garden Cloud | NVMe-backed shared layer cache and cross-cluster test/run caches accelerate teams | Best caching is a hosted (metered) service, not pure OSS; another dependency/login |
Name-based, per-kind CLI selection (no --filter/:target) | A uniform verb [names] --env surface generated from the action kinds | No package-filter (--filter/-p) or --since selection vocabulary |
Sources ​
garden-io/garden— GitHub repository (README,MPL-2.0, TypeScript)- Garden documentation — docs.garden.io
- What is Garden — Stack Graph, action kinds, caching, sync mode
- Garden basics — config discovery, cross-repo, actions & dependencies
- Graph execution internals —
GraphSolver,getStatus/process, task types - The Stack Graph (terminology)
- Using the CLI — per-kind verbs, action-name selection
- Commands reference —
--skip,--skip-dependencies,--with-dependants,dev - Remote sources — multi-repository projects,
garden link - In-cluster building —
buildModelocal-docker/cluster-buildkit/kaniko - Remote Container Builder — NVMe layer cache,
gardenContainerBuilder - Garden Cloud — team-wide test/run caches (vs cluster
ConfigMaps) - FAQ — Garden version computation,
.gardendirectory - Sibling tools: Dagger · Earthly · Turborepo · Nx · Bazel · Buck2 · Cargo ·
go.work· pnpm · Yarn Berry · Task · Just · mise · Buildbarn · NativeLink - D context: the D landscape