DaC vs IaC vs PaC vs config-as-code: how they layer
The four 'as code' methodologies aren't competing, they layer. IaC builds the foundation. Config-as-code parameterizes it. PaC enforces what's allowed. DaC is the surface a non-engineer reads. Walking through the layers, with concrete examples from Terraform up to the form a business owner sees.
A question I get a lot, since I started writing about Decisions as Code more directly: how does it relate to infrastructure-as-code, policy-as-code, and config-as-code? Are these competing approaches? Is DaC a successor? Is it the same thing dressed up differently?
None of the above. The four approaches aren't competing, they layer. Each one solves a different problem at a different altitude in the stack. Confusing them is the source of most of the bad self-service portals I've seen in the last decade, because the platform team is treating their config-as-code surface as if it were a DaC surface, and the business owner is treating their PaC denials as if they were IaC errors. Everyone is reading the wrong layer.
Let me lay the layers out, walk through a concrete example, and call out the boundaries that separate them.
The four altitudes
Going from lowest to highest:
Infrastructure-as-code (IaC) is the foundation. You describe the desired state of your infrastructure (VPCs, subnets, clusters, queues, buckets) in code, and a tool reconciles the live world toward that description. Terraform, Pulumi, CloudFormation, Crossplane. IaC's job is to make infrastructure declarative, version-controlled, and reproducible. The audience is platform engineers.
Config-as-code parameterizes the foundation. You take the IaC modules and the application services and you express their per-deployment configuration in code rather than in mutable runtime state. Helm values, Kustomize overlays, Jsonnet, env-var-driven config files, the values plugged into Terraform modules from a configuration repo. The audience is still mostly platform engineers, but now also application engineers configuring the foundation for their particular service.
Policy-as-code (PaC) governs what's allowed. You declare the rules, "every Deployment must have CPU limits," "no S3 bucket may be public," "every namespace must carry a cost-center label", in code, and a policy engine evaluates incoming changes against the rules. OPA, Gatekeeper, Kyverno, Conftest, Sentinel. PaC's job is to refuse changes that violate the org's standards, regardless of which foundation the change is targeting. The audience is the platform team plus the security and compliance teams.
Decisions as Code (DaC) is the surface a non-engineer reads. You take the small set of business decisions that drive everything below (workload class, environment, region, sizing tier, ownership, data classification) and you expose them as a curated, opinionated surface that a business owner can read in five minutes. The platform team owns everything below the DaC surface; the business owner owns everything on it. The audience is the business.
Four altitudes, four audiences, four answers to four different questions. They don't compete. They stack.
What each layer does that the others can't
The cleanest way to feel the difference is to ask what each layer is uniquely capable of.
IaC is the only layer that talks to cloud APIs. You can declare a VPC in DaC and describe a policy about VPCs in PaC, but only IaC actually creates one. If you removed IaC from the stack, nothing would get built.
Config-as-code is the only layer that turns a generic foundation into a specific deployment. The IaC module says "this is what an EKS cluster looks like." The config-as-code values say "this particular EKS cluster is in eu-west-1, has these node groups, and uses this version." The base layer is the template; the config is the concrete value plugged into the template.
PaC is the only layer that says no. IaC will build whatever you describe. Config-as-code will deploy whatever you parameterize. PaC is the one that refuses to apply a change because it violates a rule. Without PaC, the foundation has no enforcement of standards beyond convention; with PaC, the standards become structural.
DaC is the only layer that's readable by someone who doesn't write code. Everything below DaC is a YAML file or a Terraform plan or a Rego policy. DaC is a curated surface, sometimes a form, sometimes a small structured file, sometimes a Backstage Software Template, that exposes the five or six choices a business owner actually wants to make and absorbs the rest into the layers below.
Each layer has a unique capability. None of them substitutes for any of the others.
A worked example, top to bottom
The cleanest way I've found to teach this is to take one user-facing request and walk it down the stack.
The request: "I need a small production-class Postgres database in the EU region for my service."
At the DaC surface, this request is five lines:
service: catalog-service
workload_class: production
sizing: small
region: eu-west-1
data_classification: pii
The business owner reads those five lines and confirms them. They don't see the rest. They don't want to see the rest. The five decisions are theirs to make; everything below this surface is the platform's job.
Below the DaC surface, the configuration layer expands those five values into a complete configuration. The "small production" sizing tier resolves to specific CPU and memory requests, a specific Postgres parameter group, a specific backup window, a specific retention policy, a specific encryption key alias, a specific maintenance schedule. The "pii" data classification adds a tag set, an audit-log routing rule, and a network policy that restricts egress. None of this expansion is visible to the business owner; it's the responsibility of the platform team's Helm values structure or Crossplane Composition or Terraform module, the DaC values surface doing its job.
At the policy layer, the expanded configuration is evaluated against the org's rules. Is this Postgres instance encrypted? Yes. Does the bucket holding its backups have public access blocked? Yes. Is the cost-center label present? Yes. Is the version of Postgres on the supported list? Yes. The OPA policies, or the Gatekeeper constraints, admit or reject the change. If the policy refuses, the request bounces back to the configuration layer, which usually means the business owner was about to do something the org has decided isn't allowed, and the answer is "you can't do that here. Here's the supported alternative."
At the infrastructure layer, Terraform (or Crossplane, or whatever the foundation is) takes the now-validated configuration and reconciles the actual cloud against it. RDS instance gets created. Subnet group gets attached. Parameter group gets applied. Backup policy gets configured. KMS key gets associated. The foundation does its foundation job.
Four layers. Five business decisions at the top. A fully provisioned, policy-compliant, organization-standard Postgres database at the bottom. The business owner never saw the IaC, the config-as-code, or the PaC. They saw the DaC surface. The platform team owned everything else.
The boundary errors
The pattern of platform-team failure I see most often is one of three boundary errors.
Treating config-as-code as DaC. This is the eighty-nine-row YAML problem. The platform team exposes the full configuration surface as the "self-service" portal and calls it done. The business owner, faced with a hundred knobs they don't understand, files a ticket. The platform team is doing config-as-code well; they have not done DaC at all. The fix is to curate the five decisions out of the eighty-nine knobs and let the platform absorb the rest.
Treating PaC as the foundation. This is the team that writes Rego policies for everything they wish they could enforce, including things that are really configuration choices. The policy engine becomes a second configuration layer with worse ergonomics. The fix is to recognize when something is a default (config-as-code) versus when it's an enforcement (PaC). Defaults belong in the configuration layer; refusals belong in the policy layer.
Treating IaC as DaC. This is the team that hands a business owner a Terraform module and says "fill in the variables." The variables include vpc_cidr and enable_flow_logs and transit_gateway_attachment_id. The business owner has no idea what any of those mean and shouldn't have to. The fix is to wrap the Terraform module in a higher layer that exposes only the business decisions and absorbs the rest into the module's defaults.
The boundary between the layers is where the platform team decides who owns what. Get the boundary wrong and one layer leaks into the layer above it. Get it right and each layer is doing the work it's uniquely capable of.
Why all four exist
You can build a working platform with only IaC. People did, for years. The result is brittle, inconsistent, and unreadable to anyone who doesn't write Terraform.
You can add config-as-code on top and get a more reproducible, more parameterized foundation. People did this for the next era. The result is consistent and reproducible but still has no enforcement and is still unreadable to non-engineers.
You can add PaC on top of that and get enforcement. People are doing this now. The result is consistent, reproducible, enforced, and still unreadable to the business owner who's supposed to drive the choices.
You add DaC on top to give the business owner a surface they can actually read. The other three layers don't go away. They become invisible to the audience that doesn't need to see them.
Each layer adds something. None of them is a substitute for the layers below it. The layered model is the only one I've seen that holds up across foundations and across years.
If you're a platform team building self-service today, the question to ask isn't "which approach should we use." It's "which layers do we have, and which layer is leaking?" If your business owner is reading config-as-code, you're missing the DaC layer. If your DaC surface lets people violate org standards, you're missing the PaC layer. If your PaC is enforcing things that should be defaults, the boundary between layers is wrong. The diagnosis is almost always at the boundary.
Centralize the decisions. Project them everywhere. Validate at the boundary. Govern at admission. The layers do the work; the approach decides which layer does which job.
, Sid