vpcId: vpc-12345 inside your Composition YAML, you are building a fragile system. If you deploy that same Composition to your Staging cluster (which has a different VPC), every database provisioning attempt will fail. To build a robust IDP, Compositions must be context-aware. Enter EnvironmentConfigs.1. The Need for Global Variables#
In Episode 6, we used FromCompositeFieldPath to read data from the user’s Claim (e.g., storageGB).
But an Application Developer shouldn’t be forced to include vpcId: vpc-12345 or region: us-east-1 in their Claim. They don’t know the VPC ID, and they shouldn’t care.
That information is a Global Variable specific to the environment (the cluster) that Crossplane is running in.
We need a way to store global variables in the cluster, and have the Composition pull those variables in at runtime.
2. Defining an EnvironmentConfig#
Crossplane provides a built-in Custom Resource called EnvironmentConfig. It is essentially a global JSON dictionary.
Let’s assume this cluster is our European Production cluster. We will create a configuration specific to this environment.
Create a file named cluster-config.yaml:
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: EnvironmentConfig
metadata:
# We can create multiple configs and select them via labels
name: eu-prod-config
labels:
environment: production
data:
# This is a free-form dictionary. You can put anything here!
aws:
region: eu-central-1
vpcId: vpc-99998888
subnetGroup: my-prod-subnets
defaultTags:
managedBy: crossplane
costCenter: "815"Apply it to the cluster:
kubectl apply -f cluster-config.yamlThis data is now floating in the cluster, waiting to be consumed.
3. Consuming Environment Data in Compositions#
To use this data in a Composition, we must do two things:
- Tell the Composition which
EnvironmentConfigto load. - Write an
Environmentpatch to inject the data into the Managed Resources.
Step 1: Loading the Environment#
Open the Composition we wrote in Episode 6 (composition-postgres.yaml).
At the very top of the spec (above resources), add the environment block. We use a Label Selector to dynamically find the correct config!
spec:
compositeTypeRef:
apiVersion: database.acmecorp.com/v1alpha1
kind: XPostgreSQLInstance
# 1. Instruct Crossplane to load the global variables
environment:
environmentConfigs:
- type: Selector
selector:
matchLabels:
# It will dynamically find our 'eu-prod-config'!
environment: productionStep 2: The Environment Patch#
Now, inside the resources block for the RDS instance, we add a new Patch. Notice the type is FromEnvironmentFieldPath.
patches:
# Patch the Region
- type: FromEnvironmentFieldPath
# Read from the EnvironmentConfig Data dictionary
fromFieldPath: aws.region
# Write to the AWS MR
toFieldPath: spec.forProvider.region
# Patch the Cost Center Tag
- type: FromEnvironmentFieldPath
fromFieldPath: defaultTags.costCenter
toFieldPath: spec.forProvider.tags.CostCenterThe Magic of Portability#
Now, your Composition is 100% portable.
- If you
kubectl applythis Composition in the European cluster, the databases will boot ineu-central-1. - If you apply the exact same Composition file to the American cluster (which has an
EnvironmentConfigdefiningaws.region: us-east-1), the databases boot in America.
You have successfully achieved the DRY (Don’t Repeat Yourself) principle in Platform Engineering.
4. Advanced: Resolution Policies (Optional vs Required)#
When you rely on EnvironmentConfigs, what happens if a platform engineer forgets to create the EnvironmentConfig object before an Application Developer submits a Claim?
By default, the FromEnvironmentFieldPath patch is Required. If Crossplane evaluates the patch and cannot find aws.region in the environment, the entire Composition engine halts. The XR will throw an error event, and no AWS resources will be created.
This is usually the desired behavior (fail safe, rather than deploying to a default region accidentally).
However, if you want a patch to be optional (e.g., injecting a specific debugging flag that only exists in development clusters), you can change the policy:
- type: FromEnvironmentFieldPath
fromFieldPath: debugging.logLevel
toFieldPath: spec.forProvider.logLevel
policy:
fromFieldPath: Optional # Do not crash if missing!Troubleshooting & Common Errors#
cannot resolve environment config: selector did not match any config- Root Cause: The
matchLabelsin your Composition’senvironmentConfigsblock does not match thelabelson anyEnvironmentConfigcurrently running in the cluster. - Solution: Ensure the
EnvironmentConfigis applied, and verify the spelling of the labels.
- Root Cause: The
cannot apply patch: fromFieldPath not found in environment- Root Cause: The
EnvironmentConfigwas found, but the specific JSON path (e.g.,aws.vpcId) you requested in your patch does not exist in thedatablock. - Solution: Double check the YAML indentation of your
cluster-config.yaml.
- Root Cause: The
Conclusion & Next Steps#
EnvironmentConfigs act as the global state for your Internal Developer Platform. By injecting VPCs, Regions, and Organizational Tags dynamically, your Compositions become highly reusable assets that can be distributed across dozens of Kubernetes clusters.
But what if a physical dependency isn’t static? What if your Composition needs to create a brand new VPC, and then create an RDS Database inside that VPC? How do you ensure the VPC is created first, and how do you pass its dynamic ID to the Database?
In Episode 8: Managing Dependencies Between Resources, we will learn how to orchestrate multi-resource deployments and safely pass variables between them using the Pipeline.

