Kubernetes reconciliation is battle-tested and free, but using it has meant writing an operator.
Inrun opens it to anything that takes a request and returns a response: a template, a service in any language, an API you already run, or Go.
Callers send an intent in their own words and read back a view. You bring the decision; Inrun brings the machinery.
Callers send JSON or YAML in the field names the Blueprint declares, with a token. The gateway checks it against the CRD's admission rules and builds the custom resource. No caller sees a CRD schema.
Templates, a service you already run, or Go: every reconciler gets the same request and returns the same Result. Inrun runs the informers, queues, retries, finalizers, status and every write.
The caller polls the gateway and reads a view: the fields serve.view declares, not the whole object. The custom resource never has to leave the cluster.
inrun init scaffolds a Blueprint, a CRD and a CR. inrun simulate checks it with no cluster; --dev creates a kind cluster to run it on.
A reconciler decides what a resource should become. Watching, queueing, retrying, applying, pruning, status, events and deletion belong to the runtime. Pick the reconciler per CRD, or per serve target; the CRD's API stays the same.
The reconciler contractapiVersion: inrun.dev/v1 kind: Blueprint metadata: name: hello-website spec: crds: website: crdFile: ./manifests/crd.yaml operatorBox: reconcile: onCreate: deployments: - name: "{{ .metadata.name }}" image: "{{ .spec.image }}" emit: status: fields: - path: phase value: Running
// Inrun sends, once per reconcile: { "key": "default/my-webapp", "object": { ... }, "args": { "image": "nginx:1.27", "replicas": 2 } } // Your service answers; Inrun does every write: { "result": "ok", "status": { "phase": "Running" }, "resources": [ { "type": "deployment", "fields": { "name": "my-webapp", "image": "nginx:1.27" } } ] }
func (r *JobRunner) Reconcile(ctx context.Context, req domain.Request) (domain.Result, error) { job := req.Object if req.Finalizing() { return domain.Result{}, r.release(ctx, job) } return domain.Result{ Status: map[string]any{"phase": "Running"}, Resources: r.desired(job, req.Args()), }, nil } // Or keep an existing controller-runtime Reconcile unchanged.
Add serve to a CRD and the gateway accepts intents for it: the fields a caller may send, the tokens that may send them, and the view they read back. The reconciler never reads the CR either: the Blueprint maps between the two sides.
{ "target": "website",
"name": "hello-intent",
"image": "nginx:1.27",
"replicas": 2 }
// → 202, and a URL to poll{ "name": "hello-intent",
"image": "nginx:1.27",
"phase": "Running",
"endpoint": "hello-intent.default.svc.cluster.local" }A custom resource can be a durable state machine: Kubernetes stores it, Inrun retries it until the decision holds, and cleans it up when it's done. Nothing has to be a Deployment.
inrun push publishes a Blueprint to any OCI registry with its simulate and e2e results; inrun inspect shows them before anyone runs it.
migrate.from moves fields to new paths and migrate.to moves resources to a new CRD, with rollback, while callers keep sending the same intents.
The Blueprint is a ConfigMap in the cluster. Remove Inrun and nothing of Inrun's is left in your API server.
Inrun does every write, only for kinds a CRD declares, with RBAC generated from the Blueprint.
Every CR carries Inrun's finalizer. Your reconciler finishes its cleanup first; a timeout keeps it from hanging forever.
inrun simulate runs the runtime's own reconcile pass in memory; inrun e2e checks the same example on kind.
Templates for one target, a remote service or Go for another. The caller picks the target; the API stays one CRD.
Pod readiness, job completion, quantities, cron, semver and more, as functions in every template, plus your own.
The console reads each runtime's /blueprint API: CRDs, how each is reconciled, worker pools, queues, CRs and their status. It creates resources through the gateway, the same path as an intent.
inrun runs them on your machine; the Helm chart runs them as Deployments.
Watches your CRDs and their children, runs each reconcile pass, applies the Result, and exposes /blueprint. Leader-elected.
Takes intents and returns views, and serves the admission and conversion webhooks over TLS. Stateless, several replicas.
A live view of one or more runtimes, and a form that sends intents through the gateway.
A bug report, a docs fix, an example. Built in the open, Apache 2.0.