The run builder

s.run() as a with block gives you a run builder. Each call inside the block adds a command, and on exit they join into one &&-chained RUN.

Compose a multi-command RUN

This recipe clones a repo, builds it in a subdirectory, and records the result:

from __future__ import annotations

from docker_dsl import Stage
from docker_dsl import context as ctx

ctx.register("ref", str)

with Stage("ubuntu:24.04") as s:
    s.workdir("/src")

    with s.run() as r:
        r.git("clone", "https://github.com/example/widget.git", ".")
        r.git("checkout", ctx.ref or "main")

        with r.cd("build"):
            r.cmake("..", build_type="Release")
            r.make("-j$(nproc)")
            r.make("install")

        r.echo("widget built") >> "/var/log/build.txt"  # pyright: ignore[reportUnusedExpression]
        r.echo("build complete") > "/src/STATUS"  # pyright: ignore[reportUnusedExpression]
        r("rm -rf /src/build")

Render it with --ref=v2.0.0:

# syntax=docker/dockerfile:1
FROM ubuntu:24.04 AS base
WORKDIR /src
RUN git clone https://github.com/example/widget.git . \
  && git checkout v2.0.0 \
  && cd build \
  && cmake .. --build-type Release \
  && make -j$(nproc) \
  && make install \
  && cd - \
  && echo "widget built" >> /var/log/build.txt \
  && echo "build complete" > /src/STATUS \
  && rm -rf /src/build

Dynamic commands

Any attribute you access becomes a shell binary, so r.git(...) emits git and r.make(...) emits make. Keyword arguments become flags. build_type="Release" renders --build-type Release, a True value renders a bare --flag, and underscores become hyphens. The shipped builder.pyi stub gives editors completions for common binaries.

For a command the dispatch can’t express, call the builder with a raw string:

r("rm -rf /src/build")

Change directory, then return

r.cd(path) as a with block runs its body in path and restores the previous directory with cd - on exit:

with r.cd("build"):
    r.make("install")

Called as a bare statement, r.cd(path) changes directory for the rest of the RUN without restoring it.

Redirect echo to a file

r.echo(text) builds an echo you redirect with >> to append or > to truncate, with the path on the right:

r.echo("widget built") >> "/var/log/build.txt"   # append
r.echo("build complete") > "/src/STATUS"          # truncate

Fetch helpers

  • r.curl_bash(url, args=(...)) pipes a remote install script into bash over a pinned-TLS curl.
  • r.install(url, target=..., strip=...) downloads a tarball and extracts it.
  • r.fetch_file(url, dest) downloads one file, creating its parent directory.