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 reads a literal Dockerfile and produces a Vers commit. Each instruction becomes a cached “layer.” The output is the same commit id any other Vers command accepts — it boots with vers run-commit, branches with vers branch, and can be tagged with vers tag or -t. This page explains the model. For the command-level reference, see vers build.

The mapping

Every supported Dockerfile instruction translates to one or more Vers actions:
DockerfileVers action
FROM scratchvm.NewRoot with explicit sizing flags
FROM <tag-or-commit>vm.RestoreFromCommit (tag lookup first)
RUNvers exec with accumulated ENV / WORKDIR / USER
COPY / ADD (local)SFTP upload from the build context
ENV, ARG, WORKDIR, USER, LABELBuilder state — no VM mutation, but affects future layers
CMD, ENTRYPOINT, EXPOSECaptured as commit metadata
After each RUN or COPY, the builder commits the VM and moves on with a fresh parent.

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 patterns, and $VAR / ${VAR} substitution are supported.
Not yet supported — parsed but rejected at execution:
  • Multi-stage builds (FROM ... AS name, COPY --from=stage)
  • ADD from URLs or with tar auto-extraction
  • HEALTHCHECK, SHELL, VOLUME, STOPSIGNAL, ONBUILD

The cache model

Every materializing instruction (RUN or COPY/ADD) produces a commit. Before executing, the builder computes a cache key:
sha256(
  parent_commit_id
  ‖ normalized_instruction_text
  ‖ env / workdir / user snapshot
  ‖ content_hash (for COPY)
)
If that key has been seen before — stored in .vers/buildcache.json in your project — the builder skips execution and uses the cached commit as the new parent. Otherwise it runs the step and writes the resulting commit back to the cache. This is the same shape as Docker’s layer cache, with two differences:
  1. Layers here are first-class Vers commits, not OCI blobs. You can vers branch a mid-build layer directly.
  2. Cache invalidation is content-based, not timestamp-based. Touching a file without changing it doesn’t invalidate.

Cache invalidation

A layer re-runs when any of:
  • The instruction text changes (even whitespace-significant)
  • The accumulated ENV/WORKDIR/USER before this instruction differs
  • For COPY: any file in the copied tree changes size, mode, or contents
  • For COPY: .dockerignore changes the visible tree
  • --build-arg values change (and that ARG is declared in the Dockerfile)

Cache busting

  • --no-cache — bypass the cache for this build.
  • Delete .vers/buildcache.json — reset all cache state.
  • vers commit delete <commit-id> — remove specific cached layers; next build re-runs them.

Stale cache

If a cached commit id has been deleted server-side, the builder falls through to real execution and prints a cache entry stale note. No hard failure — you just lose the speedup for that step.

FROM semantics

FormBehaviour
FROM scratchFresh VM via vm.NewRoot. Requires --mem-size, --vcpu-count, --fs-size-vm-mib. There are no implicit defaults.
FROM <name>Looked up as a vers tag first, falling back to treating <name> as a commit id.
FROM <commit-id>Restored directly.
Unlike Docker, there’s no Docker Hub-style public image registry. FROM ubuntu:22.04 doesn’t work out of the box — you either use FROM scratch with a Vers rootfs, or point at a commit someone else has already built and shared (via vers repo fork or a tag id you were given).

A worked example

FROM scratch
ARG VERSION=dev
ENV APP_VERSION=${VERSION} \
    NODE_ENV=production
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . /app
EXPOSE 3000
CMD ["node", "server.js"]
Building this (vers build --mem-size 2048 --vcpu-count 2 --fs-size-vm-mib 4096 -t myapp:prod .) produces:
1

FROM scratch

Fresh VM. No commit yet.
2

ARG / ENV / WORKDIR

Metadata only. Applied to builder state, bakes into every subsequent layer’s cache key.
3

COPY package.json package-lock.json ./

Uploaded; layer committed. Cache keyed on the two files’ contents.
4

RUN npm ci --omit=dev

Executed inside the VM; layer committed.
5

COPY . /app

Every file (minus .dockerignore) uploaded; layer committed. Most likely to invalidate on change, which is why package.json copy came first.
6

EXPOSE / CMD

Metadata. Captured in the build output, no new layer.
7

Tag

Final commit gets tagged myapp:prod.
Rebuilding without changing anything is near-instant — every step hits the cache.

Common patterns

Dependency layer, then source layer

Put COPY of dependency manifests (package.json, go.sum, requirements.txt) before COPY . /app. That way editing source code doesn’t invalidate the dependency install layer.

Using a golden base

FROM golden-base:v1     # resolved as a vers tag
RUN ./setup-app.sh
COPY . /app
CMD ["./serve"]

Dockerfile as the project’s run config

Instead of maintaining vers.toml and a Dockerfile, you can tag the build and use vers run-commit as your default boot path:
vers build -t myapp:latest .
vers run-commit myapp:latest

See also