Cheatsheet
A reference you keep open. Every term, command, and link you'll need during the workshop is on this page. Skim it once, then come back whenever a module uses a word you don't remember.
1.1 Kubernetes in two screens
The shape of the API
| Thing | What it is | Example |
|---|---|---|
| Resource | The smallest API object you can create | Pod, Service |
| Kind | The resource type name | Pod |
| Namespace | A scope for resources that are not cluster-wide | default, crossplane-system |
| Namespaced vs cluster-scoped | Whether a resource lives inside a namespace (most things) or at the cluster level (Node, Namespace, CRDs) | — |
| CRD (CustomResourceDefinition) | A way for controllers to register new kinds with the API server | providers.pkg.crossplane.io |
Commands you'll run
kubectl config current-context # which cluster am I talking to
kubectl get namespaces # list namespaces
kubectl get pods -n <namespace> # pods in a namespace
kubectl get <kind> <name> -o yaml # print one resource as YAML
kubectl describe <kind> <name> -n <ns> # show events + conditions
kubectl logs <pod> -n <namespace> # stream logs
kubectl apply -f <file.yaml> # create or update from YAML
kubectl delete -f <file.yaml> # remove what was applied
kubectl api-resources | grep <word> # find which kinds exist
Put the verb first (kubectl get pods -n foo, not kubectl -n foo get pods). It reads left-to-right and caches better in your shell history.
Inline manifests use single-quoted heredocs so $ isn't expanded by the shell:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: example
EOF
Conditions
Crossplane resources tell you their state through status.conditions. The two you'll see most:
| Condition | Meaning |
|---|---|
type: Ready, status: True | The object is fully reconciled and working |
type: Synced, status: True | Crossplane has the latest spec and is trying to apply it |
If Ready=True and Synced=True, you're done. If either is False, kubectl describe shows you why.
Controllers and the operator pattern
A controller is a long-running Kubernetes program that watches resources of certain kinds and takes action when their declared state and actual state diverge. It runs in a loop: read the spec, compare to reality, reconcile (create / update / delete to match), repeat. This is the operator pattern.
The Deployment controller you've used since day one of Kubernetes works this way: it sees a Deployment with replicas: 3, counts how many Pods exist, creates or deletes Pods to match. The Crossplane core controller is one more controller in the same family — its spec describes things outside the cluster (cloud resources, Helm releases) or things you've defined yourself with an XRD. Same loop, broader scope.
When this workshop says "Crossplane reconciles" or "the provider watches", that's what's happening: a controller reading the spec you applied and making something real match it.
1.2 Tools worth installing
You don't need any of these to finish the workshop — plain kubectl is enough. They make things nicer.
| Tool | What it does | Install |
|---|---|---|
helm | Installs and upgrades Kubernetes applications packaged as charts | brew install helm |
k9s | Full-screen terminal dashboard for watching resources | brew install k9s |
kubectx | Switch between kubeconfig contexts: kubectx prod | brew install kubectx |
kubens | Switch default namespace: kubens crossplane-system | Ships with kubectx |
1.3 Crossplane terminology
The big picture
The problem Crossplane solves. kubectl apply is great for a single team's manifests. But large platforms accumulate dozens of low-level objects (Deployments, Services, ConfigMaps, cloud IAM roles, DNS records, Helm releases) that have to be coordinated for any one user-facing thing — "an app", "a database", "a tenant environment". Crossplane lets you publish your team's own API — a single Application kind, a single Database kind — that fans out into all those low-level objects. Other teams consume your platform with one YAML file instead of dozens.
How it works. Crossplane is a controller (see §1.1 above). It watches two kinds of inputs:
- External-API resources via Providers. A
Providerpackage teaches Crossplane to talk to a specific external API — AWS, GCP, Helm, another Kubernetes cluster. You apply aBucketorReleaseresource; the Provider's controller calls the external API to make it real. - Your own composite kinds via XRDs + Compositions. You declare a new kind with an
XRD(schema) and aComposition(recipe); users apply anXR(an instance of your kind), and Crossplane fans it out into the resources the recipe declares — either native Kubernetes objects directly (in v2) or external resources via Providers.
You'll do the second of these in modules 4-6 of this workshop, then add a Provider in module 7.
Core objects
The Scope column has a subtle split. CRDs and XRDs are themselves cluster-scoped objects — but they define a kind whose own scope (Namespaced vs Cluster) is set in the definition's spec.scope. The "Defines a kind that is" column captures that.
| Object | What it is | Scope (the object itself) | Defines a kind that is |
|---|---|---|---|
| CRD | Kubernetes Custom Resource Definition. Extends the API server with a new kind. Crossplane auto-generates one when you apply an XRD | Cluster | Namespaced or Cluster, per spec.scope |
| Composite Resource Definition (XRD) | Declares the schema of a new composite kind, e.g. XApplication with frontend + backend fields. Applying it makes Crossplane generate the matching CRD | Cluster | Namespaced (v2 default), Cluster, or LegacyCluster (v1 compat with claims), per spec.scope |
| Composite Resource (XR) | An instance of an XRD. Created by the user; Crossplane composes it into the resources its Composition declares | Per the XRD — Namespaced in v2 by default | — |
| Composition | The recipe. "When someone creates this XR, fan it out into these resources." In v2 the composed resources can be plain native Kubernetes objects (ConfigMap, Deployment, …) or Managed Resources from a Provider | Cluster | — |
| Composition function | Pluggable logic the Composition's pipeline runs to produce desired state — function-patch-and-transform, function-go-templating, function-kcl, function-python, etc. A Crossplane package | Cluster | — |
| Provider | A package that teaches Crossplane to manage an external API (AWS, GCP, Kubernetes, Helm, …) | Cluster | — |
| ProviderConfig | Per-Provider runtime config — credentials, target endpoint. v2 namespaced flavor (in *.m.crossplane.io for providers that ship the v2 split) | Namespaced | — |
| ClusterProviderConfig | Per-Provider runtime config — v2's cluster-scoped flavor, shared across namespaces. The "default" config for single-tenant clusters | Cluster | — |
| Managed Resource (MR) | A Kubernetes representation of an external thing the provider reconciles by calling its API (e.g. Release.helm.m.crossplane.io, Bucket.s3.aws.upbound.io) | Per-Provider choice — many v2 providers ship namespaced variants in a *.m.crossplane.io group alongside the legacy cluster-scoped kinds | — |
Concepts overview (docs.crossplane.io)
Naming convention: XR kinds start with X
Crossplane recommends starting the XR kind with X so it cannot collide with downstream API kinds the Composition might emit: Application is an ArgoCD kind, Database ships in several providers, Bucket is an S3 MR. Naming your XR XApplication keeps yours and theirs unambiguous in kubectl get.
The convention is on the kind only. Two follow-ons drop out of that:
- The XRD's
metadata.nameis<plural>.<group>, and the plural derives from the kind — soXApplicationgivesxapplications.workshop.example.io. That's mechanical, not a separate rule. - The XRD object's own kind is always
CompositeResourceDefinition— never X-prefixed. The prefix applies to the kind it defines, not to itself.
What is not part of the convention:
- The
Composition'smetadata.name— it can be anything. This workshop names it the same as the XRD purely for clarity (one Composition per XRD). - The XR instance's
metadata.name—wall-tile,hello-world, whatever you pick. No X prefix.
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition # XRD object — never X-prefixed
metadata:
name: xapplications.workshop.example.io # <plural-of-XR-kind>.<group>
spec:
group: workshop.example.io
names:
kind: XApplication # ← the only place the convention applies
plural: xapplications
---
apiVersion: workshop.example.io/v1alpha1
kind: XApplication # XR instance carries the X-kind…
metadata:
name: wall-tile # …but its own name does not
When to reach for what
| Goal | Use this |
|---|---|
| Create a fresh native Kubernetes object (ConfigMap, Deployment, Service, Job, …) as part of a Composition | A Composition with a function — Crossplane v2 emits the resource directly, no Provider needed |
| Create or manage something outside the cluster (cloud bucket, DNS record, GitHub repo) | Install the matching provider-* package and apply its MR (e.g. Bucket.s3.aws.upbound.io) |
| Take ownership of an existing in-cluster resource without recreating it | provider-kubernetes Object MR with managementPolicies: [Observe] |
| Install a Helm chart through Crossplane | provider-helm Release.helm.m.crossplane.io MR |
| Aim a single Provider at multiple target clusters or accounts | One ProviderConfig per target, with separate credentials in each |
Things that trip people up
MR ≠ XR. A Managed Resource is a 1-to-1 wrapper for a real external object (one Object = one Kubernetes object the provider will apply to a target cluster). A Composite Resource is the user-facing abstraction that fans out into MRs.
ProviderConfig is not Provider. Provider is the package (the thing you install). ProviderConfig is a runtime configuration object the provider watches — it tells the provider how to authenticate.
Namespaced XRs are a v2 feature. In v1, XRs were cluster-scoped and users had to create "claims" to get a namespaced handle on them. In v2 XRs are namespaced by default and claims are optional (the workshop doesn't use them).
Claims vs XRs — why this workshop skips claims
In Crossplane v1, users created a claim (namespaced) and Crossplane created a matching XR (cluster-scoped) for each one. The claim was a handle; the XR was the real thing. This made RBAC work cleanly but introduced a second layer for every user-facing kind.
In Crossplane v2 — what you're using today — XRs are first-class and can be namespaced. You apply an Application XR directly, no claim required. When you read older Crossplane tutorials that talk about Claim objects, that's the v1 pattern; the v2 equivalent is just the XR itself.
Crossplane v1 vs v2
| v1 | v2 | |
|---|---|---|
| XR scope | Cluster-scoped | Namespaced or cluster-scoped |
| Claim layer | Required for namespaced access | Optional (the workshop doesn't use it) |
| Composition style | Patch-and-transform (declarative), Functions (later addition) | Functions are the default |
XRD claimNames | Required for claim-backed XRDs | Not required for claim-free XRDs |
| API group (XR types) | apiextensions.crossplane.io/v1 | apiextensions.crossplane.io/v2 |
v2 migration guide (docs.crossplane.io)
Crossplane vs UXP
Crossplane is the CNCF upstream project — community-maintained, shipped as a Helm chart from charts.crossplane.io/stable.
UXP (Upbound Crossplane) is Upbound's distribution of Crossplane. It's the same core plus a bundled read-only Web UI and some enterprise-only features. It ships from charts.upbound.io/stable and the chart is still called crossplane.
This workshop uses UXP v2 so you also get the Web UI for free — convenient for visualising what you've composed. Everything you learn transfers to upstream Crossplane one-for-one.
A quirk to know: UXP v2 releases are tagged 2.x.y-up.N, which Helm treats as pre-release. That's why helm install for UXP uses --devel; it's not actually a pre-release in the Upbound sense.
1.4 Where to read more
- Crossplane docs — the upstream reference. Prefer the
/latest/pages; they track v2. - Upbound docs — UXP-specific: the Web UI, Upbound Marketplace providers, the
upCLI. - Crossplane Slack — community support.
- Awesome Crossplane — curated links.
When the assistant cites a URL, it comes from one of the two doc sites above. If anyone tells you something different, verify against the source.