Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Job Environment

Every Terraform/OpenTofu operation runs as a single-container Kubernetes Job the manager builds from internal/jobs. This page documents that Job’s environment, mounts and fixed fields; the runner’s own environment shaping; and the reserved variable names spec.jobs.env may not set.

Main container environment

NameValueMeaning
TF_IN_AUTOMATION1Tells Terraform/OpenTofu it is running unattended: it skips interactive follow-up hints in its output.
TF_INPUT0Disables interactive prompts; the runner always answers non-interactively.
HOME/captf/workThe runner’s working directory (render.WorkDir), since the image’s real HOME may not be writable.
TMPDIR/tmpThe Job’s /tmp emptyDir, since the image’s root filesystem is read-only.
KUBE_NAMESPACE(the object's namespace)The kubernetes backend’s namespace, so state Secrets land beside the owning object.
CHECKPOINT_DISABLE1Stops Terraform from calling checkpoint-api.hashicorp.com on every command: a pod holding cloud credentials otherwise makes that call and can stall on its timeout when the namespace drops egress silently.
(envFrom)(the identity credential mirror Secret)Mirrors the resolved TerraformClusterIdentity’s provider credentials into the runner’s environment (internal/identity).

spec.jobs.env entries that reuse a reserved name are dropped and reported as an event; a representative Spec with TF_LOG and KUBE_CONFIG_PATH set drops: TF_LOG, KUBE_CONFIG_PATH.

Volumes and mounts

Mount pathRead-onlyMeaning
/captf/bintrueThe runner binary, copied in by the init container.
/captf/workfalseScratch: the generated root, the CLI configuration, plan files, and TF_DATA_DIR.
/tmpfalseGeneral temporary storage (TMPDIR).
/captf/configtrueThe per-run Secret: the generated inputs root, and for a restore Job, the backup’s state chunks (jobs.RestoreChunkDir).
/var/run/captf/credentialstrueThe identity Secret’s credential files, mode 0440: a non-root image user reads them through the pod’s fsGroup.

Fixed Job fields

FieldValueMeaning
backoffLimit0The controller owns retries (the attempt number is in the Job name); the pod itself never retries.
terminationGracePeriodSeconds600SIGTERM lets the runner finish in-flight provider calls and write results before SIGKILL.
activeDeadlineSeconds (default)3600Overridable by spec.jobs.activeDeadlineSeconds.
lockTimeoutSeconds (default)300Overridable by spec.jobs.lockTimeoutSeconds.

Default resources

ContainerKindValues
init (runner copy)requestscpu=10m, memory=32Mi
init (runner copy)limitscpu=100m, memory=64Mi
main (source), when spec.jobs.resources is unsetrequestscpu=250m, memory=512Mi
main (source), when spec.jobs.resources is unsetlimitsmemory=2Gi (no default CPU limit: throttling a slow apply is worse than a slow apply)

Security contexts

ScopeFieldValue
podseccompProfileRuntimeDefault
podfsGroup65532 (so a non-root image user can read the 0440 credential files through the group)
init (runner copy)allowPrivilegeEscalationfalse
init (runner copy)capabilities.dropALL
init (runner copy)runAsNonRoot / runAsUsertrue / 65532
init (runner copy)readOnlyRootFilesystemtrue
main (source)allowPrivilegeEscalationfalse
main (source)capabilities.dropALL
main (source)readOnlyRootFilesystemtrue
main (source)runAsNonRootnot defaulted: images built FROM hashicorp/terraform run as root

Runner command and args

The main container’s command is /captf/bin/runner run. Its args, built by jobs.Build, are:

FlagValueMeaning
--opapply, destroy, refresh, drift, restore or planThe operation this Job runs.
--binThe runtime binary path (render.RuntimePath): the image’s tofu or terraform.
--image(the source image reference)Recorded in events and logs to identify which image ran.
--moduleThe image’s role module (render.ModuleDir).
--providersThe image’s optional provider mirror (render.ProvidersDir); the runner checks whether it exists.
--workdirThe generated root’s parent (render.WorkDir).
--configThe per-run Secret’s mount (jobs.ConfigDir).
--lock-timeout(jobs.DefaultLockTimeoutSeconds, or spec.jobs.lockTimeoutSeconds)sHow long the backend lock acquisition waits before failing.
--stop-timeout(terminationGracePeriodSeconds minus a margin)sHow long the runner has, after SIGTERM, to finish in-flight work and write results before the pod is killed.
--backend-config=secret_suffix(a per-attempt suffix)The kubernetes backend’s Secret name suffix.
--backend-config=namespace(the object’s namespace)The kubernetes backend’s namespace.
--backend-config=in_cluster_configTells the kubernetes backend to use the pod’s in-cluster credentials.
--backend-config=labels(the backend Secret labels, as an HCL object)Labels the backend applies to the state Secrets it manages.
--force-unlock(a stale lock ID)Set only when the controller detected a stale backend lock; the runner force-unlocks it after init.
--guard-deletesSet only for a TerraformCluster apply: the runner stops before a plan that deletes or replaces resources unless allowed.
--inputs-hash(the rendered inputs hash)Paired with –guard-deletes: the hash of the inputs this apply renders.
--allow-deletes-hash(an approved inputs hash)The TerraformCluster’s captf.io/approve-destructive-plan hash, when set: allows a destructive plan for that exact input set.
--expect-plan(an approved plan hash)Under applyPolicy Manual, the TerraformCluster’s captf.io/approve-plan hash the apply must re-plan and match before applying.
--event-object(apiVersion/kind/namespace/name/uid of the owner)Set only when the manager runs with –runner-events: lets the runner report progress as Kubernetes events about the owning object.
--job-name(the Job’s name)Paired with –event-object: the reporting Job’s own name.
--restore-chunks(chunk count)Set only for a restore Job: how many backup state chunks are projected into the config volume.
--restore-resources(managed resource count)Set only for a restore Job: the backup’s recorded managed-resource count; the runner fails the restore if the state list shows none when this is not 0.

What the runner changes before running the runtime

internal/runner.Prepare calls the exported Environ helper to build the runtime’s step environment from the Job’s. Given an environment carrying every credential-bearing and backend-changing variable the identity Secret’s envFrom or the image’s own ENV might set:

TF_IN_AUTOMATION=1 TF_INPUT=0 KUBE_NAMESPACE=default CHECKPOINT_DISABLE=1 TF_LOG=DEBUG TF_VAR_region=us-east-1 TF_WORKSPACE=default TF_CLI_ARGS_apply=-parallelism=1 KUBE_CONFIG_PATH=/tmp/kubeconfig HOME=/root TMPDIR=/tmp AWS_ACCESS_KEY_ID=AKIA...

Environ produces (TMPDIR was set, so it is kept as-is):

AWS_ACCESS_KEY_ID=AKIA... CHECKPOINT_DISABLE=1 HOME=/captf/work KUBE_NAMESPACE=default TF_DATA_DIR=/captf/work/.terraform TF_INPUT=0 TF_IN_AUTOMATION=1 TMPDIR=/tmp

and reports the dropped names: KUBE_CONFIG_PATH, TF_CLI_ARGS_apply, TF_LOG, TF_VAR_region, TF_WORKSPACE.

Rules:

  • TF_DATA_DIR is always forced to <workDir>/.terraform, overriding any inherited value.
  • HOME is always forced to <workDir>.
  • TMPDIR is kept when the Job’s environment already sets it (the Job sets it to /tmp, an emptyDir); otherwise it defaults to <workDir>/tmp, which Prepare creates.
  • TF_CLI_CONFIG_FILE is set only when the image ships a provider mirror (Prepare detects this by statting the image’s providers directory; Environ itself never inspects the filesystem).
  • Every other TF_* and KUBE_* variable is dropped, except TF_IN_AUTOMATION, TF_INPUT and KUBE_NAMESPACE, which the Job already sets and which never carry identity-Secret or image values because a container’s explicit env wins over envFrom.

spec.jobs.env rejected names

spec.jobs.env rejects (drops, with an event) any name starting with TF_ or KUBE_: the runner and the Job above own that namespace (internal/jobs.reservedEnv).

See the image contract for the image side of this: the fixed paths the runner expects, and what the image must not ship.