Overview
Nine weeks ago this series opened with a sentence I’ve been waiting to cash: “I’m building something in this gap. It isn’t ready to show you yet, and I’d rather earn the argument first.” Six posts later the argument is on the table — the missing middle, the PaaS bill at scale, golden paths over headcount, Argo CD and Kargo instead of a homegrown CD, secrets that never touch Git, and previews that promote. Every one of those posts was describing the same piece of software with the product name filed off.
The name is suparship, and today it has a version number: v0.1.0, open source under AGPL-3.0, tagged, built and pullable as ghcr.io/suparcloud/suparship:0.1.0.
Here’s the one-paragraph version. suparship is a platform runtime for teams who live on Kubernetes and want the developer experience of a hosted PaaS — create an app from a form, set its variables and secrets in one place, get a preview environment per pull request, promote to production with a click — without renting a control plane they’ll outgrow or hiring the platform team they can’t afford. It stands on Argo CD for reconciliation, Kargo for promotion, External Secrets Operator for secrets and plain Helm charts for everything an app is. Its whole output is a Git repository you own. If you can run Kubernetes, you can run a great platform. That line was the pitch in post one, and 0.1 is the first time I can point at a tag and say it out loud.
The series, with the names filled in
Posts four through six anonymized every product-specific label so the architecture would have to stand on its own. If you read them, you already know most of suparship’s data model; here’s the decoder ring.
| In the series | In suparship |
|---|---|
platform.example/env-type: preview | suparship.io/env-type: preview |
app.kubernetes.io/managed-by: your-platform | app.kubernetes.io/managed-by: suparship |
platform-store (the per-cluster ClusterSecretStore) | suparship-store |
$PLATFORM_API/projects/acme/apps/hello/previews | https://suparship.acme.com/api/v1/projects/acme/apps/hello/previews |
| “the golden-path layer” | the suparship server, one Go binary plus a React UI |
Everything else — the ApplicationSet with a Git file generator manufacturing preview Applications, the Warehouse watching a registry, the Stage whose promotion is a git commit, the scope-band secrets in an external store — is exactly as described. The only thing I was hiding was the name.
From git push to prod, end to end
The running example has been app hello in project acme with environments staging and prod since post four. Here is what the full loop looks like in 0.1, with the same names.
1. Create the app. A developer picks a template from the gallery, names it hello, and fills in a short form: image, port, replicas — the handful of values the platform team chose to expose for that chart (more on that below). Behind the form, suparship commits the app’s values and descriptor to the GitOps repo and the Argo CD ApplicationSet does the rest. There’s an Advanced view with the raw values for anyone who prefers YAML; it’s the same contract.

2. Set variables and secrets, once. The same app page has one panel for configuration: plain variables and secrets side by side, and both scoped. App-level values apply everywhere — with a banner reminding you that “everywhere” includes production — and each environment overrides what it needs: staging extends the app config with FEATURE_FLAGS and overrides LOG_LEVEL, prod inherits the rest. Secret values go straight to the external store — HashiCorp Vault or 1Password — and only an ExternalSecret lands in Git; variables become a ConfigMap. The chart never learns any of this: it envFroms two well-known names, hello-config and hello-secrets, and the platform owns their contents. The part developers actually use is the resolved view, which shows exactly what reaches each environment and where every key came from, so “why is staging seeing the wrong database” stops being an archaeology exercise.

3. Open a pull request. CI builds and pushes hello:<sha>, then makes one call:
curl -fsS -X POST "$SUPARSHIP_API/projects/acme/apps/hello/previews" \
-H "Authorization: Bearer $SUPARSHIP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"pr-42","imageTag":"'"$GITHUB_SHA"'"}'
That’s an upsert: the first call creates pr-42 as a clone of staging, every later push re-points it at the new tag, and the closed event sends a DELETE. The example workflow is about forty lines, and the token is a project-scoped API token with a fixed role, so a leaked CI secret can’t reach past acme.
4. Merge. Kargo’s Warehouse for hello sees the new tag on the registry and promotes it into staging automatically — staging is the first stage, and the first stage auto-promotes. The promotion is a commit to the GitOps repo that updates the image tag in the env’s values, followed by an Argo CD sync.
5. Ship it. Promotion to prod is a click in the UI or one API call — POST …/apps/hello/promote — and it is gated: it requires a project admin, and it requires staging to be healthy in Argo CD’s eyes. What lands in prod is the same freight, the same image digest, that ran in the preview and in staging. Roll back by promoting the previous freight from the history; pin a stage when you want prod frozen through a launch.

Every step in that loop was a Git commit you can read, made by a control plane whose job is to write boring, deterministic files. Which is the point of the next section.
Any chart is a template, and the form is curated
The thing that makes step one a form instead of a values file is the part of suparship I’d defend hardest, because it’s where the golden-paths post either cashes out or doesn’t.
suparship has no application DSL. A template is a Helm chart — yours, from Git, an OCI registry or an upload — and the platform team curates it once, on the template’s page, without touching the chart. Curation has two halves. Platform overlays are values the platform owns: global, per environment, per cluster, and preview-only. That’s where staging gets a smaller resource baseline than prod, and where ((platform.*)) tokens wire chart values to objects the platform manages — the image tag Kargo will promote, the names of the hello-config ConfigMap and hello-secrets Secret, the routing host, the ingress class. A live effective-values pane shows the merged result as you edit. Developer values are the projection: an ordered list of the chart paths a developer should actually see, each with a title, a type and whether it’s required. One question can mirror into several paths — Port writes both containerPort and service.port — so the form asks what a human means, not what the chart’s shape happens to demand.

Two details matter more than they look. First, only what the developer writes is saved. An untouched field isn’t frozen into the app at today’s default; it keeps tracking the chart and the platform overlays, so when the platform team raises the memory baseline next quarter, every app that never opined on memory moves with it. In the Advanced view the inherited keys are seeded as commented lines — uncomment one to own it. Second, this is a view, not a permission boundary. The API still accepts any key and the “show all platform values” escape hatch is one click away. The form makes the right thing easy; it doesn’t make the other things impossible, and I’d be suspicious of a platform that claimed otherwise at 0.1.
The pay-off is the division of labour post three argued for. A team of two curates a chart once and every app on it inherits the decision. A developer answers four questions and gets a Deployment, a Service, an HTTPRoute, an HPA and the two env objects, all rendered by a chart their platform team can read in full.
Designed for leaving
Post six ended on the uncomfortable question: if the platform disappeared tomorrow, would your apps keep running — and could you keep operating them with nothing but git and kubectl? I had planned a whole post on it. It fits better here, next to the repo where you can check the claims.
The design rule is that suparship is the control plane for inputs and never for runtime. Three consequences fall out of that rule:
- Nothing is named after us. Namespaces, Applications, Warehouses and Stages use configurable patterns that default to your names —
{app},{project}-{app}-preview-{name}— and are tracked by theapp.kubernetes.io/managed-by: suparshiplabel. Adoption is a label; leaving is removing it. There is nosuparship-prefix on your workloads to migrate away from. - The GitOps repo is self-contained. Values,
app.yamldescriptors, the ApplicationSets, the Kargo CRs, theClusterSecretStoreper cluster, theExternalSecretandConfigMapper app-env: all of it is plain YAML in a repo you own. The secrets doc puts it this way: anyone cankubectl applythese, or let Argo CD keep doing it, without ever running suparship again. - The runtime is upstream. Argo CD reconciles, Kargo promotes, ESO syncs secrets, sealed-secrets delivers the store tokens. Delete the suparship deployment and every one of those keeps working on the last commit it was given. You lose the form, the preview API and the click. You do not lose a single running pod, and a promotion is still a
git commityou can make by hand.
The walk-away test isn’t a feature we added; it’s what happens when you build on GitOps and refuse to keep state anywhere else.
What 0.1 is, and what it is not
The house rule of this series is an honest section, and a launch post needs one more than any.
It is a 0.1. The first tagged release, cut this week through a release pipeline that ran for the first time this week. The API will move before 1.0. Stacks — grouping tightly-coupled apps with a shared namespace and override cascade — ship as beta. The in-cluster k8s secret backend is already deprecated in favour of HashiCorp Vault or 1Password. Expect rough edges and file them.
Release trains and weighted canaries are not built. I retracted this in post six and I’m repeating it so the launch doesn’t quietly un-retract it. The design exists; no code implements it. If you need a canary in 0.1, you author the HTTPRoute weights in your own chart.
The promotion gate is health, not analysis. No metric-driven verification. Prod promotes when a project admin says so and staging is green. The auto-promote for the first stage is a small reconciler of our own, standing in for Kargo verification steps that don’t exist yet.
One HTTP surface per app. An app can have as many worker and cron components as you like, but at most one routed one. Two web frontends are two apps, or one stack.
Shared variables are secrets-only. Secrets have an org-admin-owned shared tier at every scope; plain variables today live at the app and environment level. Org-wide non-secret defaults are on the roadmap.
Previews trust your CI. There’s no webhook receiver; the API call from your workflow is the lifecycle. The closed handler is the garbage collector.
It’s AGPL-3.0. That is a deliberate choice for a self-hosted control plane, and it is a choice some organizations will need to read carefully before running it. The generated output — your GitOps repo, your manifests — is yours, unencumbered.
Try it
Two speeds, both from the tag.
Sixty seconds, no cluster. The whole backend runs in-process with seeded demo data, so you can click through every surface:
git clone --branch v0.1.0 https://github.com/suparcloud/suparship && cd suparship
cp .env.example .env
task dev # → http://localhost:5173 (admin@local / admin123)
Nothing deploys in this mode. It exists so you can decide in a minute whether the shape is right.
Fifteen minutes, the real thing. A kind cluster with Argo CD, Kargo, Gitea and a CI runner, Vault, ingress, and a working three-component demo app — React frontend, FastAPI, Postgres — so you can open a PR, watch the preview appear, merge, and promote:
task dev:dns # once per machine: *.localhost → 127.0.0.1
task up # the full dev cluster via Tilt
task demo:shipnotes # second terminal: deploys the demo end to end
The guided tour is docs/try-suparship.md. For a real install — a tooling cluster with the prerequisites, helm install from charts/suparship, configure in the UI, commit the sealed export — start at docs/install.md.
There is no waitlist and no form. The one-page pitch lives at suparship.io; everything else is in the repo. The asks are the open-source ones: star the repo if the series has been useful, open an issue for the first thing that breaks, and tell me which of the “not in 0.1” items above would actually stop you from using it — that list is the roadmap’s input, not a disclaimer.
Where this is going
The next post is the one the plan always ended on: building this in the open — what the roadmap looks like after 0.1, what’s beta and why, and what kind of help would move it fastest. Release trains, verification gates and the addons UI are on it, in roughly that order, and I’d rather argue about the order in public than guess.
Until then, the question I’ve been asking all series still stands, and now you can answer it against real code: you are the two-person platform team in the middle. What’s the first thing you’d need this to do that it doesn’t? Reply, open an issue, or come find me in the repo. Nine weeks of argument were the easy part.
