Configuration as Code, Using PKL

8 min read
Jeroen L.
Jeroen L.
Published March 1, 2024

You are building the next best thing. You are scoping out some amazing features. But now you have to add a way for someone running your software to configure things. So, you break out the JSON parser and start coding the interpretation of your new-fangled config file format.

But JSON does not allow schemas, and it doesn’t allow you to indicate to your end users if and what errors they made while configuring your product. Instead of building your configuration logic from scratch, why not pick something dedicated to the task at hand?

Enter Apple’s new PKL language.

On Feb 1, 2024, Apple released a configuration language called PKL, pronounced as “pickle.” PKL’s docs state it is “A configuration as code language with rich validation and tooling.” I’ve done a few experiments and thought it worth sharing my thoughts on this new release.

I’ll first describe what configuration as code is and why you should consider expressing your configurations in a proper coding language. Then I'll introduce PKL and briefly talk about similar tools like Jsonnet, HCL, and Dhall.

Configuration as Code

Configuration as Code (CaC) at its core is simple: you express the configuration of your environment, product, application, infrastructure, or anything that needs to be configured in a text-based format that can be ingested automatically without you as the developer having to enter any values manually, flip switches, drag sliders, or type parameters.

A script executing a command line utility with a specific set of arguments is already a form of configuration as code. Another example is a list of API keys in a JSON file, which a website app needs to be able to function. The JSON is read at launch time and used during the execution of the code.

You could take this to the extreme with a concept like infrastructure as code, which involves dedicated tooling and utilities combined with an extensive configuration allowing you to spin up an entire cloud-based application infrastructure, including databases, web servers, reverse proxies, DNS entries, and everything needed to execute a codebase on production-level infrastructure. This would even include compilation and deployment of the project’s codebase.

Configuration as Code is a dedicated and specific subset of Infrastructure as Code. But, we can use Configuration as Code to our benefit in many situations.

The biggest advantage of expressing your configuration as code is repeatability and traceability. You might wonder why repeatability and traceability are important. To truly reap the benefits of Configuration as Code, you must make configuration part of your (continuous) integration process. Suppose you want to adjust some caching parameters on the library you use to connect to a database.

You should not simply adjust the caching parameters on a production system by adjusting the configuration and restarting your application. Instead, you should adjust the configuration parameters in your codebase, check in your change, and start bringing this configuration change to production like any other codelevel change to your project. Deploy and test locally, deploy on staging, test and verify, and then deploy the configuration change to production.

PKL Overview

Apple released PKL at the start of February 2024. At first glance, it looks like yet another way to express a configuration. You would think you could use JSON or YAML just as well. But PKL brings rich validation and tooling. PKL can be used from the command line, a software library, or a build plugin. PKL implements a special-purpose configuration language. It combines a static configuration format and a general-purpose programming language.

PKL is great for generating a static configuration. A configuration for a tool configured with JSON, YAML, or any other static configuration format can be generated using PKL. It reduces verbosity and improves maintainability through programming concepts like reuse, templating, and abstraction. JSON, YAML, and XML property lists are available with PKL. Other formats are straightforward to develop. Automatic defaults, strong validation, and sensible error messages become available with configuration schemas.

Suppose you are creating a tool, service, or application that needs to be configured. In that case, PKI can easily adopt a “native” configuration format, incorporating all the power of PKL right into your code. Users of your product will gain a safe, easy, well-defined, and well-documented configuration language. If a misconfiguration is entered, your project can tell your end users using PKL. PKL libraries are already available for Java, Kotlin, Swift and Go. Other popular languages and platforms are likely to become available, too.

PKL vs. Alternative Solutions

Static formats like JSON, YAML, and XML are convenient configuration formats. It works on all platforms and languages because it is not much more than structured text. But have you ever considered other ways to configure your projects with a text-based format that is easier to read and write? This allows you to describe a large configuration in multiple files, abstract away repetition, and allow full validation according to a product-specific schema.

While convenient, JSON, YAML, and XML do have several drawbacks when it comes to using them as a configuration format.

  • You have to manually write interpretation and validation code. Making sure JSON, YAML, and XML are well-formed is easy, but validating their correctness requires a lot more work, either by writing validation logic or by creating and applying an XML Schema.
  • Semantics are easily lost in static formats, there are no objects, there’s no reuse, no way to assign additional meaning to specific groupings of configuration.
  • Documentation is impossible or hard with static formats. YAML and XML provide ways to add comments to your configuration, but they are, in no way attached to the elements they are describing.
  • There is no easy way to express a default configuration with placeholders end users need to fill.

PKL solves all of these problems.

  • PKL provides a syntax that allows you to add comments.
  • You can define when a configuration is to be considered complete and valid. This includes constraint-based validations on elements
  • You can define sensible defaults and allow overriding of a default configuration by overwriting only specific parameters.
  • If you make an error in your configuration, PKL will tell you in a succinct format, describing what exactly is wrong.

There are other specific configuration languages and/or tools available to address similar needs as PKL. The documentation of PKL specifically mentions Jsonnet, HCL, and Dhall.

  • PKL claims to be a tool better suited for your configuration needs by providing a better syntax, and sophisticated schemas enabling validation, expressions, documentation generation, and IDE support.
  • PKL is also built from the ground up to be the native configuration language for a product by providing embeddable runtime libraries capable of interpreting a PKL configuration.

PKL is still relatively new and has a small supporting community right now, also PKL states that on average their native binaries tend to have a larger size footprint in your final product.

PKL Format

The following is heavily based on the tutorials provided by the PKL project itself.

A simple PKL file could look like the following.

name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37

If you evaluate this file with PKL, you would get the same content as output.

$ pkl eval /Users/me/tutorial/intro.pkl
name = "Pkl: Configure your Systems in New Ways"
attendants = 100
isInteractive = true
amountLearned = 13.37

Fantastic.

But it does tell you something about the actual config file. You now know the config file is valid.

You could also output this PKL file as JSON or PList using the -f command line option.

$ pkl eval -f json /Users/me/tutorial/intro.pkl
{
  "name": "Pkl: Configure your Systems in New Ways",
  "attendants": 100,
  "isInteractive": true,
  "amountLearned": 13.37
}

$ pkl eval -f plist /Users/me/tutorial/intro.pkl
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>name</key>
  <string>Pkl: Configure your Systems in New Ways</string>
  <key>attendants</key>
  <integer>100</integer>
  <key>isInteractive</key>
  <true/>
  <key>amountLearned</key>
  <real>13.37</real>
</dict>
</plist>

Once you start digging into PKL, you quickly come across the concepts of templates. Let’s look at one from the tutorial of PKL and pick it apart.

First, we create a file named TutorialPart.pkl with the contents:

name: String

part: Int

hasExercises: Boolean = true

amountLearned: Float = 13.37

duration: Duration = 30.min

bandwidthRequirementPerSecond: DataSize = 50.mb

If you would evaluate this file, you would get an error message.

$ pkl eval TutorialPart.pkl
–– Pkl Error ––
Tried to read property `name` but its value is undefined.

1 | name: String
    ^^^^
...

That’s because no default value is given for the name and part fields.

If you create a file filledTutorialPart.pkl.

amends "TutorialPart.pkl"

name = "Writing a Template"

part = 3

And evaluate that, it will succeed.

$ pkl eval pklTutorialPart3.pkl
name = "Writing a Template"
part = 3
hasExercises = true
amountLearned = 13.37
duration = 30.min
bandwidthRequirementPerSecond = 50.mb

Notice how all of the fields and values of the TutorialPart.pkl now appear.

Let’s build upon this.

Create a file Workshop.pkl

module Workshop

import "TutorialPart.pkl"

title: String

interactive: Boolean

seats: Int

occupancy: Float

duration: Duration

`abstract`: String

class Event {
  name: String

  year: Int
}

event: Event

instructors: Listing<String>

class Session {
  time: Duration

  date: String
}

sessions: Listing<Session>

assistants: Mapping<String, String>

agenda: Mapping<String, TutorialPart>

You can immediately notice only names and types are defined. Not actual values.

Now let’s create file named workshop2024.pkl.

amends "Workshop.pkl"

title = "Pkl: Configure your Systems in New Ways"
interactive = true
seats = 100
occupancy = 0.85
duration = 1.5.h
`abstract` = """
  With more systems to configure, the software industry is drowning in repetitive and brittle configuration files.
  YAML and other configuration formats have been turned into programming languages against their will.
  Unsurprisingly, they don’t live up to the task.
  Pkl puts you back in control.
  """

event {
  name = "Migrating Birds between hemispheres"
  year = 2024
}

instructors {
  "Kate Sparrow"
  "Jerome Owl"
}

sessions {
  new {
    date = "8/14/2024"
    time = 30.min
  }
  new {
    date = "8/15/2024:"
    time = 30.min
  }
}

assistants {
  ["kevin"] = "Kevin Parrot"
  ["betty"] = "Betty Harrier"
}

agenda {
  ["beginners"] {
    name = "Basic Configuration"
    part = 1
    duration = 45.min
  }
  ["intermediates"] {
    name = "Filling out a Template"
    part = 2
    duration = 45.min
  }
  ["experts"] {
    name = "Writing a Template"
    part = 3
    duration = 45.min
  }
}

Try and introduce an error in any of the parameter names in this file. Rename seats to sseats or add an integer to the instructors' array. You will get an error indicating what’s wrong. This is powerful because not only does it tell you something is wrong, but you will also immediately know where the error occurred and what is causing the error.

On top of the validation, you can also embed the interpretation of a PKL configuration into your codebase with ease. Provided you are using Java, Kotlin, Swift, or Go for now. But I expect this will soon change. You can also document your PKL files with regular and doc comments, allowing you to generate structured documentation. Again, this is a very powerful feature that will help you configure your products easily.

The Choice Is Yours

Reviewing all the details is outside of the scope of this article, but do check out PKL’s documentation. It’s extensive, easy to understand, and gives much more detail on how you could use PKL in your projects. To be honest, PKL looks and feels like something already being used extensively within Apple, allowing you to reap the benefits of a solid battle-tested configuration language.

I expect that PKL has a good chance of gaining adoption. It’s a neat, clean way to define and configure your projects. As a bonus, you get a solid mechanism to document your configuration, strong and helpful runtime validation, and already good tool support for such a new open-source project. Give PKL a try on your next project, and let me know your thoughts.