Your Platform Provisions Environments. That's a Factory.
Platform Engineer + CNCF Ambassador, AWS community builder. I design scalable cloud-native platforms and love making teams faster and safer.
Your Platform Provisions Environments. That's a Factory.
Every platform team eventually faces the same request, over and over again.
A developer needs a new environment — staging, a feature branch, a sandbox for testing. They open a ticket, fill out a form, ping someone on Slack, or run a script they half-understand. A few minutes or a few days later, the environment exists. Or it doesn't. Or it exists but it's missing the secrets. Or the resource limits are wrong. Or it was set up differently than the last one.
This is the problem: environment provisioning without a clear creation model is slow, inconsistent, and doesn't scale.
Why it hurts
When there's no centralized creation logic, every team invents their own process. Some copy-paste Terraform from a previous project. Some run scripts that only the original author understands. Some open tickets and wait.
The result is environments that drift. Staging doesn't match production. The new service has a different secret management setup than everything else. Resource limits are either too tight or nonexistent. And every time something breaks, the platform team has to reverse-engineer how that environment was created in the first place.
Beyond the technical debt, there's a trust problem. Developers stop trusting that the environment they get will actually work. They start maintaining their own workarounds. The platform team loses credibility not because they're incompetent, but because the process itself is unreliable.
What the pattern says
The Factory pattern says: instead of letting every caller decide how to create something, you centralize that creation logic in one place. The caller says what they want. The factory decides how to build it.
The Abstract Factory goes one level up — it creates families of related objects that are meant to work together. Not just a namespace, but a namespace plus secrets plus network policies plus resource quotas plus an observability entry. Things that belong together get created together, consistently, every time.
In software, this pattern exists to avoid the chaos of scattered instantiation logic. In platform engineering, it exists for the same reason: to avoid the chaos of scattered provisioning logic.
Where you see this in practice
Crossplane is the clearest implementation of this pattern in the cloud-native ecosystem. You define a Composite Resource — say, an AppEnvironment — and Crossplane composes it from lower-level resources: an RDS instance, an S3 bucket, a Kubernetes namespace, an IAM role. The developer asks for an AppEnvironment. They don't know or care what's underneath. The factory handles it.
The AppEnvironment XRD is the interface. The composition is the implementation. You can swap the implementation — move from AWS to GCP, change the database engine — without touching the interface the developer uses.
Backstage Software Templates work the same way. A developer picks a template — "Python microservice", "data pipeline", "internal tool" — and gets back a repo, a CI/CD pipeline, a Kubernetes manifest, and a service catalog entry. The template is the factory. The developer just chose the product type.
Terraform modules are the most familiar version of this idea. A well-designed module is a factory for infrastructure — you pass in variables (the what), the module handles the implementation details (the how). The problem is that most teams end up with modules that are too thin, just wrappers around resources, instead of modules that encapsulate a meaningful unit of infrastructure with sensible defaults and guardrails built in.
The Builder pattern: when creation has steps
The Builder pattern is a close relative. Where Factory focuses on what you're creating, Builder focuses on how you assemble something complex step by step.
Think of a cluster bootstrap pipeline. It doesn't create everything at once. It runs in stages: provision the cluster, install the CNI, configure cert-manager, deploy the ingress controller, register with ArgoCD, apply the base manifests. Each step depends on the previous one. The order matters.
Helm with dependencies, Terraform with depends_on, pipelines chaining multiple workspaces — these are all Builder implementations. You're constructing a complex result piece by piece, in a defined sequence, with a clear separation between "how we build this" and "what we end up with."
AWS CDK is an interesting case here. It's deeply influenced by the Builder pattern — you construct infrastructure programmatically using loops, conditionals, and abstractions. The mental model is sound. But it also requires teams to fully commit to that abstraction layer, which is a significant shift if your team lives in Terraform or plain YAML. That might be part of why CDK hasn't seen the adoption you'd expect given how well-designed it is — the pattern is right, but the switching cost is real.
The distinction between Factory and Builder matters when something breaks. A Factory failure tells you what couldn't be created. A Builder failure tells you which step broke — which is much easier to recover from.
What changes when you think this way
When someone on your team says "we should let teams provision their own environments", the Factory framing turns a vague discussion into a concrete design question: how much of the creation logic do you centralize, and how much flexibility do you expose?
Do you give teams raw Terraform and hope they follow conventions? That's no factory — just scattered creation logic waiting to drift. Do you give them a module with sensible defaults and a few knobs to turn? That's a thin factory. Do you give them a Backstage template that hides everything behind a form? That's a full factory.
Each of those is a valid choice depending on your context. The pattern doesn't tell you which one to pick — it gives you the vocabulary to make that decision intentionally instead of stumbling into it.
You're not debating Terraform vs. Crossplane. You're debating how much creation logic should live in one place, and who should own it. That's an architectural decision. It deserves to be treated like one.
How does your team handle environment provisioning today — scattered scripts, thin modules, or full abstraction? Hit reply at blog@parraletz.dev — curious where people have landed on this.


