Deploy AWS Resources using Crossplane on Kubernetes
DevOps Engineering

Deploy AWS Resources using Crossplane on Kubernetes

Deploy AWS resources using Crossplane. Step-by-step guide to configure the AWS provider and create a S3 bucket.

Arthur Azrieli

12 min read

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

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:

  1. Install the AWS CLI & Authenticate it with your AWS Account
  2. Install the Kubectl CLI
  3. Install the Helm CLI
  4. 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

  1. creds file
    AWS credentials - should be filled with your own AWS credentials
  2. crossplane-provider-conf file
    Uses the creds file to create a Crossplane ProviderConfig (separated into a different file because it takes time for this resource to be ready)
  3. crossplane-provider-bootstrap file
    Creates the Crossplane AWS Provider, which enables creating AWS resources using Crossplane (and its dependencies):
    ServiceAccount, DeploymentRuntimeConfig, Provider, ClusterRole & ClusterRoleBindings
  4. bucket-definitions and bucket-crd files
    These Crossplane manifests define the reusable bucket API. One manifest declares the CompositeResourceDefinition, the Kubernetes API type users will request; the other declares the Composition, 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: the Composition must reference an existing CompositeResourceDefinition, so apply the definition first.
  5. bucket-example file
    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

__wf_reserved_inherit

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:

__wf_reserved_inherit

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:

  1. crossplane-provider-bootstrap.yaml:
    ServiceAccount, DeploymentRuntimeConfig, Provider, ClusterRole & ClusterRoleBindings
  2. 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:

__wf_reserved_inherit

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
__wf_reserved_inherit

Create a S3 Bucket using Crossplane

Create the CompositeResourceDefinition to define a S3 Bucket:

  • Run the following command:
       
kubectl apply -f bucket-definitions.yaml
__wf_reserved_inherit

Create the Composition that defines the S3 bucket implementation:

  • Run the following command:
       
kubectl apply -f bucket-crd.yaml
__wf_reserved_inherit

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

__wf_reserved_inherit

Check if the S3 Bucket was created in AWS:

  • List you AWS S3 buckets and search for the newly created one:
       
aws s3 ls
__wf_reserved_inherit

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