atmos icon indicating copy to clipboard operation
atmos copied to clipboard

RFC: Optional Alternative Configuration Languages in Atmos

Open osterman opened this issue 3 months ago • 1 comments

Describe the Feature

Over the years, one of the most surprisingly popular features of Atmos has been templating. What originally started as a small escape hatch — mainly for variable interpolation — has grown into one of the most used (and, honestly, over-used) parts of Atmos.

Speaking personally: I’ve never been a big fan of heavy YAML templating. It’s fragile, hard to validate, and easy to misuse. The multi-phase nature of “render text → then parse YAML” introduces problems that are fundamentally hard to solve:

  • validation becomes nearly impossible,
  • users can accidentally generate invalid YAML,
  • debugging becomes harder,
  • the configuration model becomes ambiguous and brittle.

Helm charts suffer from the same issues — it works until it really doesn’t.

Atmos has historically been firm about using YAML for configuration.

But maybe it’s time to acknowledge a simple truth:

The YAML escape hatch has outgrown its original purpose.

It’s become both powerful and problematic.

Proposal

What if Atmos supported alternative syntax for configuration like HCL, Pkl, Starlark, etc.

1. HCL (Terraform Syntax)

Familiar to the community and well-suited to structured configuration.

Pros:

  • strong types
  • expressive but predictable
  • great validation
  • good ecosystem support

Would an HCL-powered Atmos config be useful?

  1. Pkl (Pickle)

A modern configuration language built specifically for typed, validated configuration.

Pros:

  • excellent validation
  • strict types
  • imports/modules
  • no templating hacks
  • great tooling

Would you adopt Pkl-based stacks?

  1. TypeScript

A popular choice when teams want flexibility, IDE support, and strong type tooling.

Pros:

  • rich language
  • massive ecosystem
  • autocomplete, intellisense, real-time validation
  • easy for frontend/backend engineers

Concerns:

too much power? configuration becoming “code”?

Still, many tools (Next.js, Expo, ESLint, etc.) have moved toward TS configs successfully.

Would a TypeScript-based Atmos DSL feel natural?

  1. Starlark

Deterministic, Python-inspired, and widely used in Bazel.

Pros:

  • predictable
  • simple syntax
  • safe language
  • good middle ground

Would Starlark satisfy your needs?

Why We're Asking

Templating gave users a dynamic escape hatch.

But over time, that escape hatch has become:

  • a source of invalid YAML
  • hard-to-trace bugs
  • a multi-phase evaluation model
  • something that undermines validation & correctness
  • a de facto programming language inside YAML

Atmos has matured. Its users have matured. And maybe our configuration model should mature too.

But we don’t want to remove the current model or force changes.

We want to offer better, safer alternatives, with a smoother path for teams that want more structure.

Questions for the Community

  • Do you feel the same friction with YAML templating?
  • Are you interested in optional, alternative configuration languages?
  • If we supported multiple syntax backends, which would you prefer?
    • HCL
    • Pkl
    • TypeScript
    • Starlark
    • Other?
  • Would you adopt them incrementally in your stacks?

Your insights will help shape the future design of Atmos configuration.

Thanks for being part of this journey!

osterman avatar Dec 03 '25 16:12 osterman

YAML Stack Configuration

# stack.yaml

vars:
  tenant: tenant1
  environment: prod
  region: us-west-2

components:
  terraform:
    vpc:
      vars:
        cidr_block: "10.0.0.0/16"
    eks:
      vars:
        vpc_id: !terraform.state vpc vpc_id
        instance_type: "m6i.large"
        desired_capacity: 3

HCL

# stack.hcl

stack "dev" {
  vars {
    tenant      = "tenant1"
    environment = "prod"
    region      = "us-west-2"

    vpc_id = atmos::terraform::state("vpc").vpc_id
  }

  components {
    terraform {
      component "vpc" {
        vars {
          cidr_block = "10.0.0.0/16"
        }
      }

      component "eks" {
        vars {
          instance_type    = "m6i.large"
          desired_capacity = 3
        }
      }
    }
  }
}

Pkl

# stack.pkl

import atmos

stack: atmos.Stack = new atmos.Stack {
  atmos = new atmos.Atmos {
    terraform = new atmos.TerraformApi {
      // Implementation is provided by the Atmos runtime,
      // but the *shape* (method signature) is defined in atmos.pkl.
    }
  }

  vars = {
    "tenant"      = "tenant1"
    "environment" = "prod"
    "region"      = "us-west-2"
    "vpc_id"      = atmos.terraform.state("vpc")["vpc_id"]
  }

  components = new atmos.Components {
    terraform = new atmos.TerraformComponents {
      components = {
        "vpc" = new atmos.Component {
          vars = {
            "cidr_block" = "10.0.0.0/16"
          }
        }

        "eks" = new atmos.Component {
          vars = {
            "instance_type"    = "m6i.large"
            "desired_capacity" = 3
          }
        }
      }
    }
  }
}

Starlark

# stack.star

stack = Stack(
    vars = struct(
        tenant      = "tenant1",
        environment = "prod",
        region      = "us-west-2",
        vpc_id      = terraform.state("vpc").vpc_id,
    ),
    components = Components(
        terraform = TerraformComponents(
            components = {
                "vpc": Component(
                    vars = {
                        "cidr_block": "10.0.0.0/16",
                    },
                ),
                "eks": Component(
                    vars = {
                        "instance_type": "m6i.large",
                        "desired_capacity": 3,
                    },
                ),
            },
        ),
    )
)

Typescript

// stack.config.ts

export const stack: StackConfig = defineStack({
  vars: {
    tenant: "tenant1",
    environment: "prod",
    region: "us-west-2",

    vpc_id: terraform.state("vpc").vpc_id as string,
  },

  components: createComponents(
    createTerraformComponents({
      vpc: createComponent({
        cidr_block: "10.0.0.0/16",
      }),
      eks: createComponent({
        instance_type: "m6i.large",
        desired_capacity: 3,
      }),
    }),
  )
});

osterman avatar Dec 03 '25 18:12 osterman

Is asking for both HCL and some real-life language too much?

I understand that people might feel familiar with HCL, but me personally - I hate it. It always seemed more as a hack, than a language.

My preference would be Starlark, but someone from our team would prefer TS. Personally - typescript here also sucks - why all the extra stuff I have to type "export const stack"... yes I understand it comes from the language, but it's too much IMO.

mtb-xt avatar Dec 17 '25 04:12 mtb-xt

@mtb-xt I think if there's enough interest, we can add multiple.

We have an initial stab at this here:

  • cloudposse/atmos#1842

It implements a stack loader registry which supports a pluggable way of load stacks. This means adding other formats would be straightforward.

It also implements a method to convert from YAML/HCL/JSON<->YAML/HCL/JSON.

I think the bigger risk fractured documentation, and that there will naturally be things you can do in one language and not in another.

osterman avatar Dec 17 '25 04:12 osterman

I think the bigger risk fractured documentation, and that there will naturally be things you can do in one language and not in another.

That's the reason why I gave up on the idea of using Pulumi: Community fragmentation due to various supported languages, and suboptimal support of the language I was interested in compared to some others.

afeblot avatar Dec 20 '25 00:12 afeblot