build - Build an image from a Dockerfile

Build an image from a Dockerfile. Each directive runs against the contree API and produces a new image layer; successful layers are materialised as branches named layer:<chain-hash> so re-running the same Dockerfile reuses prior work.

Synopsis

contree build [CONTEXT] [--dockerfile PATH] [--tag NAME[:TAG]]
              [--build-arg K=V ...] [--no-cache] [--timeout SEC]
  • CONTEXT – build context directory (default .).

  • --dockerfile PATH – override the default <CONTEXT>/Dockerfile.

  • --tag NAME[:TAG] – tag the final image via PATCH /v1/images/{uuid}/tag.

  • --build-arg KEY=VALUE – supply a value for an ARG declared in the Dockerfile (repeatable).

  • --no-cache – ignore existing layer:<hash> branches and rebuild.

  • --timeout SEC – per-RUN operation timeout in seconds (default 600).

Help output

$ contree build --help usage: contree build [-h] [--dockerfile PATH] [--tag NAME[:TAG]] [--build-arg KEY=VALUE]                      [--no-cache] [-t TIMEOUT]                      [context] Build an image from a Dockerfile. Reads the Dockerfile at the given path (default ``<CONTEXT>/Dockerfile``) and applies each directive against an isolated build session keyed by the absolute path of the context directory. Successful layers are materialised as branches named ``layer:<chain-hash>`` so that re-running the same Dockerfile reuses prior work. Supported directives (MVP): FROM, RUN, COPY, ADD (local files/dirs and http(s) URLs; no tar auto-extraction), WORKDIR, ENV, ARG, USER. Other Dockerfile directives parse cleanly but are skipped with a warning (CMD, ENTRYPOINT, LABEL, EXPOSE, VOLUME, STOPSIGNAL, MAINTAINER, HEALTHCHECK, ONBUILD, SHELL). positional arguments:   context               Build context directory (default: .) options:   -h, --help            show this help message and exit   --dockerfile PATH     Dockerfile path (default: <context>/Dockerfile) (default: )   --tag NAME[:TAG]      Tag the final image (default: )   --build-arg KEY=VALUE                         Build-time variable (repeatable)   --no-cache            Ignore cached layers and rebuild   -t TIMEOUT, --timeout TIMEOUT                         Timeout in seconds for each RUN step (default: 600) examples:   contree build .   contree build . --tag myimage:latest   contree build --dockerfile ./Dockerfile.test ./app   contree build --build-arg VERSION=1.2 .   contree build --no-cache . for coding agents:   mutating command, may create operations against the API   layer cache is per-context (session keyed by abspath(context))   use --no-cache to bypass cached layers and rebuild from scratch agent note:   Before using this command in an automated workflow, read:     contree agent

Examples

# Simplest build; finds ./Dockerfile, tags the result
contree build . --tag myapp:dev

# Out-of-tree Dockerfile
contree build ./service --dockerfile ./service/Dockerfile.prod --tag svc:prod

# Override build-time variables
contree build . --build-arg VERSION=2.5 --build-arg DEBUG=1

# Force a rebuild ignoring cached layers
contree build . --no-cache --tag myapp:dev

Supported directives (MVP)

Directive

Behaviour

FROM ref[:tag] [AS name]

Resolves the base image. If the tag is not found locally, the build auto-imports it via POST /v1/images/import. AS name is parsed but ignored (multi-stage is Phase 2).

RUN ...

Shell-form (RUN echo hi) or JSON exec-form (RUN ["echo","hi"]). Spawns POST /v1/instances, polls until terminal status, captures the resulting image.

COPY [--chown=...] [--chmod=...] SRC... DEST

Walks local sources relative to the build context, applies .dockerignore, uploads files (with SHA256 dedup), and stages them for the next RUN.

ADD ...

Local paths behave like COPY. https:///http:// URLs are streamed straight from the source socket into POST /v1/files (no temp file on disk); the URL plus its ETag/Last-Modified/Content-MD5 validators are cached so repeat builds skip the download via a conditional HEAD. Tar auto-extraction is not implemented.

WORKDIR /path

Sets the working directory for subsequent directives.

ENV KEY=VALUE ...

Accumulates environment variables passed to every RUN.

ARG NAME[=DEFAULT]

Declares a build-time variable. Overridden by --build-arg.

USER name

Subsequent RUN commands are wrapped in su -s /bin/sh -c '<cmd>' <name>.

CMD, ENTRYPOINT, LABEL, EXPOSE, VOLUME, STOPSIGNAL, MAINTAINER, HEALTHCHECK, ONBUILD, SHELL

Parsed but skipped with a warning.

COPY --from=stage is a Phase 2 feature; in MVP it warns and skips.

Sessions and layer cache

Builds run in a dedicated session keyed by the absolute path of the context directory: build:<sha16(abspath(context))>. Re-running the same Dockerfile in the same context reuses cached layers across invocations of contree build; switching to --no-cache rebuilds everything.

Layers are stored as branches whose names are the chain-hash of:

sha256(parent_layer_hash || state(workdir/env/user/args) || directive || pending_files)

To inspect the resulting branches:

contree session list --filter build:
contree session show

Note

build is project-scoped from the user’s point of view: it does not bind to the agent’s -S <key> session. Passing -S is harmless but does not move that key’s image. After a successful build, attach the result to your normal agent session by tag:

contree build . --tag myapp:dev
contree -S agent_verify use tag:myapp:dev
contree -S agent_verify run -D -- myapp --version

.dockerignore

contree build reads <CONTEXT>/.dockerignore and filters every COPY/ADD walk. Rules are matched in order against POSIX-style paths relative to the context root; the last matching rule wins, so ! re-includes a previously ignored path.

# .dockerignore
**/*.log
.env*
node_modules
!logs/keep.log

Globs:

  • * matches a single path segment (does not cross /).

  • ** matches zero or more path components.

  • ? matches one character.

  • [abc] is a character class.

  • Trailing / matches a directory and everything below it.

The default exclude list from run --file (.git, *.pyc, __pycache__, .venv, node_modules, dist, build, etc.) is always applied on top of .dockerignore.

Variable substitution

$VAR and ${VAR} are expanded in FROM, RUN, COPY/ADD arguments, WORKDIR, ENV values, and USER. The value source is:

  1. --build-arg KEY=VALUE (highest priority for declared ARG names).

  2. ENV directives processed so far.

  3. ARG defaults.

  4. Empty string for unknown names.

End-to-end demo

A small example lives in docs/examples/build-demo/. The Dockerfile exercises FROM, ARG, ENV, WORKDIR, two COPY directives (file and directory), and two RUN directives. A .dockerignore filters log files and __pycache__ from the upload.

% docs/examples/build-demo/Dockerfile
FROM python:3.12-alpine

ARG GREETING=hello
ENV APP_GREETING=${GREETING}

WORKDIR /app

COPY hello.py /app/hello.py
COPY src /app/src
ADD https://github.com/nebius/contree-cli/archive/refs/heads/master.zip /tmp/contree-cli.zip

RUN python -c "import sys; print('python', sys.version)"
RUN python -m zipfile -e /tmp/contree-cli.zip /opt/
RUN pip install --no-cache-dir /opt/contree-cli-master
RUN contree --help | head -20
RUN python /app/hello.py

The ADD line streams the zip straight from GitHub into the contree API (no local temp file). The subsequent RUN steps unpack it, pip install the project, and prove the installed contree binary works inside the built image.

% docs/examples/build-demo/.dockerignore
**/*.log
**/__pycache__
.env*

Build and tag it:

contree build docs/examples/build-demo --tag contree-cli-build-demo:latest

Expected output (truncated):

[INFO] RUN spawned op=019e... RUN python -c "import sys; print('python', sys.version)"
[INFO] stdout:
python 3.12.13 ...

[INFO] RUN spawned op=019e... RUN python /app/hello.py
[INFO] stdout:
+---------------+
|     hello     |
| contree build |
+---------------+

[INFO] tagged <uuid> as contree-cli-build-demo:latest
IMAGE                                 TAG                            SESSION
<uuid>                                contree-cli-build-demo:latest  build:<sha16>

Re-running the same command without --no-cache produces layer cache hits, and the ADD URL step short-circuits at the HEAD probe (look for URL cache hit (HEAD validators match) in the log) – no body download, no upload.

See also