Skip to main content
Version: main

What is kro?

kro (Kube Resource Orchestrator) lets you turn a set of Kubernetes resources into a reusable API. You define the API schema, describe the resources behind it in YAML, and connect them with CEL expressions. kro turns that definition into a CRD, watches for instances of that API, and reconciles the underlying resources for each one.

A ResourceGraphDefinition (RGD) is the blueprint for a custom API: it describes the interface users work with and the resources each instance should produce. kro validates that definition before it ever reconciles an instance, catching schema errors, invalid expressions, and broken references before they become runtime failures.

kro
ConfigMapv1
Buckets3.services.k8s.aws/v1alpha1
Deploymentapps/v1
Servicev1
Autoscalerautoscaling/v2
Ingressnetworking.k8s.io/v1

How it works

A ResourceGraphDefinition has two parts: a schema that defines your API surface (the fields users fill in), and resource templates that reference those fields with CEL expressions. kro parses the expressions, infers the dependency graph, generates a CRD, and stands up a controller - all at runtime.

Users create
apiVersion: kro.run/v1alpha1
kind: WebApp
metadata:
  name: my-app
spec:
  image: nginx
  bucketName: my-app-assets
User sees
apiVersion: kro.run/v1alpha1
kind: WebApp
metadata:
  name: my-app
spec:
  image: nginx
  bucketName: my-app-assets
status:
  bucketArn: arn:aws:s3:::my-app-assets
  endpoint: my-app.example.com
spec:
  schema:
    kind: WebApp
    spec:
      image: string | default=nginx
      replicas: integer | default=1
      bucketName: string | required=true
    status:
      bucketArn: ${bucket.status.arn}
      endpoint: ${service.status.endpoint}
kro creates
ConfigMapv1
Buckets3.services.k8s.aws/v1alpha1
Deploymentapps/v1
Servicev1
spec:
  resources:
    - id: config
      template:
        kind: ConfigMap
        # ...
        name: ${schema.metadata.name}-config
    - id: bucket
      template:
        kind: Bucket
        # ...
        name: ${schema.spec.bucketName}
    - id: deployment
      template:
        kind: Deployment
        # ...
        image: ${schema.spec.image}
        env: ${bucket.status.arn}
    - id: service
      template:
        kind: Service
        # ...
        name: ${deployment.metadata.name}

In practice

Once the definition is installed, users work with the generated API like any other Kubernetes resource.

apiVersion: kro.run/v1alpha1
kind: WebApp
metadata:
name: my-app
spec:
image: nginx
bucketName: my-app-assets
$ kubectl get webapp my-app -o yaml
status:
bucketArn: arn:aws:s3:::my-app-assets

$ kubectl get configmap,bucket,deploy,svc
NAME AGE
configmap/my-app-config 45s

NAME AGE
bucket.s3.services.k8s.aws/my-app-assets 45s

NAME READY AGE
deployment.apps/my-app 1/1 45s

NAME TYPE PORT(S)
service/my-app ClusterIP 80/TCP

Users apply one WebApp. kro creates the ConfigMap, Bucket, Deployment, and Service, and writes useful outputs like bucketArn back onto the same object.

The definition behind it

A ResourceGraphDefinition defines the API under spec.schema (see Simple Schema) and the backing resources under spec.resources. CEL expressions (${}) are what tie those two parts together (this example uses S3 via ACK, but kro works with any Kubernetes resource - native or CRD. For example, Azure Blob Storage via ASO or GCP Cloud Storage via Config Connector).

webapp-rgd.kro
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
  name: webapp
spec:
  schema:
    apiVersion: v1alpha1
    kind: WebApp
    spec:
      image: string | default=nginx
      bucketName: string
    status:
      bucketArn: ${bucket.status.arn}

  resources:
    - id: config
      template:
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: ${schema.metadata.name}-config
        data:
          APP_NAME: ${schema.metadata.name}

    - id: bucket
      template:
        apiVersion: s3.services.k8s.aws/v1alpha1
        kind: Bucket
        metadata:
          name: ${schema.spec.bucketName}
        spec:
          name: ${schema.spec.bucketName}

    - id: deployment
      template:
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: ${schema.metadata.name}
        spec:
          selector:
            matchLabels:
              app: ${schema.metadata.name}
          template:
            metadata:
              labels:
                app: ${schema.metadata.name}
            spec:
              containers:
                - name: app
                  image: ${schema.spec.image}
                  envFrom:
                    - configMapRef:
                        name: ${config.metadata.name}
                  env:
                    - name: BUCKET_ARN
                      value: ${bucket.status.arn}

    - id: service
      template:
        apiVersion: v1
        kind: Service
        metadata:
          name: ${deployment.metadata.name}
        spec:
          selector: ${deployment.spec.selector.matchLabels}

bucket.status.arn does not exist when you apply this definition. kro waits for it before reconciling deployment, and it knows service comes after deployment because the selector is derived from ${deployment.spec.selector.matchLabels}.

What kro handles for you

Once the graph is defined, kro takes care of the mechanics that usually end up in custom controller code.

image: string | default=nginx
replicas: integer | default=1 minimum=0
bucketName: string | required=true
monitoring: boolean | default=false

SimpleSchema

Define your API schema inline — types, defaults, constraints, and validation in a single readable line. No OpenAPI boilerplate.

Schema docs
Bucket
status.arnarn:aws:s3:::my-bucket
Deployment
env.BUCKET_ARNarn:aws:s3:::my-bucket

Wires data that doesn't exist yet

Reference status fields from resources that haven't been created. kro waits for the data to exist, then wires it into dependent resources.

CEL expressions
You write (any order)
Service
Bucket
Deployment
kro orders
Bucket
Deployment
Service

Infers ordering from expressions

You never declare resource order. kro reads your CEL expressions and builds the dependency graph automatically.

Dependency ordering
Deployment
Service
enableMonitoring
ServiceMonitor
AlertRule

Conditional resources

Include or exclude entire subgraphs based on any CEL expression. When a condition is false, the resource and everything that depends on it are skipped.

Conditional resources
Pod
forEach: ${lists.range(3)}
Pod-0
Pod-1
Pod-2

One template, many resources

forEach expands a single resource template into multiple resources from a list or range. Define once, create N.

Collections
Always terminates
No infinite loops possible
No side effects
No network calls, no file I/O
Type-checked at apply
Errors caught before anything runs
Auditable
Prove what a definition does

Non-Turing complete by design

CEL always terminates, has no side effects, and is type-checked at apply time. You can prove what your definitions do.

Type checking

Get started

Need help or want to contribute? Join #kro on Kubernetes Slack, browse GitHub, or read Contributing.

Brought to you with ♥ by SIG Cloud Provider