Bruno Schaatsbergen website Mastodon PGP Key email A drawing of an astronaut in space The Netherlands

Rethinking Configuration Languages (Part 1)

in
writing
date
4/23/2025

Every day, all we do is I/O. We take in the world through streams of words—spoken, written, heard, or seen. Like I/O devices, our brains buffer and process these inputs constantly. We decode, interpret and reassemble. Funny enough, we’re not so different from parsers. We’re just a bit more complex—and a lot more vague when something goes wrong.

Parsers are the unsung heroes of programming languages. They take raw text and shape it into meaning. They’re not just about syntax—they’re about intent. About transforming a string of tokens into something we can reason about. They sit at the boundary between human expression and machine execution, quietly enforcing structure while trying not to get in the way.

Designing a configuration language—the step that comes before we can even think about parsing—is no different. It’s about shaping raw intent into structure, just like parsers do. It’s an exercise in empathy, as much as engineering. You’re not just building a syntax. You’re building a system that makes people feel like they’re in control—like their intent maps cleanly onto action. The goal isn’t expressiveness for its own sake. It’s clarity, predictability, and safety. The best configuration languages don’t try to be clever. They try to be obvious.

As someone who’s spent years in the trenches of the HashiCorp Configuration Language (HCL), I’ve seen the friction developers run into—up close and often. HCL is permissive by design, which makes it flexible—but that same flexibility can blur intent. Now, I’m not saying we should throw out permissiveness. HCL is a great language. But it’s a balancing act. Too much freedom can lead to vagueness, and too much structure can lead to frustration. It’s a balancing act that’s hard to get right.

For configuration languages, that balancing act is in finding that balance—between freedom and structure, between saying what you mean and meaning what you say.

Now, I’m not here to suggest and propose hcl/v3. It’s a great language. But I do think there’s room to do better modern configuration languages. I think we can design something more predictable, more consistent, more user-friendly. A language that codifies what we’ve learned over the years. A language that’s not just a tool, but a partner in the process of building and managing infrastructure. . One that doesn’t let me ship something cryptic, unreadable, or unmaintainable to production.

You might be wondering what an evolution of HCL—and similar configuration languages—could look like, and how it could help us build better systems. This lets us begin to explore the question of what a configuration language should be. It’s not about HCL, or any other language. It’s about the principles that guide us in designing a language that helps us build better systems. Don’t consider this write up a proposal, but a thought experiment. A way to think about the future of configuration languages and how they can help us build better systems.

For a bit of context: NGINX drove significant innovation in this space. The NGINX configuration language was one of the first to adopt a declarative approach, paving the way for others, including HCL. Then came UCL (Universal Configuration Language), which took things a step further. UCL made configuration files more human-readable while maintaining full compatibility with JSON, striking a balance between human and machine readability. This is another source of inspiration for HCL, which built on the strengths of both and introduced its own unique twist.

Configuration languages are tricky. HCL’s permissiveness is both its greatest strength—and its biggest weakness. Subtle mistakes in syntax can cause unexpected behavior, and vague error messages leave you questioning your intent. The amount of times I’ve been asked whether you could debug Terraform code is a testament to this.

I recently came across a Terraform snippet that, while semantically valid, was nearly impossible to understand at a glance. It made me wonder—should configuration languages really allow this level of ambiguity, especially when we lack proper ways to test or debug these expressions? Judge for yourself:

locals {
  # Just reverse (transpose) the order to make a list of all invalid rules
  invalid_rule_map = transpose({
    for x in toset(var.update) :
    x => toset([
      for order in var.orderings :
      order[1]
      if tonumber(order[0]) == tonumber(x)
    ])
  })
  valid_update = alltrue([
    for index in range(0, length(var.update) - 1) :
    length(setintersection(
      lookup(local.invalid_rule_map, var.update[index], toset([])),
      slice(var.update, index + 1, length(var.update)),
    )) == 0
  ])
}

On the flip side, Terraform is also such a beautiful language. The way it lets you codify an API call in just a few lines is impressive, and the block-based syntax closely following the API specification is a really nice touch.

resource "aws_ecr_repository" "runtimes" {
  name                 = "runtimes"
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

I want to clarify that there’s a distinction between Terraform and HCL. Terraform simply uses HCL as its underlying configuration language. Here, we’re focusing on HCL—not Terraform. The goal is to challenge modern configuration languages to be more than just syntax. We want them to be a guiding force in how we build and manage infrastructure.

I’m not saying HCL needs fixing—I actively advocate for its elegance and its well-deserved place in the industry and my heart. But it’s odd, isn’t it? We’re careful in our codebases, avoiding ambiguity wherever we can, yet we accept it in our configuration languages. Why? Mostly because we have no alternative. We make do with what’s available, and the truth is, many of us find comfort in the familiar.

What if we could design a configuration language that’s more intuitive, consistent, and less ambiguous? One that doesn’t feel like a programming language, but instead enforces clear, idiomatic patterns—guiding users away from the common pitfalls that make configurations fragile or overly complex. Not by stripping away flexibility, but by embedding proven patterns and best practices into the language itself. A language that helps you do the right thing by default, and makes it harder to do the wrong thing. One that actively prevents anti-patterns we’ve collectively recognized as bad practice. I’ve been thinking about this—and playing with a few ideas. I’m not claiming to have the answer, yet. But maybe the path forward starts with limiting certain constructs—like dialing back the flexibility of loops or rethinking how we handle conditionals. Not to be prescriptive, but to guide us toward better practices. A language that helps us build more reliable systems, with reliability codified into the language, not just cleaner code.

A language that refuses to let you write something cryptic, unreadable, or unmaintainable. Rather a language—built for cloud-native infrastructure—that actually helped us align. One that nudged us toward consistency, where different people solving the same problem ended up writing the same thing. Predictable. Repeatable. Boring, even. In the best possible way.

Back when I taught Terraform workshops, I’d ask students to write a module for the same simple AWS architecture. The outcome? A different module from every single student. That says a lot—not about the students, but about the language. It shows just how flexible HCL is, but also how ambiguous it can be.

Everyone solves the same problem differently, are we really aligned? Infrastructure doesn’t make money—but it can burn through a lot of it. And most businesses? They’re doing roughly the same thing: running a few apps, setting up a landing zone, wiring things together. So why isn’t there a consistent way to do it? Is it us—or is it the tools?

Truth is, we’re not aligned. Not even close. Every organization has its own spin, and it’s chaos, and the tools allow for that. It’s not a bad thing, but it’s not great either. It’s a bit like the Wild West out there. We’re all trying to build the same thing, but we’re doing it in our own way. And that’s okay, but it’s also a bit of a mess.

People say it’s because every organization does things differently—but that’s not quite true. It’s the flexibility in the tools that lets them do things differently. And I’m not sure yet whether that’s a good thing or a bad thing.

Anyway, back to the thought experiment. “What if…”. In future posts, I’ll dive into practical examples as I prototype and explore the ideas I’ve reflected on today. I’m not suggesting we overhaul everything we’ve come to rely on, but rather take a step back and reconsider what we truly want from our configuration languages. This also isn’t a rant against modern configuration languages. I think they’re great, and I’m grateful for the work that’s been done. But I want to push the art of the possible a bit further and see where it takes us.

There’s still much to unpack, we haven’t even talked about the challenge of designing configuration languages that balance both human-readability and machine-readability. It’s an interesting journey, and I’d love for you to continue along with me. The result might be that we stick with what we have, or it could lead to something entirely new. Either way, I think it’s worth exploring.

A big thank you to everyone designing and building these languages—your work shapes the tools we use every day and pushes us forward. We’re standing on the shoulders of those innovations, and it’s exciting to see where we’ll go next. Thank you HashiCorp, NGINX, and Vsevolod Stakhov (UCL) for paving the way.

Let’s continue this thought experiment together. Feel free to reach out to me at b@bschaatsbergen.com, wait for my next post, or simply reflect on the possibilities. While I don’t claim to have all the answers, I’m deep in thought on this, and I hope you’ll join me in the discussion.

/rethinking-configuration-languages-(part-1)