Day 355, it's beta time
We have pushed the beta several times, but now we feel we’re in a good state.
While its Beta, it also means there are bugs. There are also rough edges and things we already know we’ll change. None of it is what 1.0 will look like, it’s where we are right now. Also we’re sure the product will change with you giving us feedback. If you find something off (or really good!), please spark an discussion here or email us.
We built Shoehorn out of frustration. Some internal developer portals act like frameworks: you install one, then spend a quarter sifting through unmaintained community plugins, writing TypeScript and React, just to make it presentable. Most people running these portals are platform engineers, not frontend developers. So the portal becomes a product they’re stuck maintaining, instead of a tool their developers can just use.
Shoehorn starts from the opposite assumption: you should be able to use it, not develop it. Whether you reach the catalog from the CLI, Terraform, the web UI, or an MCP-connected IDE agent, it’s the same product end to end. You install it, it discovers your stuff, it works.
One view of your whole stack: every workload across every cluster, the cloud resources behind them, ownership, and scorecards. No flipping between cluster dashboards, cloud consoles, and team wikis to answer one question.
The sovereignty story is downstream of that. Because Shoehorn is genuinely self-hostable in one Terraform apply, you can run it on infrastructure you control, in a jurisdiction you choose. That’s why it’s a natural fit for UpCloud customers. But sovereignty isn’t the pitch. Simplicity is. Sovereignty is what you get for free when the product is small enough that one person can run it.
The rest of this post is what that looks like in practice.
What Shoehorn is
Shoehorn is an Intelligent Developer Platform: service catalog, scaffolding engine, scorecards, fuzzy search, multi-tenant by design. Same category as Backstage, Port, Cortex.
The differentiator is operational. We built it for the 95% of teams who don’t have a dedicated platform team to maintain a portal. Opinionated defaults instead of a plugin ecosystem. Auto-discovery from Kubernetes, GitHub, and your IdP instead of hand-written manifests. One install, sensible defaults, working catalog inside an afternoon. One person can run it. That’s the whole product thesis.
Architecturally it’s a small set of Go microservices (api, eventbus, worker, crawler, forge) backed by Postgres 18, Meilisearch, Valkey, and Redpanda. Authentication is OIDC: Zitadel, Okta, or any compliant provider. Multi-tenant isolation is enforced at the database layer using Postgres row-level security, not application-layer filtering, so a forgotten WHERE clause can’t leak data across tenants.
It ships as a Helm chart, and that’s enough if you want to drive it yourself. But for partners and customers who’d rather not assemble the pieces by hand, we ship something more direct.
Are we open-source?
Partially. Our CLI, Terraform provider, Terraform modules, Helm charts, and SDK are all open source. The core platform isn’t yet. We’re not ruling it out, just not rushing it.
What it adds on top of a Kubernetes cluster
Shoehorn doesn’t replace either of them. It sits at a different layer.
kubectlanswers “what’s the state of this resource, in this cluster, right now?” It’s a per-cluster CRUD tool. Brilliant for operations. Useless for “which team owns the payment service?” or “which services are below tier-2 reliability?”, because those questions aren’t about resources, they’re about your organisation.- ArgoCD / Flux answer “is the cluster in sync with the git-declared desired state?” GitOps is the right way to deploy. But ArgoCD doesn’t know what a service is in business terms, what its dependencies are, who’s on call, or whether it has a runbook. It’s a synchronisation engine, not a catalog.
- Shoehorn answers the questions that span clusters, repositories, teams, and time. Where does this service run? Who owns it? Which version is in prod? What’s its scorecard? Where’s the runbook? It’s a catalog plus a workflow layer plus a governance layer, built for people rather than resources.
In day-to-day terms, here’s how the three pieces work together for a typical Shoehorn-on-Kubernetes setup:
The K8s agent: zero-config service discovery
A small (~50m CPU, <150 MB memory) push-based agent runs in each of your clusters. It watches the Kubernetes API, samples metrics-server for CPU/memory aggregates, and watches GitOps CRDs (ArgoCD Applications, Flux Kustomizations and HelmReleases). Every 30 seconds it pushes a batched, read-only summary to the Shoehorn API.
What this gives you, with literally zero config beyond installing the agent:
- Every workload in every cluster shows up in the catalog automatically. New services appear within minutes. Removed services disappear.
- Team ownership is inferred from namespace labels (e.g.
team: payments-team), so there’s no separate manifest to maintain. - GitOps state is tied to catalog entities, so “is this service in sync?” is a property of the entity, not a separate ArgoCD-tab dance.
- Because it’s push-based, the platform never holds kubeconfig credentials for your clusters. Cluster credentials never leave the cluster. (This is a real benefit for SOC 2 / HIPAA / generally-paranoid environments.)
- Multi-cluster works out of the box. One agent per cluster, all reporting into the same catalog. The same service running in three clusters shows up as one entity, with visibility into every cluster it’s running in. No duplicate entries to deduplicate by hand.
For richer metadata than labels can carry, add a single annotation pointing at an entity file:
annotations:
# Relative path, resolved against the repo URL Shoehorn already knows from ArgoCD/Flux/crawler
shoehorn.dev/entityFile: ".shoehorn/catalog.yaml"
# Full URL, when the file lives in a different repo
shoehorn.dev/entityFile: "https://github.com/acme/shared-configs/blob/main/.shoehorn/checkout.yaml"
# Multi-service file with a fragment selector
shoehorn.dev/entityFile: ".shoehorn/platform.yaml#checkout-api" The file gives you full enrichment: relations, links, runbooks, interfaces. Start with zero-config, add an entity file only where the extra detail is worth it.
The CLI: the catalog from the terminal
shoehorn is a single Go binary, available via Homebrew, Scoop, mise, or a direct download. It does what gh does for GitHub: gives the developers who live in their terminal the same product the UI offers, without context-switching.
A flavour of what you can do without leaving your shell:
# Browse the catalog
shoehorn get entities --owner payments-team
shoehorn search "payment"
shoehorn get entity payment-service --scorecard
# Run a Forge workflow (scaffolding) from the terminal
shoehorn forge molds
shoehorn forge run new-go-service --inputs name=billing-api,team=payments-team
# Declarative ops: apply/diff/delete catalog entities like kubectl
shoehorn apply -f service.yaml
shoehorn diff -f service.yaml
shoehorn validate mold ./my-mold The CLI is the same API surface the UI uses, so anything you can do interactively you can script. CI/CD pipelines and developer onboarding scripts use it the same way they use kubectl.
Terraform: the install and the day-2 lifecycle
Terraform isn’t just for the initial install. The Shoehorn Terraform provider (separate repo: terraform-provider-shoehorn) lets you declare catalog entities, teams, ownership, scorecards, and Forge molds the same way you declare any other infrastructure. So your developer platform, the thing that describes your organisation, can be version-controlled, peer-reviewed, and rolled back like any other Terraform state.
Combined with the install module, a partner or platform engineer can describe an entire Shoehorn deployment as code: the platform itself, the org structure, the scorecards, the discovery agent. One terraform apply from a fresh kubeconfig to a running, populated, scored, owned developer platform.
The TL;DR
kubectl is the tool for resources. ArgoCD is the tool for sync. Shoehorn is the tool for the things that span clusters, repos, and teams. The three sit at different layers and work fine together.
MCP support: your developer platform, addressable from an IDE agent
Shoehorn includes a built-in Model Context Protocol server. Every instance exposes an MCP endpoint at /mcp, with a public capability document at /.well-known/mcp for registries and IDE agents to auto-discover.
Three things matter about this for self-hosted teams:
- The agent talks to your Shoehorn. When a developer asks Claude, Cursor, or any MCP-capable IDE “find me the payment service’s runbook” or “which services on payments-team are below tier-2 reliability?”, that query goes straight to your Shoehorn instance. The catalog data, the team mappings, the scorecards: none of it leaves your infrastructure to answer the question. The AI provider sees the question and the answer, but the index lives entirely in your cluster.
- Read-only by design. The v0 MCP surface is annotated
readOnlyHint: truefor every tool. Agents can search the catalog, fetch entity details, look up teams, query scorecards, and read docs. They cannot create, modify, or delete anything. We’ll add write tools deliberately, behind explicit scopes, when it’s clear they’re worth the surface area. - Bounded by personal access tokens. Each developer generates their own PAT scoped to
mcp:readfrom/profile/mcp, with chosen expiry (30/90/365 days or never). The token is bcrypt-hashed at rest; the raw value is shown once. PATs scope to a tenant, so an agent acting on behalf of one tenant’s developer can never see another tenant’s data.
The tool surface today covers catalog entities, teams, scorecards, repositories, workloads, security findings, dependencies, docs, and a few diagnostic tools (zombies, whoami). Enough to make an IDE agent genuinely useful for navigating an internal developer platform; not enough to let it create entities, run Forge molds, or change ownership.
The transport is stateless Streamable HTTP, protocol revision 2025-11-25. The agent on the other side is whichever one you already use.
One terraform apply
Our Terraform modules at terraform-shoehorn-modules turn the entire deployment into a single terraform apply against any Kubernetes cluster you have a kubeconfig for.
module "shoehorn" {
source = "github.com/shoehorn-dev/terraform-shoehorn-modules//modules/kubernetes"
domain = "portal.your-company.com"
organization_name = "Your Company"
organization_slug = "your-company"
admin_email = "platform@your-company.com"
auth_provider = "zitadel"
auth_config = {
issuer = "https://auth.your-company.com"
clientId = var.zitadel_client_id
}
credentials = {
postgres_password = random_password.postgres.result
jwt_secret = random_password.jwt.result
auth_encryption_key = random_bytes.auth_key.base64
session_encryption_key = random_password.session.result
valkey_password = random_password.valkey.result
meilisearch_master_key = random_password.meili.result
}
enable_bootstrap = true
deploy_agent = true
cluster_id = "prod-eu"
cluster_name = "Production EU"
} That single block does the following, in order, with no manual steps in between:
- Generates and stores all required secrets (database passwords, JWT signing key, encryption keys, search master key) using Terraform’s
random_passwordandrandom_bytesproviders. Nothing is checked into source control, and no human types a password. - Installs the Shoehorn Helm chart with all five Go microservices, their dependencies, and ingress configuration.
- Configures the OIDC provider connection (Okta, Zitadel, or any compliant IdP) and seeds the initial admin user.
- Issues a short-lived bootstrap API key, uses it to register the Kubernetes agent for service auto-discovery, then expires it. No long-lived bootstrap credentials hang around.
- Deploys the Kubernetes agent into the same cluster, which immediately starts populating the service catalog.
The output is a working Shoehorn instance reachable at the domain you configured, with the catalog already showing whatever’s running in your cluster.
For a cloud partner, this means a customer can request “give me a developer platform” and the answer is one Terraform apply against a freshly provisioned managed Kubernetes cluster. There’s no bespoke handoff between provisioning the infrastructure and provisioning the platform. From “I want this” to “it’s running” is the same workflow you already use for everything else in your stack.
Why this matters for sovereignty
The deployment-mechanics part is interesting because it’s uninteresting. There’s no clever deployment story to tell. You have a Kubernetes cluster (anywhere, on any provider, in any country) and Shoehorn deploys onto it. The cluster, the database storage, the object storage for backups, the OIDC provider: all of that lives wherever you put your cluster. The platform doesn’t depend on a SaaS control plane and doesn’t ship telemetry. The one outbound call in normal operation is a periodic license check, which we cover below.
This matters for sovereignty because sovereignty is mostly a property of where the data sits and who can compel access to it, not a property of the software itself. A self-hosted platform on infrastructure under your jurisdiction is sovereign. A hosted platform “with a European region” generally isn’t, because the company hosting it is subject to whatever legal regime applies to the parent.
By making Shoehorn fully self-hostable, with a deployment that takes one apply, we’re trying to remove the operational excuse that pushes teams towards the hosted option even when sovereignty matters to them. “We’d love to self-host but we don’t have the bandwidth” is a real constraint. One terraform apply is a reasonable answer to it.
What we deliberately don’t do, and what we’re upfront about
A few things we left out, on purpose:
- No analytics or behavioural telemetry. The platform runs entirely inside your cluster. There’s no analytics endpoint we control. We don’t track which pages your users visit, which services they look at, or how long they spend in the app. None of that data ever leaves your infrastructure.
- No SaaS control plane. Some “self-hosted” platforms still require a SaaS dependency for things like authentication, feature flags, or “premium features.” Shoehorn doesn’t. The only external service the platform talks to in normal operation is your OIDC provider, whichever one you choose.
One thing we do keep, which is worth being explicit about:
A license check. Shoehorn validates its license against our endpoint periodically. The check carries the license identifier and an aggregate count, nothing else: no user data, no service names, no telemetry. Self-hosted instances continue to function during outages or air-gapped operation, and we publish exactly what’s sent. If your threat model genuinely requires zero outbound traffic, talk to us about an offline-license arrangement.
An SDK and addon marketplace are in the works. The goal is to let the community extend the platform without compromising security. Each addon runs in a WebAssembly sandbox inside your cluster, so third-party code can’t reach beyond what the SDK exposes. Config and secrets are injected after install, never bundled with the addon and never committed to its repo. We’ve already built a JIRA-sync addon as the first reference implementation. Shoehorn-built addons ship first, with a community marketplace to follow for discovery. Addons run locally and keep working offline whether or not you can reach the marketplace.
What’s next
A short take on where we’re going. No dates, because we’d rather ship and announce than promise and slip. The order is up for negotiation; we plan the roadmap with customers, not at them. If something below matters to you and isn’t there yet, that’s a conversation we want to have.
- More authentication providers. Okta and Zitadel work today. Keycloak, Authelia, Authentik, and SAML/Entra are next. The self-host audience generally has opinions about their IdP, and we’d rather meet you where you are than push you to ours.
- More Git providers. GitHub is solid; GitLab (cloud and self-managed) and Gitea are the next ones in. For sovereignty-conscious teams running their own Git, we want to make sure Shoehorn enriches your repositories, not just ones hosted in the US.
- More Forge molds out of the box. The scaffolding engine works with any mold you author, but most teams don’t want to start from a blank repo. We’re building a library of opinionated molds.
- More addons. The JIRA sync is the first; more Shoehorn-built addons next, then community submissions through the marketplace. All sandboxed, all local.
The bigger direction, and the one we care most about, is making the platform itself more intelligent, invisibly.
Not a chatbot bolted to a sidebar. We’re building intelligence into the platform itself, surfacing pro-active insights in the workflows you already use. The MCP server is the first piece. More as it ships.
Every bit of this stays consistent with the sovereignty posture. Inference runs inside your infrastructure, against your data. Where a model is involved, the customer chooses where it runs and what gets sent. No mandatory cloud AI dependency. “Intelligent” and “self-hosted” don’t have to be a contradiction.