---
title: "Provision with OpenTofu"
url: "https://docs.caracal.run/v1.0/operations/opentofu/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/operations/opentofu.md"
description: "Use the provider-neutral bootstrap and Helm wrapper modules within their implemented boundaries."
page_type: "workflow"
concepts: []
requires: []
---

# Provision with OpenTofu

Canonical URL: https://docs.caracal.run/v1.0/operations/opentofu/
Markdown URL: https://docs.caracal.run/markdown/v1.0/operations/opentofu.md
Description: Use the provider-neutral bootstrap and Helm wrapper modules within their implemented boundaries.
Page type: workflow
Concepts: none
Requires: none

---

`caracalHost` renders cloud-init; it does not create a VM. `caracalStack` creates a namespace, optionally a Secret, and a Helm release; it does not create a cluster, Postgres, Redis, ingress controller, or secret manager.

## Prerequisites

Use OpenTofu `>=1.8`. Kubernetes also requires version `>=1.30`, kubeconfig access, external dependencies, and a complete runtime Secret.

## Kubernetes Procedure

1. Copy the production tfvars example outside source control.
2. Pin `chartVersion`, set database and Redis hosts, and deliver `caracal-runtime` before apply.
3. Add reviewed chart overlays through `extraValues`.
4. Run:

```bash
cd infra/tofu/envs/production
tofu init
tofu plan -out caracal.plan
tofu apply caracal.plan
```

The module waits for Jobs and uses atomic Helm behavior by default. Remote state and locking are operator responsibilities; never put plaintext runtime secrets in variables or state.

## VM Procedure

Attach `caracalHost.userData` or `userDataBase64` to an operator-owned VM. It installs Docker when absent, installs a pinned release, writes non-secret overrides, and starts Caracal. Runtime secrets are generated on the host.

The packaged stack publishes every port on loopback, so a host without a proxy serves no external traffic. Set `tlsProxy` to terminate HTTPS in front of it:

```hcl
tlsProxy = {
  email  = "ops@example.com"
  routes = {
    "console.example.com" = "web"
    "sts.example.com"     = "sts"
  }
}
```

The console origin, the trusted-proxy flag, and the STS issuer are derived from these routes, so they cannot drift from the names the proxy terminates. Certificate issuance needs inbound 80 and 443 open in your cloud firewall, and every hostname must already resolve to the VM. `proxyImage` is pinned by digest; override it to track a different proxy.

## Verify and Recover

Run `bash infra/tofu/scripts/validate.sh` in a checkout. After apply, verify Jobs, readiness, and audit evidence; on a VM run `caracal status --ready`. Use Helm revision rollback only when schema-compatible. Restore VM data and secrets separately before rebuilding.

## Next Step

Read [Deploy with Helm](/v1.0/operations/kubernetes-helm/) or [Deploy with Docker Compose](/v1.0/operations/docker-compose/).
