How do you build a custom Crossplane Provider using the Upjet framework?
Quick Answer
Upjet is a code generation framework that creates Crossplane providers from existing Terraform providers. You configure resource mappings in a provider-config repository, run the Upjet code generator to produce CRDs and controllers, then build and publish the provider as an OCI image that Crossplane installs as a standard provider package.
Detailed Answer
Building a custom Crossplane Provider with Upjet allows organizations to extend Crossplane's control plane capabilities by wrapping existing Terraform providers with Kubernetes-native APIs. This approach is significantly faster than writing a provider from scratch because Upjet generates the CRD schemas, controller reconciliation logic, and conversion functions from the Terraform provider's schema. At a company running services like payments-api and order-processing-service, a platform team might build an Upjet-based provider to manage internal services such as a proprietary message queue or a custom database provisioning system that already has a Terraform provider.
The process begins by scaffolding a new provider repository using the upjet-provider-template. This template contains the generator pipeline configuration, Makefile targets for code generation, and the directory structure for resource configurations. You configure the Terraform provider source and version in the generator config, then define which Terraform resources and data sources should be exposed as Crossplane managed resources. Each resource configuration specifies the API group, kind name, references to other resources (for automatic cross-resource dependency resolution), and sensitive field paths that Crossplane should store in Kubernetes Secrets rather than in the managed resource spec.
The code generation phase is where Upjet does the heavy lifting. Running make generate triggers a pipeline that reads the Terraform provider schema, applies your resource configurations, and produces Go types for CRDs, zz_controller.go files with reconciliation logic, and conversion functions between the Crossplane API types and the underlying Terraform resource state. Upjet handles complexities like nested block flattening, reference resolution for cross-resource dependencies, and late initialization of server-side defaults. For the payments-api team, this means they get a PaymentsQueue custom resource that can reference a VPC managed resource by name instead of hardcoding IDs.
After generation, you build the provider binary and package it as an OCI image. The Makefile includes targets for building multi-architecture images and pushing them to a container registry. The provider package includes a crossplane.yaml manifest that declares the provider's CRDs and controller image. You install the provider into your Crossplane control plane using a Provider resource that references the OCI image URL. Crossplane's package manager pulls the image, installs the CRDs, and starts the controller pod.
Production considerations for Upjet-based providers include version pinning the underlying Terraform provider to prevent unexpected schema changes during regeneration, implementing comprehensive end-to-end tests that provision real resources against a sandbox environment, configuring provider credentials using Crossplane's ProviderConfig resources with Kubernetes Secrets or external secret stores, and monitoring the controller's reconciliation metrics. When inventory-sync or checkout-service teams consume the custom provider's resources, they should see the same declarative experience as with official Crossplane providers, with status conditions reporting readiness and connection details published to Secrets.