Skip to main content

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:
DockerfileVers action
FROM scratchvm.NewRoot (explicit sizing flags required)
FROM <tag-or-commit-id>vm.RestoreFromCommit (tag lookup first)
RUNvers exec with accumulated ENV / WORKDIR / USER
COPY / ADD (local)SFTP upload
ENV, ARG, WORKDIR, USER, LABELbuilder state (affects future layers’ cache keys)
CMD, ENTRYPOINT, EXPOSEcaptured 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

OptionDescription
-f, --filePath to Dockerfile (default: <PATH>/Dockerfile)
-t, --tagTag the resulting commit with this name
--no-cacheIgnore .vers/buildcache.json for this build
--keepKeep the builder VM alive after the build (useful for debugging)
--build-arg KEY=VALSet a build-time ARG (repeatable)
-q, --quietPrint only the final commit id
--format jsonMachine-readable output including cmd, entrypoint, exposed_ports, labels
--mem-sizeMemory in MiB — required for FROM scratch
--vcpu-countvCPU count — required for FROM scratch
--fs-size-vm-mibRoot FS size in MiB — required for FROM scratch
--rootfsBase rootfs image name (FROM scratch only)
--kernelKernel 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.
FormBehaviour
FROM scratchFresh 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:
  1. The key changes (instruction text, ENV/WORKDIR/USER context, or COPY’d file contents differ).
  2. 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