project.toml
A project.toml declares a CI project: a buildable/testable unit,
usually one per module or service. garden discovers project.toml files
across the tree and uses them to scope what builds, what tests run, and
how changes map to work (test selection).
Schema
Section titled “Schema”[project]name = "api" # project namelanguage = "go" # primary language (go, typescript, ...)path = "services/api" # repo-relative root; "" or "." = the file's dir
[test]timeout = "10m" # optional; Go duration (e.g. 30s, 10m)images = ["postgres:17-alpine"] # containers the tests need (see below)
# Only for languages with no test inference (see "Running tests"). Go,# TypeScript and Python ignore this — the runner builds its own invocation# from the packages selection picked.command = "cargo test"
[deps]internal = ["libs/auth"] # other projects this one depends on, # so a change to a dep selects this project too
[generate]commands = ["buf generate"] # codegen commands for this projecttriggers = ["proto/**"] # globs that should re-run the codegen
[owners]team = "platform" # owning team (optional metadata)
# Zero or more extra rules: run a command when matching files change.# Repeat the [[rule]] block for each.[[rule]]name = "lint"match = ["**/*.go"] # globs that trigger this ruleexclude = ["**/gen/**"] # globs to exclude from the matchrun = "golangci-lint run"timeout = "5m" # optional; Go durationOnly [project] is required in practice; the rest are optional. CI runs
the relevant projects on each revision and after a change lands; failures
surface as inline review threads on the exact failing lines. Check a PR’s
checks with garden pr checks <pr>.
Running tests
Section titled “Running tests”For Go, TypeScript and Python, you don’t say how to run the tests.
garden reads the import graph, works out which packages your change can
affect, and runs exactly those — so [test].command is ignored (garden
warns if you set one). Leave it out.
Other languages have no inference backend yet, so they need an explicit
[test].command to have a test check at all.
[[rule]] blocks are independent of all this: a rule fires when a changed
file matches its globs and runs its own command. Linting, formatting and
codegen checks belong there, in any language.
Container images
Section titled “Container images”Tests run inside a sealed VM with no network, so a test that starts a container can’t pull one. Declare what it needs and garden fetches it outside the sandbox and loads it in before the tests run:
[test]images = ["postgres:17-alpine"]Any registry reference works — a tag, a digest pin, or a private image in
your own registry (us-central1-docker.pkg.dev/proj/repo/img:v1).
This is a setting about the environment, not about the tests. It does not make your project command-driven and it does not change test selection: inference still decides which tests run, the images are just there when they do.
If your tests use testcontainers, that’s all you need — DOCKER_HOST
is pointed at the in-VM daemon for you, and the Ryuk reaper is disabled
(the VM is thrown away after the run, so there is nothing to reap).
Notes:
- Every image a test starts must be declared. Nothing can be pulled at test time, so an undeclared image fails with “no such image”.
- Images are pulled from a fixed set of public registries (Docker Hub,
ghcr.io, quay.io, gcr.io,
*.pkg.dev, ECR Public, MCR, registry.k8s.io) and are subject to a total size limit. - A language with no dependency prefetch can’t carry images, so
imagesrequires one of the supported languages.