tfv server
tfv server
Self-hosted pull request automation for Terraform, OpenTofu and Terragrunt. Install it with Docker or Helm.
tfv server is pull request automation for Terraform, OpenTofu and Terragrunt that you host yourself, with Docker or Helm.
On every push it does three things:
- Finds the affected projects, with no config needed.
- Plans each one with the exact tool version it pins.
- Posts the plan as a new comment, showing the exact command to apply each project, plus a check run per project with an Apply button.
Every action goes through per-project permissions and required approvers taken from your Git platform's users and teams. Scheduled drift checks post to Slack, Teams, Discord, Google Chat or any webhook, and tag who last changed each resource.
Quick start
1. Create a GitHub App
- Webhook URL:
https://<host>/events/github - Events: pull request, issue comment, check run
- Permissions: Checks (read & write), Contents (read), Issues (read & write), Pull requests (read & write), Members (read)
- Logo: upload
brand/tfv-avatar.pngunder Display information. Without a logo, GitHub shows the App owner's picture on tfv's comments.
2a. Run it with Docker
docker run -p 8080:8080 -v tfv-data:/data \
-e TFV_GITHUB_APP_ID=123456 \
-e TFV_GITHUB_PRIVATE_KEY_FILE=/run/secrets/app.pem \
-e TFV_GITHUB_WEBHOOK_SECRET_FILE=/run/secrets/webhook \
-e TFV_REPO_ALLOWLIST='github.com/acme/*' \
ghcr.io/marcdomain/tfv-server:0.1.0The image is published at ghcr.io/marcdomain/tfv-server. The _FILE variables point at files inside the container, so mount them too, for example with
-v "$PWD/secrets:/run/secrets:ro".
2b. Or install it on Kubernetes with Helm
kubectl create namespace tfv
kubectl -n tfv create secret generic tfv-github \
--from-file=private-key=app.pem --from-literal=webhook-secret="$WEBHOOK_SECRET"
helm install tfv oci://ghcr.io/marcdomain/charts/tfv-server --version 0.1.0 -n tfv -f values.yaml# values.yaml
github:
appId: "123456"
existingSecret: tfv-github
config:
repoAllowlist: [github.com/acme/*]
requireApplyProject: true # one project per apply: tfv apply -p <project>
httpRoute:
enabled: true
parentRefs: [{ name: public-gateway, namespace: gateway-system }]
hostnames: [tfv.acme.dev]
dashboard:
oidc:
enabled: true
issuer: https://acme.okta.com/oauth2/default
clientId: 0oa1b2c3d4
existingSecret: tfv-okta # key: client-secret
adminGroups: [platform-admins]Every value, with its default, is in charts/tfv-server/values.yaml. The chart itself is published at ghcr.io/marcdomain/charts/tfv-server.
Then:
- Point the GitHub App's webhook at
https://tfv.acme.dev/events/github. - Register
https://tfv.acme.dev/auth/oidc/callbackas the redirect URI with Okta. - Check the install with
helm test tfv -n tfv.
Routing
| Option | Values | Notes |
|---|---|---|
| Gateway API (recommended) | httpRoute.* |
An HTTPRoute attached to your Gateway (Envoy Gateway, Istio, Cilium, NGINX Gateway Fabric, GKE, …) |
| Ingress | ingress.* |
For clusters that still use an Ingress controller |
| None | service.type |
Port-forward, or your own mesh routing |
To expose only the webhooks publicly, narrow the rule, and reach the dashboard through an internal
gateway or kubectl port-forward:
httpRoute:
rules:
- matches: [{ path: { type: PathPrefix, value: /events/ } }]3. Open a pull request
Change a Terraform, OpenTofu or Terragrunt project in a repo on the allowlist. tfv plans it and comments on the PR. From there, use the PR commands.
Next
- PR commands: plan, apply, unlock and Terraform commands
- Rules and permissions:
repos.yaml, approvals, per-command permissions - What gets planned: project detection,
tfv:watch, Terragrunt dependencies - Policy checks and safety: policy rules, the two-person rule, tamper-proof output
- Drift checks: scheduled checks with chat alerts
- Dashboard and API
- Settings: env vars and Helm values
Based on packages/server/README.md and charts/tfv-server/README.md in the tfv repo.
