Shoehorn – Intelligent Developer Platform

  • Author

    Anders Backman

    Senior DevOps Engineer, Shoehorn

    Senior Operations / DevOps Engineer with 15+ years of experience spanning hardware, infrastructure architecture and cloud platforms across Azure, GCP and AWS; currently focused on large-scale Kubernetes environments supporting thousands of microservices.

  • About

    Type
    Blog

Posted on 15 May 2026

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, and 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 is Shoehorn

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 that 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.

1 shoehorn catalog - Shoehorn - Intelligent Developer Platform

What it adds on top of a Kubernetes cluster

Platform engineers ask us this regularly: What does Shoehorn add over kubectl and ArgoCD?

Shoehorn doesn’t replace either of them. It sits at a different layer.

  • kubectl answers “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 questions spanning 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:

2 shoehorn operations - Shoehorn - Intelligent Developer Platform

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 across three clusters appears as a single entity, with visibility into all clusters 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, and interfaces. Start with zero-config and add an entity file only when 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
4 shoehorn entities - Shoehorn - Intelligent Developer Platform

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, and 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 the 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: true for 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:read from /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. Create PAT token from /profile/

3 shoehorn ide configuration - Shoehorn - Intelligent Developer Platform

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 = "../../modules/kubernetes"

  domain            = var.domain
  organization_name = var.organization_name
  organization_slug = var.organization_slug
  admin_email       = var.admin_email

  # Auth
  auth_provider = "okta"
  auth_config = {
    domain              = var.okta_domain
    clientId            = var.okta_client_id
    issuer              = var.okta_issuer
    authorizationServer = "default"
  }

  # Credentials
  credentials = {
    postgres_password      = random_password.postgres_password.result
    db_password            = random_password.db_password.result
    jwt_secret             = random_password.jwt_secret.result
    auth_encryption_key    = random_bytes.auth_encryption_key.base64
    session_encryption_key = random_bytes.session_encryption_key.base64
    valkey_password        = random_password.valkey_password.result
    meilisearch_master_key = random_password.meilisearch_master_key.result
    okta_client_secret     = var.okta_client_secret
    okta_api_token         = var.okta_api_token
  }

  # Bootstrap + Agent
  enable_bootstrap = var.shoehorn_api_key == ""
  deploy_agent     = true
  cluster_id       = var.cluster_id
  cluster_name     = var.cluster_name

  # Okta user/group sync. The module wires `auth.okta.clientSecretRef.key` and
  # `auth.okta.apiTokenSecretRef.key` automatically from the matching keys in
  # the `credentials` map above — no helm_set entry needed for those.
  helm_set = {
    "auth.orgdata.enabled"          = "true"
    "auth.orgdata.providers[0]"     = "okta"
    "auth.orgdata.primaryProvider"  = "okta"
  }

  health_check_protocol = "https"
}

That single block does the following, in order, with no manual steps in between:

  1. Generates and stores all required secrets (database passwords, JWT signing key, encryption keys, search master key) using Terraform random_password and random_bytes providers. Nothing is checked into source control, and no human types a password.
  2. Installs the Shoehorn Helm chart with all five Go microservices, their dependencies, and ingress configuration.
  3. Configures the OIDC provider connection (Okta, Zitadel, or any compliant IdP) and seeds the initial admin user.
  4. 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.
  5. 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 depend on SaaS platforms for authentication, feature flags, and “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 periodically validates its license against our endpoint. 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 an 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 within your cluster, so third-party code can’t access anything 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 add-on as the initial 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 next in line. 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 proactive 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.

Coming soon: one-click install for UpCloud customers

UpCloud customers will be able to deploy Shoehorn onto their managed Kubernetes clusters with a single click: the same terraform apply shown above, wrapped in the kind of one-step UX that makes “spin up a developer platform” indistinguishable from “spin up a database.” One workflow provisions the cluster, runs the module, generates secrets, configures ingress, registers the agent, and populates the catalog.

Have a look

  • Demo: https://demo.shoehorn.dev
  • Docs: https://shoehorn.dev

Open, self-hostable, and built so you don’t have to build the portal before you can use it.

Try out today!

Start your free 14-day trial today and discover why thousands of businesses trust UpCloud

  • Risk-free trial
  • Optimized performance
  • Scalable infrastructure
  • Top-tier security
  • Global availability

Sign up

See also

Announcement about the second Helsinki data center at UpCloud.

FI-HEL2: Second Helsinki data center now available!

New Helsinki data center FI-HEL2 Helsinki data center is here. After launching data centers in Singapore and Amsterdam, our never-ending world tour takes us back […]

Janne Ruostemaa

Editor-in-Chief

UpCloud logo next to Amazon Web Services (AWS) logo with a "VS" icon, representing a comparison guide.

Comparison Guide: UpCloud Vs AWS

With heightened geopolitical dynamics and renewed scrutiny around the EU-U.S. Data Privacy Framework, more organizations are looking to European Cloud Service Providers to ensure stability, […]

Charley Mann

Map of Europe and a Kubernetes logo over Finland, highlighting that Kubernetes Community Day is coming to Helsinki.

We 💜 KCD Helsinki

Last week we attended Helsinki’s first Kubernetes Community Day – one of a global series of locally-defined events that help grow and sustain Kubernetes and […]

Charley Mann

Back to top