Skip to content

CLI reference

Run vamos --help or vamos <command> --help for command-line help; vamos alone shows it too. Targets are names in the current directory’s BUILD file, or workspace labels. Any other name fails before anything runs, suggesting the name that was meant where one is close, and otherwise naming the targets there are. -C <dir> chooses the directory whose targets can be named without a label, which must exist; a relative one is relative to the working directory, as for other tools. It does not change the process’s working directory: local output paths remain relative to where you run vamos.

Commands

All commands that build targets reuse the build cache. Delivery commands build the requested target and its dependencies before sending the result.

CommandAccepted targetsResult
vamos build <target>...Pulls, layers, tasks, indexes, publicationsKeep results in the build cache
vamos push <target> <image>Pulls, layers, indexesPublish to an image registry
vamos publish <publication> --tag <tag>...PublicationsPublish a release’s images under every tag
vamos load <target> <image>Pulls, layers, indexesLoad an image into Docker
vamos save <target> <file.tar>Pulls, layers, indexesWrite a Docker-loadable image archive
vamos export <task> <folder>Tasks with outputWrite a fileset into a local folder
vamos update [pull...]Pulls, or none for every pullPin pulls to what their tags name, in vamos.lock; --dry-run checks the pins
vamos pruneNo targetsRemove cached build results
vamos coordinatorNo targetsCoordinate builds across workers for clients on the network
vamos workerNo targetsRun builds that a coordinator assigns

build

Terminal window
vamos build lint test app
vamos build //backend:test //ui:lint -C path/to/workspace
vamos build test --rebuild

Accepts one or more target names. An index builds all its images, and a publication its checks and images, publishing nothing, as a dry run of publish. A successful check task can be cached even though it produces no files.

--rebuild runs the explicitly named targets again while allowing their dependencies to be reused. A named pull resolves nothing again, keeping its pin, or its tag’s record if it has no pin, and says that vamos update refreshes pulls. See refreshing results.

push

Terminal window
vamos push app ghcr.io/me/app:1.0
vamos push release ghcr.io/me/app:1.0 --push-from=client

Publishes an image, or a whole index with its images, to the destination repository. An image name without a tag gets :latest. Destination names are lowercase image references and cannot include a digest.

--push-from=executor is the default: the executor pushes with its credentials and registry settings, from its own environment: registries.conf and REGISTRY_AUTH_FILE. --push-from=client pushes with the client’s credentials and settings, including --tls-verify. The executor currently runs inside vamos, so both read your local environment, but only the client honours the command line’s --tls-verify. A push retries transient registry failures as containers.conf configures, then reads the tag back from the registry it wrote to, never a mirror, and fails if it names another manifest.

publish

Terminal window
vamos publish release --tag 1.4.2 --tag 1.4 --tag latest
vamos publish release --tag v1 --set env=production --push-from=client

Builds a publication’s checks and images in one run, and only if all of them succeed publishes each image, or index, to its repository under every tag given. --tag is required and may be repeated, so that an exact version and the tags that follow each release, such as 1.4, 1 and latest, go out together; a missing, invalid or repeated tag is a command-line mistake, reported before anything is built, as is a publication that would write more than 1000 destinations, its repositories times its tags. Naming anything but a publication fails, saying what it is. --set gives the publication’s own parameters, and --push-from works as it does for push: the executor publishes by default, and --push-from=client publishes with the client’s credentials and --tls-verify.

Publishing has two phases. First every image is uploaded to each of its repositories by digest, with no tag, so that a failed upload, which fails the command naming the repository, leaves every tag as it was. Only once every upload has succeeded are the tags written, each read back, as push reads its tag back. A failure while writing tags still attempts every tag, and fails the command naming each tag that wasn’t written, with why, and each that was; running the command again finishes the job, uploading nothing already there. On success it reports one line for each repository and tag, Image built! <repository>:<tag> <id>, or Index built! with the index’s digest, in the publication’s order.

The local executor and the client upload one repository at a time, then write one tag at a time; a coordinator uploads on its workers, as many at once as its --bound, and writes every tag on one worker afterwards. Between the phases the uploads are named by no tag, so a registry that removes untagged manifests, through ECR lifecycle rules or Harbor’s garbage collection, could remove one in that window; the phases are seconds apart, and publishing again uploads it again. Reading a tag back needs read access to the repository as well as write, as push does.

load

Terminal window
vamos load app app:dev
docker run --rm app:dev

Loads the image into Docker with the given name, defaulting its tag to :latest; the name is a lowercase image reference without a digest. It uses $DOCKER_HOST, or /var/run/docker.sock, with TLS settings from $DOCKER_CERT_PATH and $DOCKER_TLS_VERIFY, once the image is built, or for an index, first, to ask its platform: a daemon that can’t be reached, or refuses the image, fails the load, saying why. When the target is an index, it selects and builds only the image matching the Docker daemon’s platform. For a single-image target, the build platform defaults to the host unless --platform overrides it.

save

Terminal window
vamos save app app.tar --tag app:dev
docker load -i app.tar
docker run --rm app:dev

Writes an archive that docker load can import, at any path, colons and all, even inside the project, whose globs never read the archive being written. --tag <image>, or -t <image>, records an image name, a lowercase image reference without a digest; without it, the archive has no image name. An existing output file is replaced only after the new image is complete. The output file’s parent directory must already exist, and the output path must not be a directory. For an index, only the image matching the host’s platform is built and saved. To save another platform, name a pull or layer directly and pass --platform.

write is a deprecated earlier name for save and still accepts the same arguments.

export

Terminal window
vamos export codegen src/generated
vamos export codegen src/generated --replace

Requires a task declaring source and include. Writes its files at paths relative to the task’s source. For example, a task selecting /out/src/main.js with source = "/out" exports src/main.js beneath the destination folder.

The folder is replaced as a whole, rather than merged with existing contents. A nonempty folder requires --replace. Without that flag, if files appear in the folder during the build, export refuses to overwrite them. Choose a dedicated generated-output directory. Its parent directory must already exist.

When vamos runs as root, exported files receive the owner and group of the destination’s parent directory. Otherwise, files belong to the user running vamos, with the filesystem’s usual group assignment. They receive the task’s timestamp, and keep the modes the task selected them with, setuid and setgid bits among them where the task’s mode_mask retains them, whatever their owner. Symbolic links are written as links, never followed.

update

Terminal window
vamos update # every pull() in the workspace
vamos update node base # only these pulls

Pins pulls in the lockfile, vamos.lock, at the workspace’s root: the directory with the WORKSPACE file, or else the directory of the BUILD file. For each pull, it lists the tags of the image’s repository and resolves the pull’s tag to the newest stable tag it covers, with the same v prefix and suffix, whose leading numbers are the tag’s: 1.3 resolves to the newest 1.3.x, and 24-alpine to the newest 24.x.y-alpine. A tag that covers no other, a prerelease, or a tag that isn’t a version, such as latest, resolves to itself. It then asks the registry for the digest of the manifest that the resolved tag names, an index or an image, and pins the pull’s image and tag to both. A registry that won’t list a repository’s tags still says what a tag names, so the tag resolves to itself, noted; one that can’t be reached fails the command, naming the image and saying why, and changes nothing. Every build of a pinned pull uses that digest, and never asks a registry what the tag names; see pull().

Updating every pull finds the workspace’s BUILD files by walking it, past hidden directories and other workspaces, though it finds a pull in a hidden directory that a package it found uses, even through a function of ctx, by configuring every target as building it would, and removes the pins of images that no pull uses; naming pulls changes only their pins. A target that can’t be configured so, as one with a parameter that has no default, may use a pull that isn’t found, so a full update then keeps, and reports, the pins that no pull it found uses. Naming a target that isn’t a pull fails. A pull whose image or tag is a function of ctx is pinned as building it would configure it, for --platform, or else this machine’s platform, and for the execution platform that ctx.exec_platform names, which a coordinator that --coordinator names is asked for, as a build asks it; a full update then keeps the pins that no pull uses, saying so, since another configuration of that pull may use them. update prints each pin it adds, changes or removes, with the tag and digest before and after, or that vamos.lock is up to date, or was rewritten with the same pins as this vamos writes it. It then reports, for each pulled tag that reads as a version, the newest stable tag of a newer minor or patch release within its major version, or the final release of a prerelease, and of a newer major version, where there is one, comparing only tags with the same v prefix and suffix; a tag that the pulled tag covers isn’t newer, since it resolves to that. It notes the newest prerelease newer than the tag resolved to, such as 1.6-rc1, and each tag that isn’t a version, such as latest or bookworm, whose newer releases it can’t report. None of these fails the command; to move to a newer release, edit the pull’s tag in its BUILD file and update again. The lines of the pins come first, sorted by image, then those of each image’s releases, image by image:

pinned docker.io/library/node:24-alpine to 24.11.1-alpine@sha256:9f3c…
updated ghcr.io/me/base:1.3 from 1.3.1@sha256:5d0e… to 1.3.2@sha256:1a2b…
removed ghcr.io/me/old:1, which no pull uses, from 1@sha256:77c4…
docker.io/library/debian:bookworm isn't tagged with a version, so newer releases of it can't be reported
docker.io/library/node:24-alpine has a newer major release, 25-alpine
ghcr.io/me/base:1.3 has a newer minor release, 1.5, and a newer major release, 2.1
ghcr.io/me/base:1.3 has a newer prerelease, 1.6-rc1

Nothing is written unless every pull resolves.

vamos update --dry-run resolves and reports as update does, saying what would change, but writes nothing, creating no lockfile where there is none. It fails, exiting with status 1 as any failure does, with vamos.lock would change; run vamos update, when update would change the lockfile; newer releases, prereleases and tags that aren’t versions alone don’t fail it. A lockfile with the same pins that update would rewrite fails it too, as one that an older vamos wrote may be, until the rewritten lockfile is committed. A pull whose image or tag is a function of ctx is checked as the command line configures it, so give the --platform that builds give; the pins it keeps for other configurations aren’t checked. CI can run it to check that the pins are current, and build with VAMOS_PINS=require, so that a pull without a pin fails rather than resolve its tag:

Terminal window
vamos update --dry-run

The lockfile is JSON, written with sorted keys, one field per line, so that a change to one pin changes only that pin’s lines:

{
"version": 1,
"pulls": {
"docker.io/library/node:24-alpine": {
"tag": "24.11.1-alpine",
"digest": "sha256:9f3c…"
}
}
}

Commit it, so that every build of a commit uses the same images and each upgrade is a reviewed change. vamos writes it; edit the BUILD file’s tags, not the lockfile. A malformed lockfile, or one of a newer version than this vamos reads, fails every command that loads it, as does a digest that isn’t sha256, by which pulling verifies the manifest.

prune

Terminal window
vamos prune

Removes all cached layer images and other build results. It keeps pulled images and the recorded resolutions of their tags and pins. To refresh a pull, use vamos update <pull-name>.

coordinator

Terminal window
vamos coordinator --listen :8080 --cache-repository registry:5000/vamos-cache --pull-through docker.io=hub-proxy:5000

Coordinates builds for clients on the network, across the workers that register with it, until interrupted. A client names it with --coordinator http://host:8080, or VAMOS_COORDINATOR, on any command that builds or prunes: its sources are uploaded to the coordinator’s build cache, which --cache-dir places, /var/lib/vamos by default; the coordinator schedules the targets across the workers, with those of one build running at once on different workers, up to --bound, and the client reads the results back for delivery. Simultaneous builds that need the same target run it once and share its result. A client whose connection drops reconnects and resumes watching its build, and gives up after --grace, with a message saying that finished work is cached. The coordinator keeps the same grace period: a build that no client watches for that long, as when the client was killed, starts nothing more, while the targets already running finish and are cached, and the build fails saying it expired; a client holds its build through delivery and releases it when done, so a prune waits for it. A client and a coordinator must run the same version of vamos’s build graph; the client refuses one that doesn’t. prune --coordinator URL empties the coordinator’s build cache and the cache repository’s tags, keeping tag records and the pull-through cache; the cache registry must have deletion enabled, as the compose file’s does. It is refused while any build, push or publication is open, a finished build that its client still holds included, while attempts are still ending on the workers, and for a lease after the coordinator started, and a build whose sources were captured before a prune is refused at submit with a message to run it again. --window says how long a finished build stays readable after it ends or was last watched before the coordinator forgets it; --grace if shorter.

load, save, push --push-from=client and publish --push-from=client read a built image from the cache repository, and a pull() root from the pull-through cache, with the client’s credentials and --tls-verify, so the client must reach both; a repository among those two that the coordinator reaches insecurely, as its registries.conf marks the repository or, failing an entry for it, its registry, or as the coordinator’s own --tls-verify=false says, the client reads as the coordinator does, over plain HTTP, whatever --tls-verify and its own registries.conf say; update, and recording a pin, list a pulled image’s tags and ask what one names at the image’s own registry, from the client, with its credentials and --tls-verify, never through a pull-through cache or a mirror, whose answer about a tag can be stale, so the client must reach each upstream registry too; the images themselves are still fetched by digest through the pull-through cache; push from the executor runs on a worker, copying from there with the worker’s credentials; publish from the executor runs its uploads on workers, as many at once as the coordinator’s --bound, then writes every tag on one worker, and a publication counts as one build against --max-builds, however many repositories and tags it has; the coordinator itself never contacts a destination registry. export reads a task’s files from the coordinator’s build cache.

Workers push every layer they build to --cache-repository, an OCI registry repository, tagged by image ID, before reporting it, so that any worker can fetch it and a result survives the worker that made it; they pull base images through the pull-through caches that --pull-through registry=cache maps, and a pull() from an unmapped registry fails with a message naming the mapping to add. The coordinator checks cached layers against the cache repository and deletes its tags when it prunes, so its credentials need read and delete access to it, and the workers’ need push access. A target’s commands run on a worker of its image’s platform when the deployment offers that platform, so a pool of amd64 and arm64 workers builds both platforms of an index natively. A build runs natively on the platforms offered when it is submitted, by --offer or by a registered worker, and on any offered later that it reaches; it keeps them, across a restart too, so if the only worker of one leaves, its targets for that platform wait for another rather than being emulated. Commands for a platform the deployment doesn’t offer, as --platform linux/arm64 asks of an amd64 pool, run under emulation on the execution platform’s workers only when the build gives --emulate, a flag kept out of --help until local builds take it too; without it the target fails before anything runs, naming the platforms and the ways out. Emulated work goes to workers that report an emulator for the platform, which they find in the kernel’s binfmt_misc handlers; --offer-emulated os/arch says the deployment has such workers, so a build waits for one, where a platform nobody emulates fails the target at once. --offer os/arch names a platform the deployment has workers for, besides those registered now: a build for an offered platform without a worker present waits, printing that it waits for a worker of that platform after a heartbeat interval, so offer only platforms the deployment serves; one for a platform never offered is refused. Pulls and targets that only add files prefer a worker of their image’s platform, but run on any worker while none of that platform is registered. --default-exec-platform os/arch, one that --offer names, is the execution platform clients use unless they give --exec-platform, so that clients of every architecture configure EXEC_PLATFORM alike and reuse each other’s results; without it each client uses its own platform where the coordinator offers it. A worker that stops renewing an attempt’s lease (--lease) is lost, and its work tried again elsewhere, up to --retries times, a publication’s uploads included, since an upload by digest changes no tag; a push, and a publication’s tagging, are never retried, since their writes may have happened. Beyond --max-builds open builds, submissions are refused as busy.

There is no authentication: run a coordinator on a trusted network alone, where anyone who can reach it can build on it and read its cache, and anyone who can reach a pull-through cache reads what its upstream credentials can. The coordinator keeps its jobs in coordinator/ under --cache-dir, so a restart that keeps that directory resumes them: clients reconnect within --grace, warn that the coordinator restarted, and see their builds finish. A resumed build runs again from its start: what finished before the restart is cached, and what was running starts again on a worker, since workers stop what they ran for a coordinator they lost and register again. A push, publication or prune that was running fails, since whether it wrote is unknown, and a cancelled build stays cancelled. Two coordinators can’t share that directory: the second refuses to start. A coordinator started without that directory has none of its jobs: clients report that their builds were lost, and rerunning reuses what finished.

worker

Terminal window
vamos worker --coordinator http://coordinator:8080 --capacity 2

Runs the attempts the coordinator assigns, up to --capacity at once, each in a subprocess of its own in a cgroup, so that cancelling a build kills every process the attempt started, however it daemonized itself. A worker needs root, cgroup v2 and buildah’s chroot isolation, as building does, and a container store of its own, which is a disposable cache of base images and built layers. It makes its cgroups under /sys/fs/cgroup/vamos, so it needs a cgroup namespace of its own, as Docker gives a container and Kubernetes doesn’t give a privileged one; the Helm chart in remote/helm/vamos starts it in one. It renews each attempt’s lease by heartbeat, and kills an attempt itself when it can’t, so that the coordinator can count an expired lease as a stopped attempt; after a kill, or on starting after a crash, it removes the attempt’s container. Workers need egress to the pull-through caches, the cache repository, and whatever run() steps fetch. Their registry credentials are their own; a client’s never reach them.

Flags

These flags are shared by commands that build targets:

FlagMeaningDefault
-C, --dir <dir>Directory for unqualified target names and workspace discovery.
--set <name=value>Set a named target’s parameter; repeat for several parametersParameter defaults
--secret <id=ID,env=VARIABLE>, --secret <id=ID,src=FILE>Give run() steps’ secret ID the value of an environment variable or a file; repeat for several secretsNone
--platform <os/arch[/variant]>Target platform for a pull, layer, or taskHost platform
--exec-platform <os/arch[/variant]>Platform that EXEC_PLATFORM names, and that runs run() steps for platforms the executor doesn’t offer; the executor must offer itThe coordinator’s --default-exec-platform, or else this machine’s, or the only one offered
--coordinator <URL>Build on the coordinator at this URL rather than in this process$VAMOS_COORDINATOR, or none
--tls-verify <bool>Require HTTPS and verify certificates when vamos itself contacts a registry: pushing from the client, and reading a coordinator’s images to deliverregistries.conf
--timestamp <time>Minimum timestamp for newly executed targetsDerived from bases and added tasks
--pins <policy>What a command that builds does with a pull that vamos.lock doesn’t pin: record resolves its tag at its own registry, from the client, as update does, adds its pin and reports it; resolve resolves it on the executor, keeping what the tag named only in the build cache; require fails, naming every pull without a pin, before anything runs$VAMOS_PINS, or else record

--platform cannot be given when naming an index. With multiple build targets, each --set applies to named targets declaring that parameter, and must match at least one named target. See parameters for value syntax.

--timestamp accepts Unix seconds or RFC 3339, such as 2026-01-01T00:00:00Z, no earlier than 1970. It does not invalidate existing cached results.

Secrets

--secret gives the secrets that run() steps name:

Terminal window
vamos build //react:build --set docker_tag=1.2.3 --secret id=sentry,env=SENTRY_AUTH_TOKEN
vamos build //app:deps --secret id=npmrc,src=$HOME/.npmrc

env= reads an environment variable of the vamos process, which must be set, though it may be empty; src= reads a file. They are read only when a build runs, and a secret that a step needs but no --secret gives fails the build before anything runs. Values stay in the vamos process: they never enter the build cache, images, traces or the graph. --secret can’t be combined with --coordinator, since a coordinator has no authentication or TLS; build secrets in your own process.

--trace <file.json> writes a Perfetto trace and is available on build, push, publish, load, save (including its deprecated write alias), and export.

The TLS flag displays true in help, and given alone means true, but when omitted vamos defers to registries.conf, including entries with insecure = true. It applies to what the client itself does: a push or publication with --push-from=client, and reading a coordinator’s images to load, save, push or publish, except from a cache registry or pull-through cache that the coordinator marks insecure, which the client reads as the coordinator does, while the destination keeps --tls-verify; the executor, which pulls and pushes otherwise, follows registries.conf alone, so mark a plain-HTTP registry insecure = true there for it.

Command-specific flags are build --rebuild, push --push-from, publish --tag and --push-from, save --tag/-t, and export --replace.

Registry credentials

Registry operations use your container registry authentication configuration, including credentials from docker login in ~/.docker/config.json. If $REGISTRY_AUTH_FILE is set, only the credentials in that file are used.

When running vamos itself inside Docker, make the authentication file available inside that container. For the tutorial’s Docker function, add this mount to its docker run invocation after logging in:

Terminal window
-v "$HOME/.docker/config.json:/root/.docker/config.json:ro"

If the configuration uses a credential helper, that helper must also be available inside the vamos container. Alternatively, mount an auth file and point REGISTRY_AUTH_FILE to its container path:

Terminal window
-v "$PWD/auth.json:/run/registry-auth.json:ro" -e REGISTRY_AUTH_FILE=/run/registry-auth.json

Output and failures

Build progress reports target states such as building and cached, along with command output. A target configured alike for several platforms of an index, such as a task on an EXEC_PLATFORM base, is built once: the others report building, shared with the one whose work they share, or with another build when another build started it. Cached targets replay output from their previous execution. Successful delivery prints one of these lines:

Image built! <destination> <image-id>
Index built! <destination> <digest>
Files exported! <folder> <digest>

Errors exit with status 1 and a vamos: prefix. BUILD errors include the file, line, and column. Command-line usage mistakes print usage; runtime failures do not.

Common problems

SymptomWhat to check
Operation not permitted during namespace creationUse the tutorial setup
exec format error in a build stepUse native tools or register emulation for the requested platform
An add() source is rejectedIt must name a glob or a task with both source and include
A glob matches no filesCheck its paths relative to the BUILD file and its exclusions
Export refuses a nonempty folderChoose a dedicated output folder or pass --replace
A moved registry tag still uses an old imagePin what it names now with vamos update, and commit vamos.lock
Docker cannot be reachedCheck the socket mount, DOCKER_HOST, and Docker’s TLS environment variables
can't reach the coordinator at ...Check the URL in --coordinator or VAMOS_COORDINATOR, and that this machine is on the coordinator’s network
the coordinator ... runs graph versions ..., not this vamos'sUse a vamos of the coordinator’s version, or upgrade the coordinator
waiting for a worker that runs ...; none is registeredStart a worker of that platform, or interrupt the build and run it again; a platform that --offer names is waited for again, so stop offering it first
waiting for a worker; none is registeredStart a worker
its commands would run under emulationAdd a worker of the image’s platform, give --platform for one that runs them, or --emulate to allow emulation on a worker that has an emulator
the connection to the coordinator was lost for ...Run the command again once the coordinator is reachable; finished targets are cached
the build expiredThe build went unwatched for the coordinator’s grace period; run the command again
the build cache was pruned while the build's sources were being capturedRun the command again
A build hangs with no output over a coordinatorCheck the coordinator’s and workers’ logs with docker compose logs, and that a worker of the platform is registered