The OpenTofu image for deploy jobs e3fd8257ce05
images/tofu: Debian slim with OpenTofu 1.13.1 (checked against its SHA-256) and a provider mirror (hashicorp/google 8.7.0, cloudflare/cloudflare 5.28.0, checked against the registry's signed checksums at build time), installed only from the mirror via /etc/tofu/cli.tfrc. Imported on Earth and pushed to the hub as cas://blob/56cbf25e287c416e0ec480ac3c3a4d518531fb969249f56ca14c17f4a04d3012; andymccall/oxygenhub-gcp's check job passes in it with no network. Closes #56
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
- When
- · 2026-10-15 13:19 UTC
- Signature
- ✔ signed by Claude Code
- Hash
e3fd8257ce0559794a8481fba88dd6ffccf0246c3d3f6eb25dd6c5bbcdc0470d- Depends on
- eb5c4db673e5
- Depended on by
- e0053e047704 e2189ffef8ff 1c9afa928691
- In bookmarks
- main
- In tags
- v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
- Edits
- 5 atoms, 6 tree operations, 5 files
Browse the tree at this change
added images/tofu/Dockerfile +33 −0
| 1 | + | # The OxygenFlow OpenTofu image (oxygenflow#56): OpenTofu and the providers |
|
| 2 | + | # the deploy pipelines use, mirrored inside, so `tofu init` needs no |
|
| 3 | + | # registry. A job reaches a cloud's API only through allow-listed egress |
|
| 4 | + | # (#55); formatting and validation need no network at all. |
|
| 5 | + | ||
| 6 | + | # Download and check OpenTofu, and mirror the providers, with the tools |
|
| 7 | + | # only this stage needs. |
|
| 8 | + | FROM debian:bookworm-slim AS fetch |
|
| 9 | + | ARG TOFU_VERSION |
|
| 10 | + | ARG TOFU_SHA256 |
|
| 11 | + | RUN apt-get update \ |
|
| 12 | + | && apt-get install -y --no-install-recommends ca-certificates curl unzip \ |
|
| 13 | + | && rm -rf /var/lib/apt/lists/* |
|
| 14 | + | RUN curl -fsSL -o /tmp/tofu.zip \ |
|
| 15 | + | "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_linux_amd64.zip" \ |
|
| 16 | + | && echo "${TOFU_SHA256} /tmp/tofu.zip" | sha256sum -c - \ |
|
| 17 | + | && unzip -q /tmp/tofu.zip tofu -d /usr/local/bin \ |
|
| 18 | + | && rm /tmp/tofu.zip |
|
| 19 | + | # The exact provider versions of the pipelines' lock files; `tofu |
|
| 20 | + | # providers mirror` checks each package against the registry's signed |
|
| 21 | + | # checksums as it downloads it. |
|
| 22 | + | COPY mirror.tf /tmp/mirror/ |
|
| 23 | + | RUN cd /tmp/mirror && tofu providers mirror -platform=linux_amd64 /opt/tofu/mirror |
|
| 24 | + | ||
| 25 | + | FROM debian:bookworm-slim |
|
| 26 | + | RUN apt-get update \ |
|
| 27 | + | && apt-get install -y --no-install-recommends ca-certificates \ |
|
| 28 | + | && rm -rf /var/lib/apt/lists/* |
|
| 29 | + | COPY --from=fetch /usr/local/bin/tofu /usr/local/bin/tofu |
|
| 30 | + | COPY --from=fetch /opt/tofu/mirror /opt/tofu/mirror |
|
| 31 | + | # Providers come only from the mirror: a job never reaches a registry. |
|
| 32 | + | COPY cli.tfrc /etc/tofu/cli.tfrc |
|
| 33 | + | ENV TF_CLI_CONFIG_FILE=/etc/tofu/cli.tfrc |
added images/tofu/README.md +46 −0
| 1 | + | # The OxygenFlow OpenTofu image |
|
| 2 | + | ||
| 3 | + | A root file system for deploy jobs (oxygenflow#56): |
|
| 4 | + | ||
| 5 | + | - Debian bookworm, slim, with CA certificates. |
|
| 6 | + | - OpenTofu 1.13.1, checked against its published SHA-256. |
|
| 7 | + | - A provider mirror in `/opt/tofu/mirror`: `hashicorp/google` 8.7.0 and |
|
| 8 | + | `cloudflare/cloudflare` 5.28.0, checked by `tofu providers mirror` |
|
| 9 | + | against the registry's signed checksums when the image is built. |
|
| 10 | + | - `/etc/tofu/cli.tfrc` (set by `TF_CLI_CONFIG_FILE`) installs providers |
|
| 11 | + | from the mirror only. |
|
| 12 | + | ||
| 13 | + | So `tofu init`, `fmt` and `validate` need no network; `plan` and `apply` |
|
| 14 | + | reach the cloud's APIs through allow-listed egress (#55). The guest's |
|
| 15 | + | environment is the pipeline's, so a job sets `TF_CLI_CONFIG_FILE` itself |
|
| 16 | + | (below). |
|
| 17 | + | ||
| 18 | + | ## Building and importing |
|
| 19 | + | ||
| 20 | + | ``` |
|
| 21 | + | images/tofu/build.sh /path/to/oxygenflow-tofu.tar |
|
| 22 | + | scp /path/to/oxygenflow-tofu.tar claude@earth:/home/claude/flow/dl/ |
|
| 23 | + | ssh claude@earth 'cd ~/flow && FLOW_HOME=~/flow bin/flow rootfs import dl/oxygenflow-tofu.tar' |
|
| 24 | + | ``` |
|
| 25 | + | ||
| 26 | + | Pin the printed hash in the pipelines that use it, and push it to the hub |
|
| 27 | + | (`flow rootfs push HASH`) so every runner can fetch it. |
|
| 28 | + | ||
| 29 | + | A provider version in a pipeline's lock file must be in the mirror: bump |
|
| 30 | + | `mirror.tf`, rebuild and re-pin when a lock file changes. |
|
| 31 | + | ||
| 32 | + | ## Using it |
|
| 33 | + | ||
| 34 | + | ```kdl |
|
| 35 | + | sandbox "tofu" { |
|
| 36 | + | runtime "microvm" |
|
| 37 | + | rootfs "cas://blob/<hash printed by flow rootfs import>" |
|
| 38 | + | memory-limit "2GiB" |
|
| 39 | + | cpu-limit 2 |
|
| 40 | + | network "isolated" |
|
| 41 | + | env { |
|
| 42 | + | TF_CLI_CONFIG_FILE "/etc/tofu/cli.tfrc" |
|
| 43 | + | TF_IN_AUTOMATION "1" |
|
| 44 | + | } |
|
| 45 | + | } |
|
| 46 | + | ``` |
added images/tofu/build.sh +16 −0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Build the OxygenFlow OpenTofu image and write it as a root file system |
|
| 3 | + | # tarball for `flow rootfs import`. Rebuild when OpenTofu or a pinned |
|
| 4 | + | # provider version changes (mirror.tf). |
|
| 5 | + | set -eu |
|
| 6 | + | here=$(cd "$(dirname "$0")" && pwd) |
|
| 7 | + | out=${1:-$here/oxygenflow-tofu.tar} |
|
| 8 | + | TOFU_VERSION=1.13.1 |
|
| 9 | + | TOFU_SHA256=8ccbc6f8ee21d2827715f3c6e08a9b3e0209b1e62057c05067ef117e047c1a80 |
|
| 10 | + | ||
| 11 | + | docker build -q -t oxygenflow-tofu \ |
|
| 12 | + | --build-arg TOFU_VERSION=$TOFU_VERSION --build-arg TOFU_SHA256=$TOFU_SHA256 "$here" |
|
| 13 | + | id=$(docker create oxygenflow-tofu) |
|
| 14 | + | docker export "$id" -o "$out" |
|
| 15 | + | docker rm "$id" >/dev/null |
|
| 16 | + | echo "$out" |
added images/tofu/cli.tfrc +8 −0
| 1 | + | provider_installation { |
|
| 2 | + | filesystem_mirror { |
|
| 3 | + | path = "/opt/tofu/mirror" |
|
| 4 | + | include = ["registry.opentofu.org/*/*"] |
|
| 5 | + | } |
|
| 6 | + | # Nothing is fetched from a registry: a provider missing from the |
|
| 7 | + | # mirror fails `tofu init` instead of reaching out. |
|
| 8 | + | } |
added images/tofu/mirror.tf +14 −0
| 1 | + | # The providers the image carries, at the versions the deploy pipelines |
|
| 2 | + | # lock. Bump here, rebuild, re-pin (README.md). |
|
| 3 | + | terraform { |
|
| 4 | + | required_providers { |
|
| 5 | + | google = { |
|
| 6 | + | source = "hashicorp/google" |
|
| 7 | + | version = "8.7.0" |
|
| 8 | + | } |
|
| 9 | + | cloudflare = { |
|
| 10 | + | source = "cloudflare/cloudflare" |
|
| 11 | + | version = "5.28.0" |
|
| 12 | + | } |
|
| 13 | + | } |
|
| 14 | + | } |