> ## 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

> Build a Vers commit from a literal Dockerfile. Each instruction becomes a cached layer.

`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

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
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

<Info>
  `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.
</Info>

| 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 <repo>:<tag>`       | Repo-scoped tag within your organization (e.g. `FROM my-app:v1`).                  |
| `FROM <org>/<repo>:<tag>` | Org-local repo first, then cross-org public repo (e.g. `FROM acme/ubuntu:latest`). |
| `FROM <org>/<repo>`       | Shorthand for `<org>/<repo>:latest`.                                               |
| `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.

<Warning>
  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`
</Warning>

## Examples

### Build from a tagged base

```dockerfile theme={"theme":{"light":"min-light","dark":"min-dark"}}
FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . /app
EXPOSE 3000
CMD ["node", "server.js"]
```

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
vers build -t myapp:prod .
```

### Build from scratch with explicit sizing

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
vers build --mem-size 2048 --vcpu-count 2 --fs-size-vm-mib 4096 .
```

### Compose with other commands

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
# 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

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
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

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
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):

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
# 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

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
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:

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
vers build --keep .        # fails mid-way; VM still running
vers connect $(vers head)  # poke around
vers kill -y               # clean up when done
```

## See also

* [vers commit](/cli-reference/commit) — snapshot a live VM instead
* [vers run-commit](/cli-reference/run-commit) — boot a built commit
* [vers tag](/cli-reference/tag) — name a commit for humans
