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:

  1. Finds the affected projects, with no config needed.
  2. Plans each one with the exact tool version it pins.
  3. 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.

The PR comment, Terraform commands, the apply report, and the dashboard. Source: charts/tfv-server/docs/flow.gif

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.png under 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.0

The 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:

  1. Point the GitHub App's webhook at https://tfv.acme.dev/events/github.
  2. Register https://tfv.acme.dev/auth/oidc/callback as the redirect URI with Okta.
  3. 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

Based on packages/server/README.md and charts/tfv-server/README.md in the tfv repo.