Appearance
Author a Blueprint
A Blueprint is a catalog entry; an operator provisions a Resource from one of its published versions. The catalog starts empty. The fastest way to fill it is to register an external OCI catalog as a source and import the Blueprints you need; hand-authoring the manifests yourself is the advanced path covered at the end of this guide.
Prerequisites
- An authenticated session — see Log in with plexctl.
- The platform-level
managerelation, which gates registration and import.
Bind the control-plane URL the steps reference:
shell
export PLEXSPHERE_URL="${PLEXSPHERE_URL:-https://localhost:8080}"Import from a catalog source
1. Register the catalog source
shell
plexctl blueprint catalog add \
--server "${PLEXSPHERE_URL}" \
--name "Platform catalog" \
--ref ghcr.io/plexsphere/catalog:v1.2.3 \
--verify-identity '^https://github\.com/plexsphere/catalog/\.github/workflows/release\.yml@refs/tags/v.*$' \
--verify-issuer https://token.actions.githubusercontent.comPin --verify-identity to the exact repository, workflow file, and tag ref of the catalog's release pipeline, as shown above. A broader pattern such as ^https://github\.com/plexsphere/.*$ accepts a signature from any workflow in the organization — including one an attacker could trigger to sign attacker-controlled content — which defeats the point of pinning the signer.
The command prints the new source's id. Pin the bundle by immutable digest with --digest ghcr.io/plexsphere/catalog@sha256:... in place of --ref, or have the source re-resolve its tag on an interval with --track 15m. Scope the registration to one Domain with --domain-id, and point a private registry at its pull secret with --registry-credential-ref namespace/name. Use --insecure-unsigned in place of the --verify-identity/--verify-issuer pair only for a registry you fully trust.
2. Browse what the source offers
shell
plexctl blueprint catalog browse <source-id> \
--server "${PLEXSPHERE_URL}"Nothing is imported yet — the response lists the slug, version, and provider kinds of each Blueprint the bundle offers.
3. Import the Blueprints you want
Import a chosen subset by slug, or --all to import everything the source offers:
shell
plexctl blueprint import <source-id> managed-postgres managed-redis \
--server "${PLEXSPHERE_URL}"Each requested slug gets one outcome row. A slug already owned by a different source reports conflict; an identical re-import reports unchanged. These are per-Blueprint, not a batch failure, so the command still exits zero.
4. Confirm
shell
plexctl blueprint get <blueprint-id> \
--server "${PLEXSPHERE_URL}" \
--output yamlThe response carries the imported Blueprint with its versions array.
5. Install it on the management clusters
An imported version composes nothing until its XRD and Composition are on the cluster where Crossplane runs. The control plane never applies them, so apply both, at exactly the version you imported, to every management cluster before a Project provisions from it. For a blueprint of the official catalog, take the files from catalog/<slug>/ of github.com/plexsphere/blueprints at the tag the source tracks:
shell
kubectl apply -f catalog/<slug>/xrd.yaml -f catalog/<slug>/composition.yamlUntil the XRD is there, a resource declared from the version stays in Pending. Install the blueprints you offer names the log line that shows it and how to keep the XRD out of a GitOps prune.
Advanced: hand-author a Blueprint
When a Blueprint you need is not published in any catalog, register it and publish its first version from a pair of Crossplane manifests yourself. The registrar is granted owner on the new Blueprint, which confers the publish permission used in step 3. You need three JSON files: the Crossplane XRD, the Composition, and the parameter-schema document. The Composition's compositeTypeRef must name the composite type the XRD declares.
1. Register the Blueprint
shell
plexctl blueprint create \
--server "${PLEXSPHERE_URL}" \
--slug managed-postgres \
--display-name "Managed PostgreSQL" \
--description "A managed PostgreSQL database instance."The command prints the new Blueprint's id. A duplicate slug exits non-zero with blueprint_slug_conflict.
2. Prepare the version files
The parameter-schema file is the canonical document the catalog stores:
json
{
"parameters": [
{ "name": "storage_gb", "type": "integer", "required": true },
{ "name": "high_availability", "type": "boolean", "required": false }
]
}xrd.json and composition.json carry the Crossplane manifests as JSON objects. A minimal pair:
json
{
"apiVersion": "apiextensions.crossplane.io/v2",
"kind": "CompositeResourceDefinition",
"metadata": { "name": "xmanagedpostgres.example.org" },
"spec": { "group": "example.org", "names": { "kind": "XManagedPostgres", "plural": "xmanagedpostgres" } }
}json
{
"apiVersion": "apiextensions.crossplane.io/v1",
"kind": "Composition",
"metadata": { "name": "managed-postgres-composition" },
"spec": { "compositeTypeRef": { "apiVersion": "example.org/v1", "kind": "XManagedPostgres" } }
}3. Publish a version
Use the Blueprint id from step 1:
shell
plexctl blueprint version create \
--server "${PLEXSPHERE_URL}" \
--blueprint-id 0190a8b8-a0c0-7a0a-8a0a-a0a0a0a0a0b0 \
--version 1.0.0 \
--injection-strategy helm-values \
--provider-kinds aws,hetzner \
--xrd-file ./xrd.json \
--composition-file ./composition.json \
--parameter-schema-file ./parameter-schema.jsonThe version is immutable; re-publishing the same --version exits with blueprint_version_exists.
The
ownergrant from step 1 is written asynchronously. If you script register → publish back-to-back and the publish returns403, retry it after a short delay — the grant has not yet propagated.
A published version composes nothing until its XRD and Composition are on the management clusters: apply your xrd.json and composition.json there as step 5 of the import flow above shows.
Do this in the Console
Both hand-authoring steps have a browser equivalent in the Console. Open the Blueprints page under the Blueprints sidebar group.
Step 1 (blueprint create) is the Register Blueprint button above the table. It requires the platform-level manage relation. The dialog takes a kebab-case slug, a display name, an optional description, and a Scope picker that defaults to catalogue-wide and offers every Domain you can read. A duplicate slug is rejected inline on the Slug field as blueprint_slug_conflict without closing the dialog. On success the new Blueprint appears in the list, and you are granted owner on it — the grant that confers publish.
Step 3 (blueprint version create) is the Publish version action in the Blueprint detail masthead. It requires the publish permission the owner grant confers. The dialog takes a version label; a checkbox group over the provider kinds (aws, azure, gcp, hetzner, openstack); an injection strategy (cloud-init-user-data, helm-values, provider-secret); and the XRD, Composition, and parameter-schema documents as three JSON entries you can paste or load from a file. Each document is checked for being a JSON object before it is sent, so a syntax error surfaces on its own field. The server's validation rejections map back to their field — invalid_provider_kind, invalid_injection_strategy, invalid_parameter_schema, and invalid_manifest — and re-publishing a version shows blueprint_version_exists. On success the new version card appears on the Versions tab.
The
ownergrant is written asynchronously, so right after registering a Blueprint the Publish version control — or a submit — may be refused for a moment. The disabled tooltip and the residual denial both say so; retry shortly.