Provisioning an S3 bucket, IAM role, VPC, or RDS instance is usually not the part that slows AWS teams down. The friction comes from the operating model around those resources: who is allowed to request them, which encryption, networking, tagging, backup, and retention defaults are non-negotiable, how changes are reviewed, how console drift is found, and why a short-lived development environment can still require a queue of platform tickets. Crossplane tackles that problem by making Kubernetes a control plane for cloud infrastructure. Instead of managing AWS through a mix of console edits, bespoke scripts, team-owned CI jobs, and disconnected IaC repositories, platform teams can publish approved infrastructure APIs, reconcile desired state continuously, and let application teams request resources through governed Kubernetes objects. In this guide, we will look at where Crossplane fits as Kubernetes-native Infrastructure as Code, when a Terraform-style workflow is still the better tool, and how the AWS provider, Compositions, and Claims can help you provision AWS resources without giving up review, policy, ownership, or auditability.
Who this Crossplane and AWS guide is for
- DevOps, SRE, and platform engineers who are evaluating Crossplane as a Kubernetes-native Infrastructure as Code model for provisioning, securing, and governing AWS resources across teams, environments, and accounts.
- Application developers who are being asked to own more of the delivery path and need a safe, repeatable way to request AWS infrastructure without receiving broad account-level permissions, copying fragile YAML from old services, or learning every low-level AWS option before they can ship.
- Engineering managers and technical leads who want to reduce ticket-driven handoffs, standardize infrastructure delivery, and offer governed self-service while retaining visibility into cost, ownership, policy compliance, audit history, and change control.
Why Crossplane matters for AWS platform teams now
Crossplane can be a little less straightforward to get started with than a well-established tool like Terraform. Some documentation is not precise for every use case or provider, and even AI-generated examples do not always work as expected. This guide aims to make the process easier by showing you step by step how to install and configure Crossplane and deploy your first AWS resource.
Why should you even use Crossplane then?
Crossplane is especially useful when application delivery and cloud infrastructure need to move together. A preview environment, for example, might include the app, an S3 bucket, a database, and IAM wiring, all created from Kubernetes manifests and cleaned up the same way. A SaaS platform can also expose a tenant environment as a governed request, so a tenant or automation workflow can create a full stack without running separate IaC plan and apply commands. That does not make Crossplane a universal replacement for Terraform, but it is strong when Kubernetes is already the interface developers use.
How to use this article
- We prepared a repository with resources to deploy everything needed.
- We explain on each resource what it does.
- We walk you through how to deploy Crossplane.
- We deploy a S3 Bucket to make sure everything works.
Prerequisites
1. Clone the repository and step into it
git clone https://github.com/MeteorOps/crossplane-aws-provider-bootstrap.git
cd crossplane-aws-provider-bootstrap
2. Make sure you have the required CLIs:
- Install the AWS CLI & Authenticate it with your AWS Account
- Install the Kubectl CLI
- Install the Helm CLI
- An existing Kubernetes cluster (we’ll be using kind)
Optional: Start a local kind cluster
brew install kind
kind create cluster
open /Applications/Docker.app
kubectl cluster-info --context kind-kind
Repository Overview
Link to the Github Repository: https://github.com/MeteorOps/crossplane-aws-provider-bootstrap.git
credsfile
AWS credentials - should be filled with your own AWS credentialscrossplane-provider-conffile
Uses the creds file to create a Crossplane ProviderConfig (separated into a different file because it takes time for this resource to be ready)crossplane-provider-bootstrapfile
Creates the Crossplane AWS Provider, which enables creating AWS resources using Crossplane (and its dependencies):
ServiceAccount, DeploymentRuntimeConfig, Provider, ClusterRole & ClusterRoleBindingsbucket-definitionsandbucket-crdfiles
These Crossplane manifests define the reusable bucket API. One manifest declares theCompositeResourceDefinition, the Kubernetes API type users will request; the other declares theComposition, the implementation that maps that API to a managed AWS S3 bucket. Think of the pair as the Crossplane equivalent of a small Terraform module: interface plus implementation.
Note: theCompositionmust reference an existingCompositeResourceDefinition, so apply the definition first.bucket-examplefile
The example Kubernetes manifest we’ll apply at the end. It requests an S3 bucket through the bucket API defined above, which lets you validate the provider credentials, Composition, and end-to-end reconciliation path.
Deploy Crossplane
1. Fill the creds file with your AWS access keys
Get your AWS IAM User (not an SSO user as it requires a token to work) access keys and fill them in the credentials file
- NOTE: for production usage, please create a Crossplane IAM user and use its access keys, or preferably use something like IRSA
2. Deploy the Crossplane Helm Chart
Add the Helm repository from which the Crossplane Helm Charts will be fetched
helm repo add crossplane-stable https://charts.crossplane.io/stable
Install Crossplane into the cluster in its own crossplane-system namespace. Keeping it isolated makes provider pods, package installs, and controller logs easier to inspect when reconciliation fails.
helm install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace
3. Examine your Crossplane Deployment
- Run the following command to get Crossplane's pods:
kubectl get pods -n crossplane-system
Then, you should see 2 pods: crossplane & crossplane-rbac-manager

4. Provide Crossplane AWS access by creating a Kubernetes Secret
Insert your AWS credentials to the creds file and run the following from the same folder:
kubectl create secret generic aws-credentials -n crossplane-system --from-file=creds=./creds
Make sure the secret was created as expected:
- Run the following command:
kubectl get secret aws-credentials -n crossplane-system
You should see the aws-credentials secret:

5. Deploy the Crossplane AWS Provider
Creating a Crossplane AWS Provider requires creating a bunch of resources: ServiceAccount, DeploymentRuntimeConfig, Provider, ClusterRole & ClusterRoleBindings, and ProviderConfig
We divided the resources creation into 2 phases:
-
crossplane-provider-bootstrap.yaml:
ServiceAccount, DeploymentRuntimeConfig, Provider, ClusterRole & ClusterRoleBindings crossplane-provider-conf.yaml:
ProviderConfig
The reason for dividing it into 2 phases is that the creation of the ProviderConfig fails if we attempt to create it before the first set of Provider resources and dependencies is ready.
Create the Provider Kubernetes resources using the bootstrap YAML file:
- Run the following command:
kubectl apply -f crossplane-provider-bootstrap.yaml
Validate the creation readiness of the Provider & wait for it to be ready:
- Run the following command to see the AWS Provider resource:
kubectl get provider
You should see something like this:

It might take 1-2 minutes to become Healthy.
Create the ProviderConfig resource & Validate its creation:
- Run the following command:
kubectl apply -f crossplane-provider-conf.yaml && kubectl get providerconfig

Create a S3 Bucket using Crossplane
Create the CompositeResourceDefinition to define a S3 Bucket:
- Run the following command:
kubectl apply -f bucket-definitions.yaml

Create the Composition that defines the S3 bucket implementation:
- Run the following command:
kubectl apply -f bucket-crd.yaml

Create the S3 Bucket Crossplane resource in Kubernetes:
kubectl apply -f bucket-example.yaml
When we installed the AWS Provider, it was installed with some Crossplane CRDs of the AWS Provider.
One of those CRDs is bucket.
Now we can check if the bucket was created by running kubectl get bucket against our Kubernetes cluster

Check if the S3 Bucket was created in AWS:
- List you AWS S3 buckets and search for the newly created one:
aws s3 ls

Teardown & Cleanup
We’ll start by deleting the S3 Bucket Crossplane resource in Kubernetes, which will end up deleting the resource in AWS.
Eventually, if we used kind to spin up a local Kubernetes cluster, we’ll terminate the cluster to keep our workstation nice and clean.
Delete the S3 Bucket resource:
kubectl delete -f bucket-example.yaml
If used kind delete the cluster:
kind delete cluster
Useful Debugging Commands
kubectl get provider
kubectl logs -n crossplane-system deploy/crossplane -c crossplane
kubectl logs -n crossplane-system -l pkg.crossplane.io/provider=provider-aws




