Kubernetes, Crossplane, and Atlas—Better Together
July 28, 2025
More and more companies are moving away from the original approach to DevOps, where application developers were given access to—and often responsibility for—the tooling used to spin up infrastructure and deploy workloads. While this definitely ticked the self-service box, the overhead in cognitive load was very high for developers; they now had to learn and support what was previously centrally managed (even if that meant a slow ticket-ops approach to provisioning and deploying).
At MongoDB, we're seeing more and more customers moving towards the concept of an Internal Developer Platform. Tooling and governance are centrally managed and developed, often by central teams with titles like "platform engineering". Application developers retain the self-service that was the great value proposition of DevOps, but thanks to the central ownership, they don't suffer the overhead of maintaining (or even needing to fully understand) the tooling they leverage.
The tooling often abstracts the developers from many of the optional or typically unchanged options and settings. This can offer developers a minimal number of decisions to make to meet their needs
It also offers many benefits to the company: not only are developers empowered to move and deliver faster, but governance and compliance become easier thanks to centrally enforced settings (e.g., security best practices like TLS). Such tooling also typically makes it far easier for the company to roll out changes to the centrally owned templates used by application developers, whether that be new defaults or new enforced security settings.
Properly enabling application developers makes their lives easier, and delivering customer value becomes faster than with either a fully centralized approach (with ticket-ops) or a totally decentralized 'classic' DevOps approach. The company retains security, governance, and monitoring oversight, helping meet business requirements. Everyone wins.
Atlas Kubernetes Operator
MongoDB Atlas is a fully managed cloud database service that simplifies deploying, managing, and scaling MongoDB clusters. With features like automated backups, advanced security controls, global clusters, and real-time performance metrics, Atlas is designed to help teams move faster while ensuring the reliability and security of their data infrastructure.
MongoDB Atlas offers a wide range of programmatic management options. Many MongoDB customers are leveraging Kubernetes-native workflows, either where applications are deployed to Kubernetes, or where a centrally managed Internal Developer Platform is run through Kubernetes (regardless of where the applications run). To support this, MongoDB provides the Atlas Kubernetes Operator—an open-source operator that lets you manage Atlas resources declaratively through Custom Resource Definitions (CRDs). This means you can define projects, clusters, database users, IP access lists, and more, directly in YAML files, and the operator will reconcile these specs with the actual state in MongoDB Atlas - in other words, apply your declarative configuration to Atlas.
The value for customers and application development teams is that you can manage Atlas through the same workflow (often GitOps and leveraging tooling like ArgoCD) that you already use to configure your applications running in Kubernetes, or through a Kubernetes-based Internal Developer Platform.
By bridging the gap between GitOps and database management, the Atlas Kubernetes Operator empowers platform engineers to treat Atlas resources as first-class citizens in their Kubernetes ecosystem—just like pods, deployments, or services.
Crossplane
Various tools and solutions are competing to provide a base for a Kubernetes-native Internal Developer Platform.
Crossplane is a tool at the forefront of this, and we're gradually hearing more interest in it among MongoDB customers.
It is a powerful open-source framework that extends Kubernetes into a universal control plane for managing infrastructure and services across environments using standard Kubernetes APIs - whether those services run in Kubernetes or outside. Rather than relying on separate tools or external scripts, Crossplane lets users define and manage infrastructure and workloads in a consistent, declarative, and version-controlled way.
At its core, Crossplane works by installing custom controllers and CRDs into a Kubernetes cluster. These controllers can manage internal or external systems, including cloud services, databases, and more, via APIs, using native Kubernetes resources. This makes it possible to describe infrastructure as code, enforce organizational policies, and integrate infrastructure provisioning into existing GitOps workflows.
The standardization even simplifies any further abstraction a company might implement for its application developers, for example, a GUI for simplified infrastructure provisioning.
Crossplane compositions
Crossplane is very well aligned with the value proposition of an Internal Developer Platform thanks to its ability to abstract infrastructure provisioning behind Compositions and Composite Resources (XRs). This allows platform engineers (with input from other teams, such as security) to define reusable blueprints (sometimes called "golden paths" in the context of Internal Developer Platforms) for common services. The standardization and simplification possible with these templates make it far easier for application developers - whether they use the simplified declarative configuration directly or it is abstracted behind a customer's user interface—all without exposing the underlying complexity or provider-specific details.
Crossplane’s flexibility lies in its ability to:
- Connect to multiple external systems via providers with custom templates that:
- Enforce standards through mandatory settings aligned with the organization’s policies (e.g., TLS)
- Reduce cognitive load for application developers through abstraction that cuts down on what they have to think about when trying to provision something like a MongoDB Atlas cluster
- Enable centrally implemented changes through central management of the templates, addressing a common problem with other configuration tooling
Key concepts
Concept | Description |
---|---|
This is the end result and the resulting configuration in Kubernetes that defines the thing actually being managed—this might be a server, a VM, or a much lower-level object like an IP access list. So, for Atlas, a managed resource could be a custom resource defining an Atlas project. This is the output of Crossplane and the configuration ultimately applied to do something, like create or update an Atlas Project. | |
This is the input to Crossplane. It's the template, created by a platform team and used (either directly or indirectly) by the application developers. A composite resource might define a single resource in Kubernetes or a cloud service or represent something more akin to a whole stack. For example, a composite resource might describe a full MongoDB Atlas setup, including a project, a cluster, and a user. | |
This defines a new custom resource type for Crossplane—essentially the schema of the template. This enables customers to request infrastructure via a Composite Resource (XR). The central platform team creates this type, but application developers do not directly use it. | |
This governs the translation of a Composite Resource (XR) into one or more Managed Resources. An example would be translating a Composite Resource defining an Atlas Project, Cluster, and user into distinct custom resources, which the Application Developer doesn't need to see but which are then applied to Atlas via the Atlas Kubernetes Operator. | |
A developer-friendly alias for an XR. Think of it as a simplified interface to create a Composite Resource without exposing platform-specific naming. An optional—but powerful and recommended—further abstraction and simplification. | |
Crossplane Providers are secondary to the inbuilt capabilities in the previous concepts. Providers are extensions to Crossplane that typically enable management of a specific service or workload. They're akin to Terraform Providers, and many Crossplane Providers are even built from an existing Terraform Provider. A Crossplane Provider is one option for actually applying Managed resources - for example, applying the configuration to an external service via APIs. | |
Kubernetes Operators are a well-established concept in the Kubernetes ecosystem. They can run services in Kubernetes (e.g., the MongoDB Controllers for Kubernetes Operator, which supports running MongoDB Community or Enterprise Advanced in Kubernetes) or manage external services via APIs (e.g., the MongoDB Atlas Kubernetes Operator, which supports managing Atlas). When using Crossplane, any Kubernetes Operator can be used thanks to the Crossplane Kubernetes Provider, which enables Crossplane to provide a consistent, simplified, and centrally managed interface in front of any number of existing Kubernetes Operators. |
How it works
Below is a visual representation of how Crossplane Compositions work—from claim to managed infrastructure:

Workflow:
- Application Developer applies a
Claim
to Kubernetes - likely via a GitOps flow. (This may also be generated through some further abstraction for application developers—e.g., a GUI—and automatically applied to Kubernetes) - Crossplane creates a
Composite Resource
(XR) from the claim. - Crossplane selects the matching
Composition
. - The
Composition
generates one or moremanaged resources
, which are actioned/applied using Crossplane Providers—for example, the Crossplane Kubernetes Provider, which enables the use of the Atlas Kubernetes Operator to apply configuration to Atlas.
In essence, Crossplane shifts the responsibility of infrastructure design and lifecycle management to the central platform team, while giving application development teams a clean, consistent, simplified interface to request the services they need.
MongoDB's support of Crossplane
As described above, Crossplane provides a centrally managed self-service interface for a wide array of services thanks to its flexibility and native support.
Though not directly supported by MongoDB, Crossplane can be used in conjunction with tools like the MongoDB Atlas Kubernetes Operator, thanks to the already mentioned Crossplane Kubernetes Provider, which enables Crossplane to work with any operator.
The following is an example of how this can be done. Bear in mind that this blog is not official guidance on Crossplane (consult the official Crossplane documentation) and is not guaranteed to be kept up to date. It's meant as an illustration of using Crossplane with the Crossplane Kubernetes Provider and the Atlas Kubernetes Operator.
As a result, MongoDB is not able to offer anything beyond best efforts guidance on using the Atlas Kubernetes Operator with Crossplane, though MongoDB officially supports its use by Atlas customers.
Example use case: Self-service data platform for microservices teams
We are going to define a Composite Resource Definition called ProjectEnvironment (which allows users to define specific ProjectEnvironments) and a Composition that governs how a ProjectEnvironment is broken down into the underlying Managed Resources, which in this case define Atlas resources, including Project, Deployment, IP Access List, and Database Users.
So, with one ProjectEnvironment Composite Resource (perhaps applied to Kubernetes via GitOps), our Crossplane Composition will generate several custom resources in Kubernetes. The Atlas Kubernetes Operator will then read these and apply them via the Atlas Admin API to create the various resources in Atlas.
Before diving into the examples, let’s make sure you have the necessary setup in place.
Pre-requisites
To follow the examples below, ensure the following:
- A Kubernetes cluster is available and running (can be local or managed).
- Crossplane is installed in the cluster. You can follow the official installation guide.
- The Crossplane Kubernetes Provider is installed.
- Functions patch-and-transform, template-go, and auto-ready are installed.
- The Atlas Kubernetes Operator is installed. Refer to the official quick start guide.
- An Atlas Organization has been created and API keys set up and put in place for the Operator to use (all covered in the quickstart guide, but stop after step 4 as we'll be using the Operator via Crossplane!)
- Ensure your API keys include permission to manage:
- Projects
- Deployments (clusters)
- Database Custom Roles
- Database Users
Note: In order to allow the crossplane provider-kubernetes to manage Atlas Kubernetes Operator objects, RBAC permissions must be given:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
 name: crossplane:provider:provider-kubernetes:mongodb-atlas
rules:
 - apiGroups:
 - ""
 resources:
 - serviceaccounts
 verbs:
 - '*'
 - apiGroups:
 - atlas.mongodb.com
 resources:
 - "*"
 verbs:
 - "*"
 - apiGroups:
 - rbac.authorization.k8s.io
 resources:
 - "*"
 verbs:
 - "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: crossplane:provider:provider-kubernetes:mongodb-atlas
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: crossplane:provider:provider-kubernetes:mongodb-atlas
subjects:
 - kind: ServiceAccount
 name: upbound-provider-kubernetes-beb1eef47cde
 namespace: crossplane-system

Step 1: Define the Composite Resource Definition (XRD)
A Composite Resource Definition (XRD) defines a custom API for your platform, offering a self-service interface for users to provision resources. It acts as a template for the Composite Resources (XRs) that your users will create. While it looks like a Kubernetes Custom Resource Definition (CRD), an XRD provides a higher-level abstraction designed for Crossplane.
The following XRD defines a ProjectEnvironment
resource. This custom API allows development teams to provision a complete MongoDB Atlas environment, including a project, a database deployment, access lists, and users, by creating a single, simple Kubernetes object.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
 name: projectenvironments.platform.example.org
spec:
 group: platform.example.org
 names:
 kind: ProjectEnvironment
 plural: projectenvironments
 claimNames:
 kind: ProjectEnvironmentClaim
 plural: projectenvironmentsclaims
 defaultCompositeDeletePolicy: Foreground

spec.group
: Defines the API group for your composite resource, used for organizing related resources.spec.names
: Sets the name for the Composite Resource (XR) that platform administrators will see. In this case, it's ProjectEnvironment.spec.claimNames
: Defines the developer-facing resource, known as a Composite Resource Claim. Developers create aProjectEnvironmentClaim
, and the platform automatically provisions a correspondingProjectEnvironment
resource based on this definition.spec.defaultCompositeDeletePolicy
: Specifies what happens when aProjectEnvironment
is deleted.Background
: Deletes the Composite Resource first and cleans up the underlying cloud resources (like the Atlas project and database) in the background. This is faster but can leave resources orphaned if cleanup fails.Foreground
: Ensures all underlying cloud resources are successfully deleted before removing the Composite Resource from Kubernetes. This is safer and prevents orphaned resources.
The schema block defines the structure of your new API, including the fields that users can configure (spec
) and the information that will be reported back (status
).
versions:
 - name: v1alpha1
 served: true
 referenceable: true
 schema:
 openAPIV3Schema:
 type: object
 properties:
 spec:
 type: object
 required:
 - project
 - users
 properties:
 project:
 type: string
 environment:
 type: string
 enum: ["dev", "qa", "prod"]
 default: dev
 version:
 type: string
 enum: ["6.0", "7.0", "8.0"]
 default: "8.0"
 users:
 type: array
 items:
 type: object
 required: ["username", "secret"]
 properties:
 username:
 type: string
 secret:
 type: string
 status:
 type: object
 properties:
 id:
 type: string
 connectionStrings:
 type: object
 properties:
 standard:
 type: string
 standardSrv:
 type: string
 private:
 type: string
 privateSrv:
 type: string
 mongoDBVersion:
 type: string
 additionalPrinterColumns:
 - name: Project
 type: string
 jsonPath: ".spec.project"
 - name: MongoDB Version
 type: string
 jsonPath: ".status.mongoDBVersion"

versions
: Contains one or more versions of your API schema.served
: When true, this API version is enabled and can be used on the cluster.referenceable
: Allows other resources to reference this one, which is useful for building complex compositions where one resource depends on another.openAPIV3Schema
: Defines the data structure for your API.spec
: The fields that users configure when creating aProjectEnvironmentClaim
. This example includes required fields like project and users, along with optional fields like environment and MongoDB version that have default values.status
: The fields that Crossplane will populate with information from the provisioned cloud resources, such as the Atlas project id and database connectionStrings.
For a comprehensive list of all attributes of a CompositeResourceDefinition
see its API Reference.
Step 2: Define the composition
This Composition defines the platform logic that translates a ProjectEnvironment
into Managed Resources representing all the elements of a full MongoDB Atlas environment. The Managed Resources (representing project, deployment, access list, and users) are then consumed and applied to MongoDB Atlas by the MongoDB Atlas Kubernetes Operator.
This Composition
provides the logic that turns a high-level ProjectEnvironment
resource into a complete, ready-to-use MongoDB Atlas environment. It acts as the "brain" of the platform, orchestrating the creation of several underlying resources in a specific order.
It uses Pipeline
mode, which allows for a sequence of steps, each powered by a Composition Function. This mode is ideal for complex scenarios that require conditional logic, looping, and custom processing beyond simple field mapping.
Step 2.1: Composition definition
This initial block defines that this Composition
is responsible for fulfilling requests for ProjectEnvironment
resources and that it will use the Pipeline
mode for its logic.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
 name: projectenvironments.platform.example.org
spec:
 compositeTypeRef:
 apiVersion: platform.example.org/v1alpha1
 kind: ProjectEnvironment
 mode: Pipeline

compositeTypeRef
Specifies the Composite Resource type this Composition applies to. In our case, it refers toProjectEnvironment
Composite Resources.mode
defines how the composition is executed.Pipeline
indicates that a Composition specifies a pipeline of Composition Functions, each of which is responsible for producing composed resources that Crossplane should create or update.Resource
indicates that a Composition uses “Patch & Transform” (P&T) composition. This uses an array of resources, each a template for a composed resource.
The pipeline defines the sequence of operations required to build the full environment. Each step can use a different function and is executed in order.
Step 2.2: Atlas project and IP access list
These steps use the patch-and-transform
function to create the foundational AtlasProject
and a corresponding AtlasIPAccessList
.
pipeline:
 - step: atlas-project-with-ip-access-list
 functionRef:
 name: function-patch-and-transform
 input:
 apiVersion: pt.fn.crossplane.io/v1beta1
 kind: Resources
 patchSets:
 - name: project-ref
 patches:
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "spec.forProvider.manifest.spec.projectRef.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-project"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.namespace"
 toFieldPath: "spec.forProvider.manifest.spec.projectRef.namespace"
 resources:
 - name: atlas-project
 base:
 apiVersion: kubernetes.crossplane.io/v1alpha2
 kind: Object
 spec:
 readiness:
 policy: AllTrue
 deletionPolicy: Delete
 forProvider:
 manifest:
 apiVersion: atlas.mongodb.com/v1
 kind: AtlasProject
 providerConfigRef:
 name: kubernetes-provider
 patches:
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "metadata.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-project-object"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "spec.forProvider.manifest.metadata.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-project"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.namespace"
 toFieldPath: "spec.forProvider.manifest.metadata.namespace"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.project"
 toFieldPath: "spec.forProvider.manifest.spec.name"
 - type: ToCompositeFieldPath
 fromFieldPath: "status.atProvider.manifest.status.id"
 toFieldPath: "status.id"

 - name: atlas-ip-access-list
 base:
 apiVersion: kubernetes.crossplane.io/v1alpha2
 kind: Object
 spec:
 readiness:
 policy: AllTrue
 deletionPolicy: Delete
 references:
 - dependsOn: {}
 forProvider:
 manifest:
 apiVersion: atlas.mongodb.com/v1
 kind: AtlasIPAccessList
 spec:
 entries:
 - cidrBlock: "10.0.16.0/20"
 comment: "Company Office Network"
 providerConfigRef:
 name: kubernetes-provider
 patches:
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "metadata.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-ial-object"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "spec.references[0].dependsOn.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-project-object"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.name"
 toFieldPath: "spec.forProvider.manifest.metadata.name"
 transforms:
 - type: string
 string:
 type: Format
 fmt: "%s-ial"
 - type: FromCompositeFieldPath
 fromFieldPath: "spec.claimRef.namespace"
 toFieldPath: "spec.forProvider.manifest.metadata.namespace"
 - type: PatchSet
 patchSetName: project-ref

This step performs direct field mappings from the incoming ProjectEnvironmentClaim
to the new AtlasProject
and AtlasIPAccessList
resources. It uses a PatchSet
to define a reusable way to reference the project's name and namespace, ensuring consistency. For simplicity, the IP access list is hardcoded to a specific CIDR block, but could be parameterized by consuming an input from our composite or by applying custom logic using a custom function.
Step 2.3: Atlas deployment
- step: atlas-deployment
 functionRef:
 name: function-template-go
 input:
 apiVersion: gotemplating.fn.crossplane.io/v1beta1
 kind: GoTemplate
 source: Inline
 inline:
 template: |
 apiVersion: kubernetes.crossplane.io/v1alpha2
 kind: Object
 metadata:
 name: {{ printf "%s-deployment-object" .observed.composite.resource.spec.claimRef.name }}
 annotations:
 {{ setResourceNameAnnotation (printf "%s-deployment" .observed.composite.resource.spec.claimRef.name) }}
 spec:
 readiness:
 policy: AllTrue
 deletionPolicy: Delete
 references:
 - dependsOn:
 name: {{ printf "%s-project-object" .observed.composite.resource.spec.claimRef.name }}
 providerConfigRef:
 name: kubernetes-provider
 forProvider:
 manifest:
 apiVersion: atlas.mongodb.com/v1
 kind: AtlasDeployment
 metadata:
 name: {{ printf "%s-deployment" .observed.composite.resource.spec.claimRef.name }}
 namespace: {{ .observed.composite.resource.spec.claimRef.namespace }}
 labels:
 environment: {{ .observed.composite.resource.spec.environment }}
 spec:
 projectRef:
 name: {{ printf "%s-project" .observed.composite.resource.spec.claimRef.name }}
 {{- $env := .observed.composite.resource.spec.environment }}
 {{- if eq $env "dev" }}
 flexSpec:
 name: {{ printf "%s-deployment" .observed.composite.resource.spec.project }}
 terminationProtectionEnabled: false
 providerSettings:
 backingProviderName: AWS
 regionName: US_EAST_1
 {{- else }}
 deploymentSpec:
 tags:
 - key: environment
 value: {{ $env }}
 name: {{ printf "%s-deployment" .observed.composite.resource.spec.project }}
 clusterType: REPLICASET
 mongoDBMajorVersion: "{{ .observed.composite.resource.spec.version }}"
 backupEnabled: false
 replicationSpecs:
 - regionConfigs:
 - providerName: AWS
 regionName: US_EAST_1
 priority: 7
 electableSpecs:
 instanceSize: M10
 nodeCount: 3
 {{- if eq $env "prod" }}
 readonlySpecs:
 instanceSize: M40
 nodeCount: 2
 autoscaling:
 compute:
 enabled: true
 maxInstanceSize: M50
 diskGB:
 enabled: true
 {{- end }}
 {{- end }}

A Go template generates the deployment's specification dynamically. It inspects the environment
field from the user's claim and creates a cost-effective flexSpec
deployment for dev
environments, or a more robust deploymentSpec
for qa and prod. It also uses references to ensure this deployment is only created after the project from Step 1 is ready.
Step 2.4: Atlas database users
- step: atlas-database-users
 functionRef:
 name: function-template-go
 input:
 apiVersion: gotemplating.fn.crossplane.io/v1beta1
 kind: GoTemplate
 source: Inline
 inline:
 template: |
 {{- range $index, $user := .observed.composite.resource.spec.users }}
 ---
 apiVersion: kubernetes.crossplane.io/v1alpha2
 kind: Object
 metadata:
 name: {{ printf "%s-user-%s" $.observed.composite.resource.spec.claimRef.name $user.username }}
 annotations:
 {{ setResourceNameAnnotation (printf "%s-user-%s" $.observed.composite.resource.spec.claimRef.name $user.username) }}
 spec:
 readiness:
 policy: AllTrue
 deletionPolicy: Delete
 references:
 - dependsOn:
 name: {{ printf "%s-project-object" $.observed.composite.resource.spec.claimRef.name }}
 providerConfigRef:
 name: kubernetes-provider
 forProvider:
 manifest:
 apiVersion: atlas.mongodb.com/v1
 kind: AtlasDatabaseUser
 metadata:
 name: {{ printf "%s-user-%s" $.observed.composite.resource.spec.claimRef.name $user.username }}
 namespace: {{ $.observed.composite.resource.spec.claimRef.namespace }}
 spec:
 projectRef:
 name: {{ printf "%s-project" $.observed.composite.resource.spec.claimRef.name }}
 namespace: {{ $.observed.composite.resource.spec.claimRef.namespace }}
 username: {{ $user.username }}
 passwordSecretRef:
 name: {{ $user.secret }}
 namespace: {{ $.observed.composite.resource.spec.claimRef.namespace }}
 databaseName: admin
 roles:
 - roleName: readWriteAnyDatabase
 databaseName: admin
 scopes:
 - name: {{ printf "%s-deployment" $.observed.composite.resource.spec.project }}
 type: CLUSTER
 {{- end }}

This step also uses the template-go
function, but this time to loop through the user's request and create multiple database users. The template uses a range block to iterate over the users array specified in the ProjectEnvironmentClaim
. For each entry in the array, it generates a complete AtlasDatabaseUser
resource, linking it to the correct project and password secret. This allows a single claim to stamp out multiple, similar resources.
Step 2.5: Status update
- step: custom-status-update
 functionRef:
 name: function-template-go
 input:
 apiVersion: gotemplating.fn.crossplane.io/v1beta1
 kind: GoTemplate
 source: Inline
 inline:
 template: |
 {{ if .observed.resources }}
 {{ $project := index .observed.resources "atlas-project" }}
 {{ $deployment := index .observed.resources (printf "%s-deployment" .observed.composite.resource.spec.claimRef.name) }}
 apiVersion: platform.example.org/v1alpha1
 kind: ProjectEnvironment
 status:
 {{ if $project.resource.status.atProvider.manifest }}
 id: {{ $project.resource.status.atProvider.manifest.status.id }}
 {{ end }}
 {{ if $deployment.resource.status.atProvider.manifest }}
 mongoDBVersion: {{ $deployment.resource.status.atProvider.manifest.status.mongoDBVersion }}
 connectionStrings: {{ $deployment.resource.status.atProvider.manifest.status.connectionStrings | toJson }}
 {{ end }}
 {{ end }}

This crucial step uses a Go template not to create a cloud resource, but to feed information back to the user. It inspects the resources created in the previous steps, extracts key information like the project ID, MongoDB version, and connection strings, and patches them into the status
field of the ProjectEnvironment
resource. This makes vital information available to the developer directly on their claim object.
Step 2.6: Readiness
- step: readiness-check
 functionRef:
 name: function-auto-ready

The final step uses the function-auto-ready
function to determine when the entire composition is complete and healthy. This function automatically inspects all the resources managed by the pipeline and updates the ProjectEnvironment's
status conditions accordingly. It signals that the environment is fully provisioned and ready for use only when every component (project, deployment, users) reports a ready state.
For a comprehensive list of all attributes of a Composition
resource, see its API reference.
Step 3: Create a composite resource
This is the final step - all of the previous elements are the cogs in the machine, and this is a specific instance of a Composite Resource that is our input to Crossplane. This is what an application development team would use (directly or with further abstraction) to request a MongoDB environment in a self-service manner, using a single claim and secret.
apiVersion: platform.example.org/v1alpha1
kind: ProjectEnvironmentClaim
metadata:
 name: dev-env-claim
spec:
 compositionRef:
 name: projectenvironments.platform.example.org
 project: payments
 environment: dev
 users:
 - username: user1
 secret: user-secret
 - username: user2
 secret: user-secret
 - username: user3
 secret: user-secret

This is a standard Kubernetes Secret
that securely stores the password for the database users.
apiVersion: v1
kind: Secret
metadata:
 name: user-secret
 labels:
 atlas.mongodb.com/type: credentials
type: Opaque
stringData:
 password: myH4rdP@ssw0rd

Once this ProjectEnvironmentClaim is applied to the Kubernetes cluster where Crossplane is running:
- The Composition is selected by Crossplane via compositionRef.name in our ProjectEnvironmentClaim.
- The Composition instructs Crossplane on the creation of custom resources in Kubernetes for:
- A MongoDB project
- A dev environment deployment
- A CIDR-bound IP access list
- 3 database users, sharing the same password by using the same secret (for simplicity)
- Once created in Kubernetes, those custom resources are read by the MongoDB Atlas Kubernetes Operator and applied to Atlas via the Atlas Admin API.
The status of the claim reflects the readiness of the composed resources. This means that the Application Developers have visibility into the success/failure/status of what's ultimately being created in MongoDB Atlas. How developers see the claim status depends on how they've applied the claim into Kubernetes—e.g., it might be surfaced in a GUI or through a tool like ArgoCD.
Conclusion
Crossplane is a powerful tool for providing central management and governance while enabling developer self-service with minimal cognitive load.
Though MongoDB does not directly support Crossplane, it's entirely possible to leverage the MongoDB Atlas Kubernetes Operator through Crossplane and the Crossplane Kubernetes Provider.
The self-service data platform design using Crossplane Compositions, CompositeResourceDefinitions, and Kubernetes Provider Object resources empowers microservices teams with declarative infrastructure provisioning for MongoDB Atlas. This approach offers several key benefits:
- Application developer self-service
- Write a simplified claim without needing to understand the underlying mechanics or infrastructure.
- Central enablement and governance
- Platform Engineers define and version the Composition, while developers only need to interact with a simplified custom API tailored to their use case.
- Platform Engineers can enforce mandatory settings (enabling central governance) and recommend sensible defaults that simplify application developer use.
- Flexible environment profiles
- Environment-specific configurations (dev, qa, prod) can be baked into the templates, enabling consistent infrastructure provisioning with variable capacity and redundancy.
- Extensibility
- The use of template-go and patch-and-transform functions allows for rich customization logic, templating, and conditional behavior, going beyond what vanilla Crossplane patching can offer.
- Namespace isolation & multi-tenancy
- Claims can be namespace-scoped and operate safely in multi-tenant environments using namespaced secrets and resources.
Despite its many strengths, there are known limitations to this approach, mainly due to the current state of the Crossplane and external operators' interoperability:
- Deletion Ordering & Finalizer Issues
- While Crossplane Kubernetes Provider coordinates deletion in a dependency-aware manner, a bug in the Crossplane Kubernetes Provider fails to properly set a finalizer on dependent resources. This means that a parent resource (e.g., AtlasProject) may be deleted before children resources (e.g., AtlasDatabaseUser), and these resources will become orphaned. This is especially problematic because:
- Some Atlas Kubernetes Operator resources do not support independent references - the orphaned child resources may sit there in an error state due to their dependence on the deleted parent resource.
- Manual cleanup may be required for such orphaned resources.
- Readiness may not be fully tracked
- When using go-template to generate the resource, readiness is not automatically checked.
- This might mean that the status of resources that a claim is meant to generate or manage might not be up to date by default.
- This must be mitigated by explicitly generating
ClaimConditions
to update the status, or using the `auto-ready` function. - With
Resource
mode, or usingResources
in thePipeline
mode, this is not an issue, as readiness is automatically checked.
Visit our docs page to learn more about the MongoDB Atlas Kubernetes Operator.