Skip to content
Back to blog

Self-hosting Shoehorn with one terraform apply

Edit: Now available on Terraform Registry!

We’ve shipped the Shoehorn Terraform modules. The repo lives at terraform-shoehorn-kubernetes, and it takes any Kubernetes cluster you have a kubeconfig for from “empty” to “running Shoehorn with a populated catalog” in a single terraform apply.

Three working examples

Three flavours under examples/, ordered by how much you bite off:

  • basic: Okta auth, chart-deployed Postgres, no agent. Smallest thing that runs. Good for kicking the tires.
  • okta-with-agent: platform plus the Kubernetes discovery agent in a single apply, with Okta user/group sync turned on. The default starting point for most self-hosters.
  • full-stack-okta-github: everything in okta-with-agent, plus a GitHub App for repository discovery, a second GitHub App for Forge workflow execution, ArgoCD GitOps on the agent, and a namespace-scoped cert-manager Issuer. The configuration that runs on demo.shoehorn.dev.

Each example has its own README.md, a terraform.tfvars.example, and a .gitignore so you don’t accidentally commit secrets or state. Copy, fill, apply.

Run it

git clone https://github.com/shoehorn-dev/terraform-shoehorn-kubernetes
cd examples/full-stack-okta-github
cp terraform.tfvars.example terraform.tfvars
# fill in your values
terraform init && terraform apply

After the first apply, log into https://${domain} with your Okta account, create a permanent API key in Settings → API Keys, set shoehorn_api_key in terraform.tfvars, and re-apply. Bootstrap turns off, the permanent key takes over, the agent keeps running.

Caveats

A few things worth knowing before you apply:

  • The Forge GitHub App is a separate install from the discovery app, on purpose. Different scopes, different rate-limit budgets. The repo README covers the gotcha that GitHub App IDs need helm_values, not helm_set, because Helm’s --set coerces numeric strings to integers and the chart schema rejects them.
  • Postgres data survives terraform destroy. The chart’s StatefulSet carries helm.sh/resource-policy: keep. A later apply reattaches the same data. If you want it gone, drop the StatefulSet and PVC by hand. The repo README has the exact commands.
  • session_encryption_key and auth_encryption_key must be base64 of 32 raw bytes. Use random_bytes { length = 32 }.base64. Using random_password { length = 32 } gives you 32 ASCII chars which decode to 24 bytes, and the platform rejects with “encryption key must be 32 bytes”. The examples already get this right.

Where this fits

The Helm chart still works on its own if you’d rather drive it yourself. The Terraform provider + Shoehorn Helmchart still configures a running platform: integrations, IdP group-to-role mappings, agent registration, scorecards, entities, teams. The modules sit on top of both, opinionated, for the case where you want one apply from “nothing” to “running developer platform”.

If you find something off (or really good), open a discussion or simply send us and email.