{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "@id": "https://www.vidyasource.com/blog/infrastructure-as-code-formae-pkl-aws/",
  "url": "https://www.vidyasource.com/blog/infrastructure-as-code-formae-pkl-aws/",
  "mainEntityOfPage": "https://www.vidyasource.com/blog/infrastructure-as-code-formae-pkl-aws/",
  "headline": "Infrastructure as Code Has a New Playbook: formae and Pkl",
  "description": "formae brings agentic, stateless IaC with Pkl's type-safe config to AWS. No drift, no state files, and an MCP server AI agents can actually use.",
  "datePublished": "2026-03-14T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "Neil Chaudhuri",
    "jobTitle": "President",
    "url": "https://www.linkedin.com/in/neil-chaudhuri/",
    "sameAs": "https://www.linkedin.com/in/neil-chaudhuri/"
  },
  "publisher": {
    "@id": "https://www.vidyasource.com/#organization"
  },
  "image": "https://www.vidyasource.com/img/blog/iac.webp",
  "keywords": [
    "AI",
    "MCP",
    "Open Source",
    "DevSecOps",
    "Software Engineering"
  ],
  "articleBody": "I have always been a big fan of \"&lt;Insert artifact here> as Code\" implementations for several reasons. The artifacts are co-located with the code so you don't have\nto go hunting for them you need information. They also enjoy the same rigor like versioning and testing as production code. There are many great examples.\nArchitecture as Code. Diagrams as Code. Documentation as Code. And of course the ubiquitous Infrastructure as Code (IaC).\n\nIaC is powerful because it allows you to automate the provisioning and management of your infrastructure through configuration files.\nVersioning across deploys and repeatability across environments are particularly useful, and the\nconfidence to tear down and recreate entire environments on demand was a game change when IaC arrived on the scene. There are many popular\nIaC tools out there:\n- Terraform by HashiCorp, which uses a declarative language called HCL to manage multi-cloud resources\n- OpenTofu, the open source fork of Terraform\n- AWS CloudFormation, Amazon's native IaC service for provisioning AWS resources via JSON or YAML templates\n- Ansible by Red Hat, an agentless automation tool for configuration management and orchestration\n- Pulumi, which lets you define infrastructure using general-purpose programming languages like TypeScript, Python, and Go\n\nWhile all of these represent significant advancements over the status quo before IaC, there were several significant problems.\n\nConfiguration drift is the slow, silent disaster of infrastructure management. Someone updates an S3 bucket policy in the console. A teammate tweaks an\nECS task definition directly. Nobody writes it down. Six months later your code says one thing and production says something completely different, and you\nspend two days figuring out why your deployment broke. The root cause is almost always the same: configuration files have no types. YAML, JSON, and TOML are all happy to accept\n`maxConnections: \"fifty\"` right alongside `maxConnections: 50`. There are just magic strings. As someone obsessed with type safety,\nI find this especially problematic.\n\nWhile Pulumi solves this particular problem by replacing endless walls of configuration text with Turing complete, probably typed programming languages, this makes\nconfiguration more complex, less deterministic, and more error-prone because you have introduced the possibility of bugs and side effects just like your production code. In other words,\nthis approach overcompensates too far in the opposite direction.\n\nAll of this means there is room a for a happy medium that can offer the best of both worlds.\n\nTwo tools have combined to reshape what IaC can actually look like. One is [Pkl](https://pkl-lang.org/), Apple's open source, type safe configuration (but not programming) language.\nYou can use it anywhere you need configuration, from Spring Boot to Kubernetes.  The other is [formae](https://github.com/platform-engineering-labs/formae) (yes, spelled in lowercase like\npoet ee cummings or musician k.d. lang), a new IaC tool that uses Pkl for configuration files\nthat represent the source of truth for your infrastructure. Together they plug the leaks that have made traditional IaC so frustrating.\n\nformae also goes one step further with an MCP server that lets AI agents reason about and act on your infrastructure directly. Let's dig in.\n\n## Pkl: Configuration Errors Are Now Compile-Time Errors\n\nFor such a simple language, Pkl is really quite powerful. It offers typed properties, constraints, union types,\ndefault values, inheritance through `amends`, and computed properties. When your config violates a constraint, you get an error *at evaluation time*, before\nanything gets deployed. It's the \"make impossible states impossible\" principle I love so much applied to infrastructure.\n\nHere's what that looks like in practice. Imagine you're defining the configuration for your ECS task definition shared across environments:\n\n```pkl\n// infra/base/EcsTaskConfig.pkl\nmodule EcsTaskConfig\n\ntypealias CpuUnits  = Int(this == 256 || this == 512 || this == 1024 || this == 2048 || this == 4096)\ntypealias MemoryMiB = Int(this >= 512 && this <= 30720)\n\n// Only allow known, safe log levels — no \"YOLO\" in production\ntypealias LogLevel = \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\"\n\nclass ContainerConfig {\n  image: String(startsWith(\"123456789012.dkr.ecr.us-east-1.amazonaws.com/\"))\n  cpu: CpuUnits = 512\n  memory: MemoryMiB = 1024\n  logLevel: LogLevel = \"INFO\"\n  portMappings: Listing<UInt16>\n}\n\nclass EcsTaskConfig {\n  family: String\n  containers: Listing<ContainerConfig>(length >= 1)\n  executionRoleArn: String(startsWith(\"arn:aws:iam::\"))\n  taskRoleArn: String(startsWith(\"arn:aws:iam::\"))\n}\n```\n\nThis is like defining classes in Object-Oriented Programming.\n\nThen your production config `amends` this base and fills in the specifics. Think of it like instantiating the classes\nyou defined.\n\n```pkl\n// infra/production/ecs-webapp.pkl\namends \"package://pkg.pkl-lang.org/github.com/platform-engineering-labs/formae/formae@0.15.0#/Resource.pkl\"\n\nimport \"../base/EcsTaskConfig.pkl\"\n\nfamily = \"webapp-production\"\nexecutionRoleArn = \"arn:aws:iam::123456789012:role/ecsTaskExecutionRole\"\ntaskRoleArn      = \"arn:aws:iam::123456789012:role/webappTaskRole\"\n\ncontainers {\n  new EcsTaskConfig.ContainerConfig {\n    image        = \"123456789012.dkr.ecr.us-east-1.amazonaws.com/webapp:latest\"\n    cpu          = 1024\n    memory       = 2048\n    logLevel     = \"INFO\"\n    portMappings { 8080 }\n  }\n}\n```\n\nTry setting `cpu = 999` or `logLevel = \"TRACE\"` or an image URI that doesn't match your ECR registry. Pkl tells you immediately.\nYou've moved configuration errors from \"3 AM production incident\" to \"the moment you save the file.\" That's the kind of\nfeedback loop shift that DORA research has confirmed makes teams more productive.\n\nTake a look at the `typealias` called `CpuUnits`. AWS ECS only accepts specific CPU values. The Pkl defintinion above encodes that business rule directly in the\ntype as a union of type of a finite set of integer values. You can't accidentally set `cpu = 300`. The type system prevents it.\nThis is what statically typed configuration means in practice, and it's why I'm a big fan of Pkl.\n\n## formae: IaC That Treats Code as Truth\n\nTerraform and OpenTofu are the most popular IaC tools. They manage *state files* that track what's deployed but they can become a liability at scale.\nThey need to be stored somewhere, typically somewhere remote, with their own locking mechanism. The big problem is that it is common for\nsomeone to alter the infrastructure manually in the console because they do not recognize state files as the source of truth. With the state\nfiles now misaligned with the real infrastructure, this is textbook configuration drift. Terraform won't know it until you make the effort\nto run `terraform plan` periodically as a chrom job or in CI/CD on a schedule or on an event basis (like a pull request). Running `terraform apply`\nto make changes will also detect drift. Still, in both cases, it's on you to figure it out.\n\nformae has no state files. Your Pkl configuration *is* the source of truth. formae's agent (not an AI agent but rather a long-running daemon with its\nown HTTP server, datastore connection, and retry logic) continuously reconciles reality against your\ncode. If something drifts outside your IaC definitions, like a manual change in the AWS console or an automated key rotation, formae detects it and can\nmerge or reject the change depending on your configuration. This is pretty cool.\n\nConsider a simple web application deployed to AWS. Let's see what it looks like to manage an S3 bucket for static assets alongside an\nECS-hosted web application behind CloudFront. First, configure your AWS target using formae's Pkl types:\n\n```pkl\n// infra/formae-config.pkl\namends \"package://pkg.pkl-lang.org/github.com/platform-engineering-labs/formae/formae@0.15.0#/Config.pkl\"\n\nlocal awsTarget = new Target {\n  label       = \"aws-production-us-east-1\"\n  namespace   = \"AWS\"\n  discoverable = true\n  config {\n    type    = \"aws\"\n    region  = \"us-east-1\"\n    profile = \"production\"\n  }\n}\n\ntargets { awsTarget }\n```\n\nThen define your stack on AWS:\n- S3 bucket for assets\n- ECS for the app\n- CloudFront as the CDN for both\n\n```pkl\n// infra/production/webapp-stack.pkl\namends \"package://pkg.pkl-lang.org/github.com/platform-engineering-labs/formae/formae@0.15.0#/Stack.pkl\"\n\n// --- S3: Static Asset Bucket ---\nlocal assetBucket = new Resource {\n  label  = \"webapp-assets\"\n  type   = \"AWS::S3::Bucket\"\n  target = \"aws-production-us-east-1\"\n  stack  = \"webapp-production\"\n  properties {\n    BucketName          = \"my-webapp-assets-prod\"\n    VersioningConfiguration { Status = \"Enabled\" }\n    PublicAccessBlockConfiguration {\n      BlockPublicAcls       = true\n      BlockPublicPolicy     = true\n      IgnorePublicAcls      = true\n      RestrictPublicBuckets = true\n    }\n    Tags { [\"Environment\"] = \"production\"; [\"ManagedBy\"] = \"formae\" }\n  }\n}\n\n// --- ECS Cluster ---\nlocal ecsCluster = new Resource {\n  label  = \"webapp-cluster\"\n  type   = \"AWS::ECS::Cluster\"\n  target = \"aws-production-us-east-1\"\n  stack  = \"webapp-production\"\n  properties {\n    ClusterName = \"webapp-production\"\n    Tags { [\"Environment\"] = \"production\" }\n  }\n}\n\n// --- ECS Service ---\nlocal ecsService = new Resource {\n  label  = \"webapp-service\"\n  type   = \"AWS::ECS::Service\"\n  target = \"aws-production-us-east-1\"\n  stack  = \"webapp-production\"\n  properties {\n    ServiceName    = \"webapp-production-service\"\n    Cluster        = ecsCluster.properties[\"ClusterName\"]\n    TaskDefinition = \"webapp-production:latest\"\n    DesiredCount   = 2\n    LaunchType     = \"FARGATE\"\n    NetworkConfiguration {\n      AwsvpcConfiguration {\n        AssignPublicIp = \"DISABLED\"\n        Subnets        { \"subnet-abc123\"; \"subnet-def456\" }\n        SecurityGroups { \"sg-webapp-prod\" }\n      }\n    }\n    Tags { [\"Environment\"] = \"production\"; [\"ManagedBy\"] = \"formae\" }\n  }\n}\n\n// --- CloudFront Distribution ---\nlocal cdn = new Resource {\n  label  = \"webapp-cdn\"\n  type   = \"AWS::CloudFront::Distribution\"\n  target = \"aws-production-us-east-1\"\n  stack  = \"webapp-production\"\n  properties {\n    DistributionConfig {\n      Enabled         = true\n      PriceClass      = \"PriceClass_100\"\n      HttpVersion     = \"http2and3\"\n      Origins {\n        // S3 origin for static assets\n        new {\n          Id                    = \"S3-webapp-assets\"\n          DomainName            = \"\\(assetBucket.properties[\"BucketName\"]).s3.us-east-1.amazonaws.com\"\n          S3OriginConfig { OriginAccessIdentity = \"origin-access-identity/cloudfront/ABCDEFG\" }\n        }\n        // ECS / ALB origin for the app\n        new {\n          Id         = \"ALB-webapp\"\n          DomainName = \"webapp-prod-alb-1234567890.us-east-1.elb.amazonaws.com\"\n          CustomOriginConfig {\n            HTTPSPort            = 443\n            OriginProtocolPolicy = \"https-only\"\n          }\n        }\n      }\n      DefaultCacheBehavior {\n        TargetOriginId       = \"ALB-webapp\"\n        ViewerProtocolPolicy = \"redirect-to-https\"\n        CachePolicyId        = \"658327ea-f89d-4fab-a63d-7e88639e58f6\" // CachingOptimized\n      }\n      CacheBehaviors {\n        new {\n          PathPattern          = \"/assets/*\"\n          TargetOriginId       = \"S3-webapp-assets\"\n          ViewerProtocolPolicy = \"redirect-to-https\"\n          CachePolicyId        = \"658327ea-f89d-4fab-a63d-7e88639e58f6\"\n        }\n      }\n    }\n  }\n}\n\nresources { assetBucket; ecsCluster; ecsService; cdn }\n```\n\nNow apply your configuration in the command line (probably in your CI/CD pipeline).\n\n```bash\nformae apply infra/production/webapp-stack.pkl --target aws-production-us-east-1\n```\n\nformae's agent picks up your configuration, resolves the dependencies in the right order (S3 before CloudFront, cluster before service), and drives the changes\nasynchronously. If you like, you can even simulate your changes first with `--simulate` before you make things official.\n\nMost importantly, the formae agent checks periodically for drift. If someone manually changed your S3 bucket policy or scaled the ECS\nservice through the console, formae validates that the change conforms to the rules you defined through your type safe Pkl configuration. If it\ndoes, it incorporates the changes. If it doesn't, formae tells you.\n\nIf formae existed a few years ago, I would have thought its automated drift detection and validation was its coolest feature. But in the\nage of AI, there is something else formae can do that is probably cooler.\n\n## The formae MCP Server: Your AI Agent Can Now Manage Infrastructure\n\nformae ships with an [MCP server](https://modelcontextprotocol.io/), so you can use any MCP client like Goose, Claude Desktop, or any modern IDE to\nquery and, if your organizational policy allows, update your infrastructure directly using natural language. That is pretty cool.\n\nWhen you run the formae agent and expose its MCP server, you can configure your MCP client to do things like this:\n\n- **Query your inventory** — Ask \"What S3 buckets are in the production stack?\" and get back the right answer in real time.\n- **Check drift** — Ask \"Has anything changed in the webapp-production stack since the last check?\" if you want to check for configurtion drift manually.\n- **Simulate changes** — Ask \"What would change if I applied this config?\" before any real action is taken.\n- **Apply infrastructure** — Let an agent propose and apply a formae config with human-in-the-loop approval.\n\nThis is already awesome, but imagine the possibilities. For example, AI agents could connect to formae MCP as part of a swarm that is also connected\nGitHub, Jira, and your entire engineering infrastructure for autonomous action. Of course you need a mature AI governance and guardrail strategy\nto make that work, but that is a story for another day. The main point here is formae MCP opens up a lot of options.\n\nThis is the right model for AI + infrastructure. A live, type-validated, code-first source of truth with an MCP interface that AI agents can actually trust and act on.\n\n## Why The Pkl-formae Combination Works\n\nPkl and formae reinforce each other in a way that's more than the sum of their parts.\n\nPkl gives you the type system. Types enforce your infrastructure. Constraints enforce your\nbusiness rules. The `amends` feature enable reuse so staging and production share a common base without copy-paste drift.\n\nformae gives you the runtime guarantee. It uses Pkl to validate your configuration. It validates and detects drift for you. It manages dependencies among resources.\nIts MCP server enables AI to understand your infrastructure—not as a novelty but as a genuine operational, natural language interface. Your AI assistant knowing in\nreal time how many ECS services are running, whether any resources have drifted, and what a proposed change would actually do to your infrastructure is\npowerful. That's AI at its best in an organization.\n\nThe old playbook of raw YAML, state files in S3 with DynamoDB locking, CI/CD running `terraform plan` on a schedule, and crossing your fingers got us pretty far, but configuration drift, fragile state,\nand zero feedback at definition time have always been cracks in the foundation. Pkl seals them at the config layer. formae seals them at the\ninfrastructure layer. Together, they represent Infrastructure as Code at its modern best."
}