This platform can reproduce. In under an hour you grow a complete, sovereign copy — your own Git server, CI, registry, and AI build crew — on a laptop, a cloud, a VPS, or a fleet. This process is called germination. The daughter shares nothing with the parent: no keys, no secrets, no credentials.
Grow one locally
You need four things:
- Nix, with flakes enabled.
- Docker, with about 4 CPUs and 8 GB of memory.
- Free host ports 80 and 443. If they are taken, set
HTTP_PORT/HTTPS_PORT. - One Claude credential:
CLAUDE_CODE_OAUTH_TOKEN(subscription) orANTHROPIC_API_KEY(API billing).
Run claude setup-token on your machine to mint the subscription-backed CLAUDE_CODE_OAUTH_TOKEN. The credential is optional, but without it the build crew sits idle. The daughter's build crew runs the Claude Code CLI inside a pod on the daughter's own cluster — the credential lives on the platform, not on your laptop. Linux is the tested path. macOS works on a best-effort basis.
git clone https://git.open-platform.sh/open-platform/mitosis
cd mitosis
CLAUDE_CODE_OAUTH_TOKEN=<token> nix run .#up
That is the whole install. The first run takes 30-60 minutes and completes unattended:
- Cluster creation (~2 minutes).
- Git server bootstrap (~2 minutes).
- GitOps reconcile and the Postgres cold start (10-20 minutes).
- The cluster builds its own platform images through its own CI (15-25 minutes).
If a run fails, run k3d cluster delete plat and start again. Setup is one-shot by design. It leaves no half-finished state.
The YOUR PLATFORM card
Germination ends with a boxed card that holds every sovereign value. The platform prints the card once and never writes it to a file. The card shows:
- Your domain and the Forgejo admin login:
plat, plus a freshly minted 32-hex password. - The sovereign key path — the age key that seals every secret on your platform. Keep this file safe, forever.
- Reach-it instructions: the
/etc/hostsline, the per-OS command for CA trust, and a port-forward fallback. - Your front door:
https://www.<domain>(the apex redirects there) — a bare app from the template,open-platform/www, born with the platform so the console's "Open" and the forge's "site" link resolve on day one. Make it yours. (FRONT_DOOR=0skips it.) - The recipe for your first app, and the pointer for seeding children.
Note: Germination does not start until you back up the key and acknowledge custody. Set FORK_KEY_BACKUP=<path>, then type YES. For non-interactive runs, set FORK_KEY_ACK=1.
If you germinated without a Claude credential, the card includes the exact sops set + flux reconcile recipe to seal one in later. From here, follow Build Your First App on your own instance.
What germination does
The daughter shares nothing with its parent:
- Mints a fresh age key. The parent's key is never needed and never present. Without your key, the seed's sealed values are inert ciphertext.
- Regenerates every platform secret, sealed to your key: the admin password, tokens, the OAuth app, webhook secrets, registry credentials, and the reviewer's login.
- Restores the system repos from seed bundles and pushes them to your Forgejo: gitops, mcp, agents,
app-template, ci-builder, ci-runner, mitosis, hello. Workflow-file branch protection applies to every one. - Builds its own images and charts through its own CI. It then verifies that your registry serves each tag before it declares success.
- Rewrites identity to your domain everywhere: in-cluster DNS, app URLs, OAuth redirects. This wiki ships in the seed, so your daughter is born with its own copy, with links that point at its own domain.
- Grows the front door last:
open-platform/wwwfromapp-templateas shipped — no crew, no pull request, no Claude spend (the dispatcher is held off while it is made, then adopts it as shipped) — served athttps://www.<domain>with the apex redirecting to it. A door that fails to build never fails the platform: the card says so and names the one-line fix.
Seed children of your own
From a checkout against your running platform, run:
PLAT_PAT=<a Forgejo PAT with repo read> nix run .#seed
This writes seed-<timestamp>-lean.tar.gz and prints its manifest — a holistic integration spec: every bundled repo with its commit and sha256, plus the platform versions the seed integrates (mcp, agents, the CI runner, the k3s and Flux floor) and the acceptance gate that shape answers to. The seed carries no age key, so it leaks nothing usable. PROFILE=full additionally bundles your app repos.
Seeds are also published artifacts. PUBLISH=1 uploads the tarball and its spec to your platform's own package registry (open-platform/seed, a generic package), and anyone growing a child resolves one by version instead of carrying a file:
SEED=latest nix run .#up # newest published seed
SEED=r20260814120000 nix run .#up # or an exact version
Germination verifies every bundle against the spec's sha256 before it builds — a tampered or corrupt seed refuses loudly. The manifest is fetchable on its own (…/api/packages/open-platform/generic/seed/<version>/manifest.yaml), so you can read exactly what a seed integrates before downloading it. The nightly reseed publishes a fresh version only after germinating it clean — the registry only ever serves gate-passed seeds. A seed file handed over on a USB stick still works exactly as before; the registry is distribution, not a dependency.
Grow one on a cloud
One command grows a platform on AWS, Azure, or DigitalOcean — bare VMs, k3s, and the same germination as everywhere else. The IaC is native to each cloud: Terraform for AWS and DigitalOcean, Bicep for Azure. All three provision the identical node — the registry recipe lands before k3s installs, then a one-shot service clones mitosis and germinates. DigitalOcean, end to end, with no domain at all:
cd deploy/do # or deploy/aws; Azure is az group create + az deployment group create
terraform init
terraform apply -var "ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)"
# ...or with a domain you control DNS for:
terraform apply -var domain=plat.example.com -var "ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)"
Zero DNS. Leave domain at its default and the node names itself <ip-with-dashes>.sslip.io from its own public IP at boot — every name under it already resolves to the machine, so the platform is born reachable at https://git.<that> without a DNS record anywhere. Bring a domain when you want a name.
Managed instead? The console at www.open-platform.sh grows platforms with this exact node contract through the DigitalOcean API — + New → platform, ~20-40 minutes, and the card (front doors + admin logins) lands in the console's Platforms pane. Underneath it is everything on this page; the sovereign path above is how you run the same thing on your own account.
The exact lines for each cloud, sizes, and costs are on the front page of deploy/. What you choose and what you get:
- Modes. The default is one VM. Set
ha = truefor three k3s servers with embedded etcd — the platform survives the loss of one node. - The edge is the VM's own IP. The platform's Traefik binds host ports 80, 443, and 2222 (Git over SSH) directly. No cloud load balancer, no tunnel.
- One DNS record finishes it (with your own domain): a wildcard A —
*.<your-domain>→ the node IP. It coversgit.,mcp.,grafana., and every app the crew ships. With the sslip.io default there is nothing to do. - It can report home. With
REPORT_URL+REPORT_TOKENin/etc/open-platform/node.env, the node posts its progress and, at the end, its card or its failure — one small JSON, best-effort. That is how a managed deploy knows the platform is up; the IaC leaves it unset. - Watch it grow with
journalctl -u open-platform-node -f; a clean run germinates in 15-20 minutes. When it is done,/root/PLATFORM.mdholds your next steps and/root/platform-card.txtholds the YOUR PLATFORM card. - First duty: copy the sovereign key (
/root/fork-<domain>.age) off the machine. It is the only key to your platform's secrets, forever.
Note: The platform serves its own CA until a public certificate issuer is wired in. Either trust the CA (the card shows how) or front the domain with Cloudflare — proxy on, SSL mode "Full" — for browser-trusted certificates with zero extra infrastructure.
These stacks are day-0 provisioning. After germination the platform is governed by its own gitops repo, like every other platform grown from this page — don't re-apply the IaC to change a living platform.
Real domain, bare VPS
Prefer to place every piece yourself, use another provider, or keep zero inbound ports? One VPS with 8 GB or more (Ubuntu 24.04), reached through a Cloudflare Tunnel — no inbound ports, no load balancer. The shape:
-
Create the server with cloud-init. The cloud-init writes
/etc/rancher/k3s/registries.yaml, which pointsgit.<domain>athttp://127.0.0.1:31100. It must run before k3s installs with--disable=traefik --disable=servicelb— k3s wires the registry mirror only at install time. -
Create a Cloudflare Tunnel with the public hostname
*.<domain>→ the servicehttp://traefik.kube-system.svc:80. -
Import the ci-runner image once from any Docker machine. A bare k3s box has no Docker, so germinate cannot bootstrap the CI toolchain itself.
Warning: Without this image, the platform stands up but no CI ever runs.
tar -xzf genesis/seed.tar.gz -C /tmp/mitosis-seed git clone /tmp/mitosis-seed/repos/open-platform__ci-runner.bundle /tmp/ci-runner docker build -t git.<domain>/open-platform/ci-runner:0.1.0 /tmp/ci-runner docker save git.<domain>/open-platform/ci-runner:0.1.0 | ssh root@<server-ip> \ 'ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import -' -
Germinate:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml DOMAIN=<domain> CUTOVER_TUNNEL=1 CLAUDE_CODE_OAUTH_TOKEN=<tok> nix run .#germinate— 15-30 minutes on a cold box.
Note: A public browser-trusted certificate issuer is not wired in yet. Import the platform CA on client machines until it lands.
The full runbook, with the cloud-init file and a troubleshooting table, is docs/deploy/hetzner-vps.md.
Add a node
Join a machine to a running platform with three commands on the new machine, and zero commands anywhere else:
- Join the tailnet with routes accepted.
- Write the registry shim.
- Install the k3s agent against the one API endpoint.
The routed fabric — a subnet-routed container network, one stable API address, and a DaemonSet that keeps NIC offload sane — is standing infrastructure. It survives reboots. A new node is Ready in about 35 seconds. The runbook is docs/add-a-node.md.
Why you can trust the seed
CI gates every change to the reproduction path. Every PR that touches the seed, the scripts, or the flake germinates a complete platform from scratch on CI and runs the coldstart gate. A nightly run does the same. The gate's assertions (A1-A11) check that Flux converges, governance policies are live, the webhook is singular and idempotent, the registry mirror serves pulls, and the fork genealogy is recorded.
The nightly adds the end-to-end phase (E1-E8). The gate generates a canary app from app-template. The app must build, preview, pass review, auto-merge, and serve 200 in production, with no human involved. The seed you clone passed that gate. The gate runs on a dedicated host-mode runner; the full assertion table is in Reference.
Lineage
The platform records genealogy three times, in human-readable form:
- The
ORIGINfile appends one line per germination:<domain> germinated-from <parent> <date> seed=<file>. - The seed manifest records a durable
createdFromURL. - Your Forgejo permanently records the migration source on the forked
open-platform/mitosisrepo.
The flagship instance, open-platform.sh, is the root of the tree. Every daughter can trace its way back. Every daughter can seed daughters of its own.
For everyone
Experience
- Build Your First App
- Building with Issues
- Dependency Trees
- Routines
- Orgs and Planning
- Preview Environments
- Connect Your Agent
Operate
Under the hood
Grow
This wiki ships inside open-platform/mitosis (wiki/) — edit it there, not here. Grown by the platform it describes.