Practical example: Lifeline
The Lifeline example demonstrates a complete Day-0 to Day-N infrastructure lifecycle. It shows how different teams collaborate, how to use modular architecture patterns, and how apply modes support different workflows.
This example is included with formae and can be found in /opt/pel/formae/examples/lifeline/
.
Prerequisites: Understand Fundamentals, Modular infrastructure, and Workflows before diving into this example.
What you'll learn
- How to organize complex infrastructure into focused modules
- When to use reconcile vs patch mode
- How different teams can work on the same infrastructure
- The Day-0 to Day-N progression pattern
The Lifeline progression
Project structure
Lifeline organizes infrastructure into focused modules, each with a specific responsibility:
lifeline/
├── basic_infrastructure.pkl # Main entry point
├── cross_cutting_change.pkl # Cross-cutting updates
├── micro_change.pkl # Application-specific changes
├── network.pkl # Networking components
├── nvpc.pkl # VPC creation
├── security_groups.pkl # Security configuration
└── vars.pkl # Shared configuration
Key architectural patterns
Function-based resources
Modules use functions to create reusable infrastructure components:
// network.pkl
function subnets(properties: Dynamic?): Listing = new Listing {
new subnet.Subnet {
label = "lifeline-public-subnet-1"
vpcId = nvpc.nvpc(properties).res.id
cidrBlock = properties.subnetCidr1.value
availabilityZone = "\(properties.region.value)a"
tags { new { key = "Name"; value = properties.name.value + "-public-subnet-1" } }
}
// More subnets...
}
Clean import strategy
The main forma imports modules and shared configuration:
// basic_infrastructure.pkl
amends "@formae/forma.pkl"
import "@formae/formae.pkl"
import "./vars.pkl"
import "./nvpc.pkl"
import "./network.pkl"
import "./security_groups.pkl"
import "@formae/ext/random.pkl"
Shared configuration
The vars.pkl
file contains stack, target, and properties used across all formae:
// vars.pkl - Note the stack and target patterns
projectName = "lifeline"
stackName = "lifeline"
region = "us-east-1"
stack: formae.Stack = new {
label = stackName
description = "Stack for the lifeline showcase"
}
target: formae.Target = new formae.Target {
label = "aws-target"
config = new aws.Config {
region = module.regionProp.value
}
}
vpcLabel = "lifeline-vpc"
s3BucketSuffix = "-unique-bucket-id"
crossCuttingTag = "cross-cutting-tag"
crossCuttingTagValue = "cross-cutting-tag-value"
The complete workflow journey
This example demonstrates how different teams work on the same infrastructure over time using different apply modes.
Day 0: Platform engineers deploy foundation
formae apply --mode reconcile --watch basic_infrastructure.pkl
Platform engineers use reconcile mode to deploy the complete networking foundation. This creates VPC, subnets, internet gateway, route tables, and security groups.
Day 10: Platform engineers make structural changes
formae apply --mode reconcile --watch basic_infrastructure.pkl # Updated version
Using reconcile mode again ensures the infrastructure matches the updated code exactly.
Day 50: Developers add application resources
formae apply --mode patch --watch micro_change.pkl
Developers use patch mode to add application-specific resources (like S3 buckets) without affecting the foundation infrastructure. The blast radius is minimal - only creates or updates, never destroys. This can be triggered from a GitHub workflow.
Day 90: Security team applies compliance update
formae apply --mode patch --watch cross_cutting_change.pkl
The security team uses patch mode to add compliance tags across multiple resources. While the blast radius in terms of resources touched can be large, the scope of changes is minimal and focused.
Day 100: Clean up everything
formae destroy --watch --query "stack:lifeline"
Working with ephemeral infrastructure or need to start over? Destroy all resources in the stack with a single command.
Understanding the forma files
Let's look at what each forma file does and why specific modes are used.
Foundation infrastructure (reconcile mode)
The basic_infrastructure.pkl
creates core networking resources using reconcile mode:
forma {
vars.stack
vars.target
// VPC
nvpc.nvpc(properties)
// gateways
network.igw(properties)
network.attachIgw(properties)
// subnets
for (subnet in network.subnets(properties)) {
subnet
}
network.routeTable(properties)
network.publicRoute(properties)
for (subnetAssoc in network.subnetAssocs(properties)) {
subnetAssoc
}
// security groups
for (securityGroup in security_groups.securityGroups(properties)) {
securityGroup
}
}
Why reconcile mode? This is your infrastructure foundation. You want complete control - reconcile mode ensures the infrastructure matches your code exactly, removing anything not defined.
Micro changes (patch mode)
Applying micro_change.pkl
adds application-specific resources:
forma {
vars.stack.res
vars.target.res
new bucket.Bucket {
label = "bucket-1"
bucketName = vars.projectName + properties.s3BucketSuffix.value + "-1"
versioningConfiguration = new bucket.BucketVersioningConfiguration {
status = "Enabled"
}
tags {
new {
key = "Name"
value = vars.projectName + "-bucket-1"
}
new {
key = "Project"
value = vars.projectName
}
new {
key = "Environment"
value = "Development"
}
}
publicAccessBlockConfiguration = new bucket.BucketPublicAccessBlockConfiguration {
blockPublicAcls = true
blockPublicPolicy = true
ignorePublicAcls = true
restrictPublicBuckets = true
}
bucketEncryption = new bucket.BucketBucketEncryption {
serverSideEncryptionConfiguration {
new bucket.BucketServerSideEncryptionRule {
serverSideEncryptionByDefault = new bucket.BucketServerSideEncryptionByDefault {
sseAlgorithm = "AES256"
}
}
}
}
}
}
Why patch mode here? You're adding resources without changing the foundation. Minimal blast radius, minimal risk, fast deployment.
Cross-cutting changes (patch mode)
Applying cross_cutting_change.pkl
updates security across existing resources:
forma {
vars.stack.res
vars.target.res
new internetgateway.InternetGateway {
label = "lifeline-igw"
tags {
new {
key = vars.crossCuttingTag
value = vars.crossCuttingTagValue
}
}
}
new securitygroup.SecurityGroup {
label = "lifeline-alb-sg"
groupDescription = "Allow HTTP traffic to ALB"
tags {
new {
key = vars.crossCuttingTag
value = vars.crossCuttingTagValue
}
}
}
new securitygroup.SecurityGroup {
label = "lifeline-task-sg"
groupDescription = "Allow HTTP traffic from ALB"
tags {
new {
key = vars.crossCuttingTag
value = vars.crossCuttingTagValue
}
}
}
}
Why patch mode here? You're changing multiple resources that affect the foundation. There is minimal risk despite a potentially large blast radius, and no need to see unnecessary details in order to make orthogonal changes.
Takeaway Insight: Each apply mode serves different team workflows and risk profiles.
Try it yourself
You can run this example on your own AWS account:
-
Copy the example:
cd ~/projects cp -R /opt/pel/formae/examples/lifeline ./ cd lifeline
-
Review the code: Examine the module structure and see how functions create reusable components.
-
Test with eval:
# See what will be created formae eval basic_infrastructure.pkl # Test with different property values formae eval --region us-west-2 basic_infrastructure.pkl
-
Deploy the foundation:
formae apply --mode reconcile --watch basic_infrastructure.pkl
-
Add application resources:
formae apply --mode patch --watch micro_change.pkl
-
Clean up when done:
formae destroy --watch --query "stack:lifeline"
What's next
You've seen how to organize complex infrastructure using modules and apply modes. Continue learning:
- Workflows - Deep dive into deployment workflows
- Classic GitOps - Understand reconcile mode in detail
- Always up-to-date GitOps - Learn about patch mode and discovery
- CLI: Apply - Master the apply command and its modes