
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Platform engineering teams face a persistent bottleneck: developers need cloud resources fast, but granting direct console access creates security risks and configuration drift. Self-service infrastructure with Crossplane solves this by exposing managed cloud services as native Kubernetes APIs, allowing application teams to provision databases, caches, and storage through standard kubectl commands or GitOps workflows. This approach shifts infrastructure from a ticket-based gatekeeper model to a scalable, policy-enforced product that aligns with how modern engineering teams actually work.
What is self-service infrastructure with Crossplane and why use it?
Crossplane extends Kubernetes to manage external infrastructure by treating cloud provider APIs as reconciled resources. Unlike traditional Infrastructure as Code tools that run imperatively during deployment, Crossplane operates on a continuous control loop. If someone manually changes an RDS instance parameter in the AWS console, Crossplane detects the drift and reverts it to match the declared state. For teams already running GitOps with ArgoCD for declarative Kubernetes deployments, this means infrastructure lifecycle management uses the exact same workflow as application deployments.
The critical distinction is the abstraction layer. Platform engineers define Composite Resource Definitions (XRDs) that expose only the parameters developers should touch—database size, engine version, backup retention—while hiding VPC IDs, subnet selection, and IAM role wiring. This prevents the "too many knobs" problem that plagues direct Terraform module usage. When combined with AWS IAM best practices for least-privilege access, you get a system where developers can self-serve without ever holding long-lived cloud credentials.
How do you configure Composite Resource Definitions for safe developer abstractions?
XRDs are the contract between platform teams and application developers. A well-designed XRD exposes intent, not implementation. In practice, I start by identifying the 3–5 parameters that actually vary per environment, then hardcode everything else in the Composition. Here's a production-grade PostgreSQL claim definition:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.db.example.com
spec:
group: db.example.com
names:
kind: XPostgreSQLInstance
plural: xpostgresqlinstances
claimNames:
kind: PostgreSQLInstance
plural: postgresqlinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
storageGB:
type: integer
minimum: 20
maximum: 500
description: "Allocated storage in gigabytes"
engineVersion:
type: string
enum: ["14", "15", "16"]
default: "16"
tier:
type: string
enum: ["dev", "staging", "production"]
description: "Environment tier determines HA and backup config"
required:
- storageGB
- tier Notice the constraints: storage has min/max bounds, engine versions are enumerated, and the tier field drives implicit configuration. Developers never see subnet IDs or security group rules. The corresponding Composition maps these fields to actual AWS RDS resources while injecting platform-managed defaults like encryption keys, monitoring agents, and tagging standards. Always validate XRDs in a staging cluster before promoting; a malformed schema can block all claims in that API group.
Testing XRDs locally before cluster deployment
Use crossplane beta render to dry-run compositions without a live cluster. This catches composition logic errors and missing patches early:
crossplane beta render xr.yaml composition.yaml functions/ \
--output yaml > rendered-output.yaml This command outputs the fully-rendered managed resources that would be created. Review them for correct tagging, proper secret references, and expected parameter mapping. Integrate this step into your CI pipeline alongside CI/CD best practices for small teams to prevent broken abstractions from reaching production.
How does Crossplane compare to Terraform for internal developer platforms?
Teams evaluating self-service infrastructure with Crossplane inevitably ask whether to replace existing Terraform workflows. The answer depends on your operational model, not technical superiority. Both tools solve infrastructure provisioning, but they optimize for different personas and feedback loops.
| Criteria | Crossplane | Terraform |
|---|---|---|
| Control Plane | Kubernetes API (continuous reconciliation) | CLI / CI job (plan/apply cycles) |
| Drift Remediation | Automatic, continuous | Manual or scheduled plan detection |
| Developer Interface | kubectl, Helm, ArgoCD, Kustomize | HCL modules, Atlantis, Spacelift |
| State Management | Kubernetes etcd + provider status | Remote backend (S3, Consul, TF Cloud) |
| Abstraction Model | XRD + Composition (API-first) | Modules + Variables (config-first) |
| Multi-Provider Orchestration | Native single control plane | Workspace/module composition |
| Learning Curve for Devs | Low if K8s-native | Moderate (new DSL + workflow) |
In my experience, Crossplane wins when your team already lives in Kubernetes and wants infrastructure to behave like a platform API. Terraform remains superior for complex multi-cloud networking, legacy brownfield migrations, or teams without dedicated platform engineers. Many organizations successfully run both: Terraform for foundational networking and cluster bootstrapping, Crossplane for application-layer self-service above that foundation.
How do you secure self-service infrastructure with Crossplane in production?
Security in self-service infrastructure with Crossplane hinges on three layers: RBAC scoping, provider credential isolation, and policy enforcement. Never give developers direct access to managed resource kinds like RDSInstance. Instead, grant permissions only to your custom XRD claim types. This ensures all provisioning flows through your validated Composition logic.
- Namespace-scoped RBAC: Bind developer roles to specific namespaces. Use Kubernetes ServiceAccounts tied to CI pipelines rather than shared kubeconfig files. Each team gets its own namespace with claims restricted to their XRD types.
- Provider Config isolation: Create separate ProviderConfigs per environment or team boundary. Store credentials in external secret stores (AWS Secrets Manager, HashiCorp Vault) referenced via the External Secret Store plugin. Never embed credentials in ProviderConfig manifests.
- Policy-as-code validation: Deploy Kyverno or OPA Gatekeeper to enforce tagging standards, region restrictions, and cost guardrails at admission time. Reject claims that exceed budget thresholds or violate compliance requirements before they reach the controller.
- Audit trail automation: Enable Crossplane's built-in event logging and forward to your observability stack. Every reconcile action, status change, and error generates structured events. For SOC 2 environments, map these events to control evidence automatically.
A common mistake is over-permissioning the Crossplane provider service account. Follow the principle of least privilege: if your Composition only creates RDS instances and S3 buckets, the provider IAM role should have zero EC2 or Lambda permissions. Test permission boundaries by attempting forbidden operations in staging first.
How do you integrate Crossplane with existing GitOps and monitoring stacks?
Crossplane's Kubernetes-native design makes GitOps integration straightforward. Store XRDs, Compositions, and ProviderConfigs in your infrastructure repo alongside application manifests. ArgoCD or Flux manages the platform layer; developers submit claims through PRs to their application repos. This separation keeps platform evolution controlled while enabling developer autonomy within defined boundaries.
For observability, deploy the prometheus-operator service monitor for Crossplane metrics. Key alerts include high reconcile error rates, long sync durations, and provider authentication failures. Pair this with monitoring with Prometheus and Grafana dashboards that track claim fulfillment SLAs and resource utilization trends. When building infrastructure as code foundations, ensure your monitoring covers both the provisioning layer and the resulting managed resources.
Handling secrets and connection strings securely
Crossplane writes connection details to Kubernetes Secrets by default. For production, configure the External Secret Store plugin to write directly to AWS Secrets Manager or Vault instead. This prevents sensitive credentials from appearing in etcd backups or kubectl get secret output. Reference these external secrets in your application pods using CSI drivers or secret injection operators. Always rotate provider credentials independently of claim lifecycles.
Implementing Self-Service Infrastructure with Crossplane Successfully
Start small: pick one resource type (typically PostgreSQL or Redis) and one team as early adopters. Build the XRD, Composition, and RBAC bindings together with that team's input. Measure time-to-provision before and after; real adoption requires demonstrable velocity gains. Scale gradually, adding resource types based on actual demand signals rather than speculative completeness.
If your organization needs help designing compliant, developer-friendly platform abstractions or auditing existing Crossplane deployments for security gaps, reach out to discuss your infrastructure platform strategy. Getting the abstraction boundaries right early prevents costly rework and developer trust erosion later.