Skip to main content
In the Quick start you deployed an example someone else wrote. Now you will write one yourself, from an empty directory to running infrastructure. You will create an S3 bucket, put a file in it that references the bucket, and then make the whole thing configurable from the command line.
This tutorial uses AWS S3 because a bucket is the simplest resource to reason about. Every structure you learn here (stack, target, resources, references, properties) is identical on Azure, GCP, and every other cloud. Only the resource types change.

Before you start

  • formae installed and on your PATH
  • The agent running in another terminal (formae agent start)
  • AWS credentials in your environment
If any of that is missing, work through the Quick start first.
1

Create a project

A forma project is a directory with the Pkl dependencies it needs. Scaffold one with the AWS schema included:
This creates three files:
2

Write a minimal forma

Open main.pkl and replace its contents with this:
S3 bucket names are globally unique across all of AWS. Replace CHANGE-ME with something nobody else will have used. You will fix this properly with a property in a later step.
Four things are happening here:
  • extends "@formae/forma.pkl" tells Pkl this file is a forma. This is what formae project init generates; it also lets you add typed properties later. (The older amends "@formae/forma.pkl" form still works.)
  • The forma block holds everything formae will manage.
  • formae.Stack groups your resources. Destroying the stack destroys everything in it, so a stack is what you create and tear down as a whole, not just a folder for organizing files.
  • formae.Target says where resources go: which cloud account and region.
Every resource takes a label, which is how you refer to it inside formae. It is not the name of the thing in your cloud. bucketName is the real S3 name.
3

Preview before you deploy

Never guess at what a forma will do. Run a dry run with --simulate:
This shows exactly what would be created, changed, or destroyed without touching your cloud. If a Pkl expression is wrong, you find out here rather than halfway through a real apply.
4

Apply it

--mode reconcile means “make my cloud match this file exactly”. On a terminal, apply shows a live progress view until it finishes; press q to detach at any time and it keeps running on the agent.Confirm formae is managing the bucket:
5

Reference one resource from another

Real infrastructure is connected: a subnet needs its VPC, an object needs its bucket. formae handles this with .res.Add an object to your bucket. Note the two changes: the bucket becomes a local you can refer to, and the object reads the bucket’s name through .res.
myBucket.res.bucketName is a resolvable: at write time the bucket does not exist yet, so there is no name to hand the object. formae works out that the object depends on the bucket, creates the bucket first, then fills in the real value. You never declare dependencies or ordering yourself.
Declaring local myBucket is not enough to deploy it. You must also mention myBucket inside the forma block, as above. A local that is never referenced in forma is simply not rendered, and formae will not create it.
Dry-run, then apply:
The bucket is untouched (nothing about it changed) and the object is created.
6

Make it configurable

Hardcoding the bucket name means editing code for every environment. Properties turn values into command-line flags.Declare a typed Props class (this is why the file extends rather than amends), and use its members in the bucket:
Two rules to remember:
  • The member name is the flag: name becomes --name, environment becomes --environment.
  • Read a property directly by its member name. properties.name is the string, no .value to unwrap, and your editor resolves it.
Now pass the properties on the command line, and the same forma serves any environment. This also gives the bucket a real name instead of the CHANGE-ME default:
To see every property a forma exposes:
7

Clean up

Because everything lives in one stack, this removes the object and the bucket together, in the right order.

What you learned

  • A forma is a stack (lifecycle group), a target (where), and resources (what).
  • label identifies a resource to formae; the cloud name is a separate field.
  • .res references another resource and gives you dependency ordering for free.
  • A local must still be mentioned inside forma to be created.
  • Properties are members of a typed Props class: each becomes a CLI flag, read directly by name (properties.name).
  • apply --simulate shows you the truth before you change anything.

What’s next

Modular infrastructure

Split formae into reusable classes and functions as your infrastructure grows.

Apply modes

When to use reconcile and when to use patch.

Core concepts

Stacks, targets, resources, resolvables, and how formae stays in sync.

Pkl cheatsheet

The Pkl syntax you will actually use in formae.