Skip to content

The official image runs the same rootform CLI as the release archive for linux/amd64 and linux/arm64. It contains the embedded RF Vocabulary and Dialects, not Terraform, OpenTofu, providers, project source, or external selections. Produce a plan or state JSON before starting the container; OpenTofu users replace terraform with tofu in those commands, and Plan inputs gives the procedure. Use an exact version tag such as ghcr.io/rootform-dev/rootform:0.1.0 or the reviewed multi-platform index digest; there is no moving latest tag. The image has no entrypoint, so put rootform after the image reference in every command.

Check the CLI version

docker run --rm ghcr.io/rootform-dev/rootform:0.1.0 rootform version
Shell

The version must match the release your team reviewed. To pin exact image bytes, replace the version tag with ghcr.io/rootform-dev/rootform@sha256:<index-digest>, using the complete reviewed index digest. For disconnected use, the image must already be present locally.

Run against a project

Mount the directory containing plan.json read-only and run a one-shot analysis:

docker run --rm \
--volume "$PWD:/workspace:ro" \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform run plan.json --no-serve
Shell

The terminal summary reports the input, selected stage, interpreted resource instances, and facts by kind. Status 0 confirms successful analysis, not compliance; rootform check plan.json in the same image evaluates the selected Policies and exits with their verdict. A binary saved plan is not a run input; export it to JSON first.

To keep the Form while the project mount stays read-only, ask for JSON on standard output and let the host shell write the file:

docker run --rm \
--volume "$PWD:/workspace:ro" \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform run plan.json --no-serve --format json > analysis.json
Shell

Saved plans, plan JSON, and state JSON can contain cleartext secrets. Mount only the protected files needed by the run, keep them out of Git and public artifacts, and remove them according to your retention policy. Rootform reads locally, does not contact a provider or cloud, and does not place sensitive values in its Form, reports, SARIF, or Explorer. Its outputs still expose topology and names.

Write a report without changing the input mount

By default the container runs as UID/GID 65532:65532, which may be able to read a host bind mount but not write to it. For a host-owned report directory, run the container as your host user and give Rootform a writable temporary home. Select outputs by extension:

mkdir -p reports
docker run --rm \
--user "$(id -u):$(id -g)" \
--env ROOTFORM_HOME=/tmp/rootform-home \
--volume "$PWD:/workspace:ro" \
--volume "$PWD/reports:/reports" \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform run plan.json --no-serve \
-o /reports/analysis.json -o /reports/report.md -o /reports/explorer.html
Shell

The JSON, Markdown, and self-contained HTML come from one analysis. The HTML export makes no network requests when opened from disk. If writing fails with status 4, check host ownership and mount permissions before retrying; ownership behavior differs across Linux, Docker Desktop, and other runtimes. Do not make the source tree world-writable. For a saved plan, mount both files and add --plan-file plan.tfplan --require-enrichment; a refused pair exits 3.

Keep external packages between runs

A project using only embedded Dialects needs no lock or package volume. ROOTFORM_HOME holds installed OCI Dialects and Policy Packs selected by rootform.lock; a named volume preserves them across containers while the lock stays in the project. Local selections use their recorded project paths and need no package volume. Prepare the locked selection once:

docker volume create rootform-home
docker run --rm \
--volume "$PWD:/workspace:ro" \
--volume rootform-home:/home/rootform/.rootform \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform init . --locked --no-input
Shell

Mount the same volume on later run commands and add --locked. init may acquire only the exact OCI content recorded in the lock; run never acquires packages. Locks and vendored content explains source precedence, and Other CI/CD shows the same preparation in a runner.

Run with vendored content offline

A disconnected run needs the exact rootform.lock and the vendored .rootform/dialects and .rootform/policy-packs directories in the project mount. Prepare and vendor them before disconnecting; the embedded RF Vocabulary and Dialects are already in the image.

Docker must have the chosen image locally before the container starts. --network none isolates the running container but does not stop Docker from pulling a missing image; --pull never makes a missing local image fail before startup. With the image, project, and plan JSON local, this read-only run writes the Form to standard output:

docker run --rm \
--pull never \
--network none \
--read-only \
--cap-drop ALL \
--security-opt no-new-privileges \
--tmpfs /tmp:uid=65532,gid=65532,mode=0700 \
--tmpfs /home/rootform/.rootform:uid=65532,gid=65532,mode=0700 \
--volume "$PWD:/workspace:ro" \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform run plan.json --locked --no-serve --format json > analysis.json
Shell

The temporary directories are writable by the container user, not the host workspace. A damaged or incomplete vendor tree fails closed instead of falling back to the Rootform home or a registry. To check the selection alone, run rootform init . --locked --offline --no-input with the same flags; it verifies local or vendored content and cannot fetch anything missing. Reproduce an analysis offline covers transfer and byte comparison.

Use private registry credentials

Only an explicit init or vendor acquisition needs registry credentials; run never acquires packages. Mount a Docker configuration directory read-only and point DOCKER_CONFIG at that directory, not at config.json itself:

docker run --rm \
--volume "$PWD:/workspace:ro" \
--volume "$HOME/.docker:/run/docker-config:ro" \
--volume rootform-home:/home/rootform/.rootform \
--env DOCKER_CONFIG=/run/docker-config \
--workdir /workspace \
ghcr.io/rootform-dev/rootform:0.1.0 \
rootform init . --locked --no-input
Shell

Rootform reads config.json and chooses credentials for the registry host. A credHelpers or credsStore executable named there must also exist on PATH inside the container; the image does not ship host helpers, and mounting config.json does not mount their binaries. Supply a trusted helper separately or use a protected configuration that does not depend on one. For a private CA, mount its PEM bundle read-only and set SSL_CERT_FILE to its path inside the container. Never bake credentials into the image or copy them into rootform.lock. Registry compatibility explains registry behavior.

Understand the runtime boundary

PropertyValue
Platformslinux/amd64, linux/arm64
Working directory/workspace
User65532:65532
HOME/home/rootform
ROOTFORM_HOME/home/rootform/.rootform
Binary/usr/local/bin/rootform
EntrypointNone; default command is rootform --help
Binary licenseElastic-2.0

run normally serves a loopback interface and opens a browser. In a container, use --no-serve and export HTML for interactive review on the host. Publishing a container port does not change Rootform's loopback bind inside the container. The image carries the binary license, third-party notices, and SPDX SBOM; it does not include external Dialects or Policy Packs, Terraform or OpenTofu, provider binaries, Git, registry credentials, or project source. See security and data handling before moving artifacts beyond the workstation.