Skip to content

feat!: migrate homelab-workspace template toolchain to OpenTofu - #898

Merged
ppat merged 1 commit into
mainfrom
opentofu-migration
Sep 1, 2026
Merged

feat!: migrate homelab-workspace template toolchain to OpenTofu#898
ppat merged 1 commit into
mainfrom
opentofu-migration

Conversation

@homelab-agent-bot

@homelab-agent-bot homelab-agent-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What

Migrates templates/kubernetes/homelab-workspace's authoring, linting, and CI toolchain from HashiCorp Terraform to OpenTofu. Deliberate breaking change to the toolchain, not a compatibility shim — see the BREAKING CHANGE footer on the squashed commit for what a contributor needs to change locally.

Full research, decision packet, and status tracking: #897.

Why / scope

Other Terraform-authored repos (e.g. homelab-ops-terraform) stay on Terraform — this is scoped to this repo's one template. The live homelab Coder deployment's provisioner (owned by homelab-ops-kubernetes-apps, a different repo) is untouched and keeps applying this same template with real Terraform; only this repo's disposable local/CI test control plane now applies it under OpenTofu. Both work because the HCL this template emits is fully backward-compatible with either binary — see #897's linked research for why.

What changed

Area Change
Authoring toolchain mise.toml pins opentofu (installs tofu); pre-commit uses tofu-fmt/tofu-validate; required_providers pins explicit registry.opentofu.org hosts; .terraform.lock.hcl regenerated for real via tofu init -upgrade
Renovate template-terraform-provider.jsontemplate-opentofu-provider.json; tool-version bump rule retargeted at the opentofu mise tool
Commit scopes terraform-provider/terraform-versionopentofu-provider/opentofu-version in commitlint.config.js
CI lint lint.yaml's job now calls ppat/github-workflows's new lint-opentofu.yaml (ppat/github-workflows#631 — pinned to its PR head SHA until that merges, TODO(#897) left in place to re-pin)
Test control plane .github/compose/compose.yaml bind-mounts an OpenTofu binary over the official Coder image's bundled terraform (root-owned, uid-1000 container can't overwrite it in place); test-template.yaml installs that binary via jdx/mise-action, versioned from mise.toml's own pin so it can't drift
Docs CLAUDE.md/DESIGN.md/README.md/TESTING.md updated throughout, including a new named implementation gotcha for the binary-injection mechanism

Verification

  • pre-commit run --all-files clean, including real tofu fmt/tofu validate/tflint against the template.
  • End-to-end, in real GitHub Actions CI (test-template.yaml's integration job — green run), not just locally: the workspace stood up under OpenTofu, pinged 3/3, and answered coder ssh -- env.
  • Two genuine, real-CI-only bugs surfaced and fixed while getting there — neither reproducible locally, neither caught by actionlint/yamllint, both detailed in the squashed commit message:
    1. The first attempt used opentofu/setup-opentofu, which this repo's locked-down Actions allowlist rejects outright at workflow-intake time (startup_failure, zero jobs, generic "workflow file issue"). Fixed by installing via jdx/mise-action instead, already trusted transitively through the shared lint workflows.
    2. CODER_TOFU_BINARY was set via $GITHUB_ENV and used by docker compose up in the same step — $GITHUB_ENV writes only take effect for later steps, never later commands in the step that wrote them. Fixed by resolving it into a local shell variable and exporting it for the same-step use, while still appending to $GITHUB_ENV for the later steps that also need it.
  • actionlint clean on the touched workflow files.

Sequencing note

ppat/github-workflows#631 should land first (or at least be reviewed alongside this one) — this PR's lint.yaml change references its PR head SHA as an interim pin and has a TODO(#897) to re-pin once that PR is tagged on main.

Refs #897.

@homelab-agent-bot
homelab-agent-bot Bot force-pushed the opentofu-migration branch 3 times, most recently from 7b2f551 to 358af94 Compare August 31, 2026 22:45
homelab-agent-bot Bot pushed a commit to ppat/homelab-ops-kubernetes-apps that referenced this pull request Aug 31, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.

Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.

So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.

Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.

The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).

Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.

Refs ppat/coder#897, ppat/coder#898.
homelab-agent-bot Bot pushed a commit to ppat/homelab-ops-kubernetes-apps that referenced this pull request Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.

Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.

So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.

Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.

The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).

Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.

BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.

Refs ppat/coder#897, ppat/coder#898.
Deliberate migration of the authoring/linting/CI toolchain for
templates/kubernetes/homelab-workspace from HashiCorp Terraform to
OpenTofu. The HCL itself needed no change: every construct the
template uses is backward compatible, verified by running
tofu init/fmt/validate/tflint for real rather than assuming it. Full
research backing this decision, and remaining status, is tracked in
issue #897 (three linked private gists cover Coder-provisioner
compatibility, provider-registry/licensing, and language deltas).

Authoring toolchain:
- mise.toml now pins `opentofu` (installs the `tofu` binary) instead
  of `terraform`; .pre-commit-config.yaml uses gruntwork-io/pre-commit's
  `tofu-fmt`/`tofu-validate` hook ids (same pinned revision).
- required_providers now pins explicit `registry.opentofu.org` hosts
  for both providers, removing the tool-dependent implicit registry
  default that would otherwise make the lock file's provider addresses
  depend on which binary ran `init`.
- .terraform.lock.hcl regenerated via `tofu init -upgrade`: same
  versions. coder/coder's hashes are byte-identical (OpenTofu's
  registry points at the same GitHub release artifacts for third-party
  providers); hashicorp/kubernetes's hashes changed even though the
  version didn't move, because OpenTofu rebuilds `hashicorp`-namespace
  providers from source under its own release pipeline rather than
  redistributing HashiCorp's binaries (HashiCorp's registry ToS blocks
  that redistribution).
- Renovate: renamed template-terraform-provider.json to
  template-opentofu-provider.json; exceptions.json's tool-version
  grouping rule now targets the `opentofu` mise tool. The
  terraform-provider Renovate *datasource* name is unrelated to the
  authoring tool and needed no change - it already tracks provider
  registry versions regardless of which tool consumes them.
- commitlint.config.js's allowed scopes swap terraform-provider/
  terraform-version for opentofu-provider/opentofu-version, since this
  is a full toolchain switch, not dual support. Historical commits keep
  their old scope text; only current and future ones follow the new
  vocabulary.

CI and the disposable test control plane:
- The disposable local/CI Coder deployment (.github/compose/compose.yaml,
  test-template.yaml) is the only place this repo controls the binary
  that actually *applies* the template - `coder template push` never
  execs terraform/tofu itself (it's a local, pure-Go HCL parse), and
  Coder's provisioner resolves its binary purely via
  `LookPath("terraform")` plus a numeric version check, with zero
  vendor awareness. The official ghcr.io/coder/coder image ships its
  real Terraform binary at /usr/local/bin/terraform, root-owned 0755,
  in a container that runs as uid 1000 - so compose.yaml now bind-mounts
  an OpenTofu binary directly over that lookup path, since the bind
  mount replaces the mount point at the kernel level regardless of that
  ownership. test-template.yaml sources that binary via
  ppat/homelab-ops-actions/actions/setup-repository-tools - the same
  checkout+mise action every reusable lint workflow in
  ppat/github-workflows already uses - with no mise_toml override, so
  it installs straight from the checked-out mise.toml's own `opentofu`
  pin rather than a second hardcoded version string. That action does
  its own checkout into current/, so every path this job touches after
  that step is prefixed accordingly.

  This wasn't the first attempt. opentofu/setup-opentofu is rejected
  outright by this repo's locked-down Actions allowlist ("selected"
  patterns, not including that action) at workflow-intake time - a
  real failure mode invisible to actionlint, yamllint, or any local
  run, surfacing only as a bare "workflow file issue" with zero jobs
  created. The fix that replaced it, jdx/mise-action called directly,
  worked (it happened to already be allow-listed, transitively, via
  setup-repository-tools's own dependency on it) but broke with this
  repo's established convention of using setup-repository-tools itself
  for exactly this, for no reason beyond expedience - corrected here.
- A second real-CI-only failure, also invisible locally: `echo
  "CODER_TOFU_BINARY=$(...)" >> "$GITHUB_ENV"` immediately followed by
  `docker compose up` in the *same* step silently resolved to an empty
  value, and docker compose failed with an opaque "invalid spec: empty
  section between colons". `$GITHUB_ENV` writes only take effect for
  steps that run *after* the one that wrote them, never for later
  commands within that same step. `CODER_KUBECONFIG` a few lines above
  worked all along for an unrelated reason - it's this step's own
  `env:`, set from the start; the `$GITHUB_ENV` echo next to it exists
  only to propagate it to *later* steps, not to make it usable here.
  Fixed by resolving `CODER_TOFU_BINARY` via `mise which tofu` into a
  local shell variable, `export`ing it so this step's own `docker
  compose up` sees it, and *also* appending it to `$GITHUB_ENV` so the
  later steps that run `docker compose exec`/`logs` - which re-resolve
  the whole compose file, volumes included, on every subcommand - find
  it too.
- Verified end-to-end against a disposable Kind/Compose stack, not just
  wired: a real template push's provisioner log reads "OpenTofu 1.12.6"
  / "OpenTofu has been successfully initialized!" before "Apply
  complete!", `docker compose exec coder terraform version` reports the
  OpenTofu banner, and the resulting workspace came up, pinged 3/3 over
  p2p wireguard, and answered `coder ssh -- env` for real.
- The live homelab Coder deployment's provisioner is a separate repo
  (homelab-ops-kubernetes-apps) and out of scope for this PR - it's
  addressed separately in homelab-ops-kubernetes-apps#3900, since this
  template is now genuinely coupled to OpenTofu's own registry (see
  below), not merely backward-compatible with either tool in the
  abstract.
- lint.yaml's terraform job now points at ppat/github-workflows's new
  lint-opentofu.yaml, pinned to the ppat/github-workflows@v6.1.0
  tagged commit that shipped it (ppat/github-workflows#631). Renamed
  the job/output/detect-changed-files keys involved from terraform-*
  to opentofu-* to match.
- actions/coder-cli-login and actions/coder-template-push needed no
  changes - neither ever touches a terraform/tofu binary, only the
  coder CLI and HTTP calls.
- Fixed a pre-existing, unrelated bug hit while validating locally:
  TESTING.md's documented `coder login --username --password` never
  worked against the pinned Coder CLI version - those flags apply only
  to first-user bootstrap. Replaced with the session-token HTTP login
  flow actions/coder-cli-login already uses.

Docs (CLAUDE.md, DESIGN.md, README.md, TESTING.md) updated throughout
to describe this as the current state, including a new CLAUDE.md
implementation gotcha naming the exact tofu-binary-injection mechanism
and its verification evidence, and a DESIGN.md entry on the
authored-vs-applied-under-OpenTofu asymmetry between this repo's test
control plane and the live deployment.

BREAKING CHANGE: the template's authoring and CI toolchain is now
OpenTofu, not Terraform - anyone working on
templates/kubernetes/homelab-workspace needs `tofu` (via `mise
install`) rather than `terraform` on PATH, and Renovate/commitlint
commit scopes for provider/tool-version bumps are now
opentofu-provider/opentofu-version instead of terraform-provider/
terraform-version.

Refs #897.
homelab-agent-bot Bot pushed a commit to ppat/homelab-ops-kubernetes-apps that referenced this pull request Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.

Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.

So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.

Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.

The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).

Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.

BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.

Refs ppat/coder#897, ppat/coder#898.
@ppat
ppat merged commit d464a67 into main Sep 1, 2026
15 checks passed
@ppat
ppat deleted the opentofu-migration branch September 1, 2026 11:57
ppat added a commit to ppat/homelab-ops-kubernetes-apps that referenced this pull request Sep 1, 2026
`provisionerd` (bundled in the `ghcr.io/coder/coder` image this Deployment runs)
resolves its provisioning binary purely by `LookPath("terraform")` plus a numeric
version check - there is no config surface to point it at a differently named
binary, and no vendor awareness at all. `ppat/coder#897`/`#898` migrated the
`homelab-workspace` template's authoring toolchain to OpenTofu and proved the
mechanism - and its live-deployment implication - end to end in a disposable
test control plane there: bind-mount an OpenTofu binary directly over the
image's own `/usr/local/bin/terraform`.

Tried first, and rejected by this cluster's containerd (2.3.2-k3s2) at pod
creation: a Kubernetes image volume (`coder.volumes[].image`) mounting
`ghcr.io/opentofu/opentofu` directly, subPath-ed to just the `tofu` binary.
"ImageVolumeMountFailed: ... only directory subpath is supported" - file-level
subPath on image volumes isn't supported on this runtime, and directory-level
would replace the whole `/usr/local/bin` with the OpenTofu image's own, which
names the binary `tofu`, not `terraform`; renaming isn't possible without a
copy step.

So this uses a small init container instead, `coder.initContainers` - still
sourcing the binary from the official OpenTofu image, but copying (and
renaming) it into a plain `emptyDir`, whose file-level subPath support is
universal and unaffected by the image-volume limitation above. The main
`coder` container's `volumeMounts` entry then subPath-mounts just that one
file onto `/usr/local/bin/terraform`, read-only - the image's real binary
there is root-owned 0755 and the container runs as uid 1000, so nothing
inside it can overwrite that path in place, but a uid-1000 process can still
execute a root-owned, world-executable file mounted read-only over it. Same
access pattern the disposable test's bind mount already proved works, and
independently re-verified here with the equivalent Docker volume-subpath
mount against the real `ghcr.io/coder/coder:v2.35.6` image as uid 1000:
`terraform version` reports `OpenTofu v1.12.6`.

Rendered with `helm template` against this exact values block before landing:
the initContainer, volumeMount, and volume land on the Deployment's `coder`
container exactly as intended, nothing else in the manifest changes.
`coder.initContainers`/`coder.volumes`/`coder.volumeMounts` were previously
unset here, so nothing existing is replaced.

The `opentofu` image tag is not Renovate-managed: Renovate's `kubernetes`
manager has native support for image-volume references, but only on raw
Pod/Deployment/etc. manifests it parses directly - not on values embedded in a
Flux `HelmRelease`'s opaque `values:` blob (moot here anyway, since this ended
up as an initContainer image reference, not an image volume). Bump it by hand
alongside `ppat/coder`'s `mise.toml` `opentofu` pin, which this is meant to
track (the version this repo's disposable test control plane actually
verified the template against).

Template *authoring* moved to OpenTofu; this is what makes this Deployment's
provisioner the thing that actually *applies* templates with OpenTofu, matching
the disposable test control plane `ppat/coder` already runs both under.

BREAKING CHANGE: this Deployment's provisioner now applies every workspace
template - not just homelab-workspace - with OpenTofu instead of the image's
bundled Terraform. OpenTofu's compatibility promise covers standard HCL, but
any template (present or future) relying on Terraform-specific behavior
outside that promise now behaves differently in this live deployment and
needs re-verification against OpenTofu, the same way ppat/coder#898 verified
homelab-workspace.

Refs ppat/coder#897, ppat/coder#898.

Co-authored-by: Peter Pathirana <peter@latent.io>
ppat added a commit to ppat/homelab-ops-kubernetes-clusters that referenced this pull request Sep 1, 2026
Rolls the coderd Deployment onto a provisioner that applies workspace templates with OpenTofu instead of the
image's bundled Terraform. An init container sources the binary from ghcr.io/opentofu/opentofu, copies it into
an emptyDir as `terraform`, and that file is subPath-mounted read-only over /usr/local/bin/terraform. Coder's
provisioner resolves its binary purely via LookPath("terraform") plus a numeric version check, with no vendor
awareness, so the rename is the whole mechanism.

The module release is breaking: every workspace template in this deployment now applies under OpenTofu, not just
homelab-workspace. OpenTofu's compatibility promise covers standard HCL, so templates that stay inside it are
unaffected; any template relying on Terraform-specific behaviour outside that promise needs re-verification.

Ordering matters and runs in one direction only. ppat/coder's template is coupled to OpenTofu's own registry -
required_providers pins explicit registry.opentofu.org hosts and the lock file was regenerated by `tofu init` -
and real Terraform rejects that registry's signing identity outright:

    Error while installing registry.opentofu.org/coder/coder v2.18.0:
    authentication signature from unknown issuer

So this deployment must be on OpenTofu before ppat/coder publishes its next template version. The reverse
direction is safe and was verified the same way: the template version live today initialises cleanly under
OpenTofu, which rewrites the lock file's registry.terraform.io entries to registry.opentofu.org in the
provisioner's disposable working directory and preserves the version selections.

Rollout is non-disruptive by construction. maxSurge rounds up to 1 and maxUnavailable rounds down to 0 at one
replica, so the new pod must clear its init container and readiness probe before the old one is terminated; a
failed init container leaves the previous pod serving and surfaces as a stuck HelmRelease. Running workspaces
are separate Deployments and are not restarted by a coderd roll.

The ghcr.io/opentofu/opentofu tag is not Renovate-managed - Renovate's kubernetes manager cannot see inside a
Flux HelmRelease's opaque values blob - so it must be bumped by hand in lockstep with ppat/coder's mise.toml
opentofu pin.

Refs ppat/homelab-ops-kubernetes-apps#3900, ppat/coder#897, ppat/coder#898.

Co-authored-by: Peter Pathirana <peter@latent.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant