Documentation Index
Fetch the complete documentation index at: https://docs.vers.sh/llms.txt
Use this file to discover all available pages before exploring further.
vers build parses a literal Dockerfile, runs each instruction against a throwaway VM, and commits a “layer” after every step. The output is a commit id — same as vers commit — so it plugs straight into the rest of the primitives: vers run-commit, vers branch, vers tag.
Synopsis
vers build [PATH] # Build using <PATH>/Dockerfile
vers build -f build.Dockerfile . # Custom Dockerfile path
vers build -t myapp:prod . # Tag the final commit
vers build --no-cache . # Ignore the layer cache
vers build --build-arg VERSION=1.2.3 . # Pass ARG values
vers build --mem-size 2048 --vcpu-count 2 \
--fs-size-vm-mib 4096 . # Required for FROM scratch
PATH is the build context directory; its default is .. The Dockerfile defaults to <PATH>/Dockerfile and can be overridden with -f.
Description
Every Dockerfile instruction maps to a Vers action:
| Dockerfile | Vers action |
|---|
FROM scratch | vm.NewRoot (explicit sizing flags required) |
FROM <tag-or-commit-id> | vm.RestoreFromCommit (tag lookup first) |
RUN | vers exec with accumulated ENV / WORKDIR / USER |
COPY / ADD (local) | SFTP upload |
ENV, ARG, WORKDIR, USER, LABEL | builder state (affects future layers’ cache keys) |
CMD, ENTRYPOINT, EXPOSE | captured in the build output metadata |
After each RUN/COPY, the builder calls vm.Commit and stores the resulting commit id in .vers/buildcache.json, keyed by sha256(parent_commit ‖ normalized_instruction ‖ content_hash_for_COPY). On a hit, the step is skipped and the cached commit becomes the new parent — the same shape as Docker’s layer cache, just using Vers commits as layers.
Options
| Option | Description |
|---|
-f, --file | Path to Dockerfile (default: <PATH>/Dockerfile) |
-t, --tag | Tag the resulting commit with this name |
--no-cache | Ignore .vers/buildcache.json for this build |
--keep | Keep the builder VM alive after the build (useful for debugging) |
--build-arg KEY=VAL | Set a build-time ARG (repeatable) |
-q, --quiet | Print only the final commit id |
--format json | Machine-readable output including cmd, entrypoint, exposed_ports, labels |
--mem-size | Memory in MiB — required for FROM scratch |
--vcpu-count | vCPU count — required for FROM scratch |
--fs-size-vm-mib | Root FS size in MiB — required for FROM scratch |
--rootfs | Base rootfs image name (FROM scratch only) |
--kernel | Kernel image name (FROM scratch only) |
FROM semantics
FROM scratch always requires --mem-size, --vcpu-count, and --fs-size-vm-mib. There are no implicit defaults — the build will fail fast if you forget them.
| Form | Behaviour |
|---|
FROM scratch | Fresh VM via vm.NewRoot. |
FROM <name> | Looked up as a vers tag first, falling back to treating <name> as a commit id. |
FROM <commit-id> | Restored directly. |
Supported instructions
FROM, RUN, COPY, ADD (local paths only), ENV, ARG, WORKDIR, USER, LABEL, CMD, ENTRYPOINT, EXPOSE.
Line continuations, exec-form arrays (CMD ["node", "server.js"]), .dockerignore, and $VAR / ${VAR} substitution are all supported.
Not yet supported — parsed but rejected at execution time:
- Multi-stage builds (
FROM ... AS name, COPY --from=stage)
ADD from URLs or with tar auto-extraction
HEALTHCHECK, SHELL, VOLUME, STOPSIGNAL, ONBUILD
Examples
Build from a tagged base
FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . /app
EXPOSE 3000
CMD ["node", "server.js"]
vers build -t myapp:prod .
Build from scratch with explicit sizing
vers build --mem-size 2048 --vcpu-count 2 --fs-size-vm-mib 4096 .
Compose with other commands
# Build, then boot the result
COMMIT=$(vers build -q .)
vers run-commit "$COMMIT"
# Build and branch straight off the output
vers branch $(vers build -q .)
JSON output for scripting
vers build --format json . | jq '.commit_id'
Caching
The cache lives at .vers/buildcache.json inside your project. Keys are deterministic: the same Dockerfile, same build args, same file contents produce the same key. Two scenarios trigger a re-execution:
- The key changes (instruction text, ENV/WORKDIR/USER context, or COPY’d file contents differ).
- The cached commit id no longer exists server-side — the builder falls through to real execution and prints a
cache entry stale note.
Use --no-cache to bypass lookup entirely. Use vers commit delete to explicitly remove layers you don’t want.
Common Patterns
Build, tag, boot — one flow
COMMIT=$(vers build -q .)
vers tag create latest "$COMMIT"
vers run-commit "$COMMIT" --vm-alias dev
Iterative dev loop
Cache makes rebuilds cheap. Layout your Dockerfile so cheap-to-invalidate things come late (application code) and expensive things come early (system deps, language runtimes):
# edit source
vers build -t myapp:dev . # cached through `npm ci`, only re-runs COPY . and later
vers run-commit myapp:dev --vm-alias iter
Pin a known-good commit in CI
COMMIT=$(vers build -q .)
echo "built: $COMMIT"
# downstream CI jobs reference "$COMMIT" explicitly
Debug a failing step
--keep leaves the builder VM alive so you can SSH in after a failed RUN:
vers build --keep . # fails mid-way; VM still running
vers connect $(vers head) # poke around
vers kill -y # clean up when done
See also