Classic GitOps
Classic GitOps treats your forma as the single source of truth. This is formae's default approach to infrastructure management, where your code is the only authority and external changes are not accepted or are simply ignored over time. This is where most organizations are right now with their GitOps adoption.
In this workflow, your Git repository contains the complete definition of your infrastructure, and any changes must go through code review and version control.
Prerequisites: Understand Fundamentals and Modular infrastructure before exploring workflows.
How Classic GitOps works
When you run formae apply --mode reconcile
, formae:
- Reads your entire forma - Everything in your forma block
- Compares with current state - What currently exists in AWS
- Automatically creates a complete plan - What actually needs to be created, updated, or destroyed
- Applies all changes - Bringing reality in line with your desired target state, expressed in your code
This is called "reconcile mode" because formae reconciles the actual infrastructure state with your desired state defined in code.
// This forma completely defines your infrastructure
forma {
myVpc = new vpc.Vpc {
cidrBlock = "10.0.0.0/16"
tags {
new { key = "Name"; value = "production-vpc" }
}
}
// If you remove this subnet from the forma,
// it will be destroyed on next apply
new subnet.Subnet {
vpcId = myVpc.vpcId
cidrBlock = "10.0.1.0/24"
}
}
Hard reconcile vs soft reconcile
If you are really, really sure that external changes can be ignored, you can always run a hard reconcile from your code. When you aren't sure, and actually would like to be able to catch up on some external changes that happened outside of your control, then the soft reconcile is your mode of choice.
Hard reconcile (Classic GitOps) means formae will:
- Overwrite any external changes made to your resources
- Destroy resources that exist in AWS but not in your forma
- Create resources that exist in your forma but not in AWS
- Update resources to match your forma exactly
Soft reconcile (used in other workflows) means formae will:
- Preserve external changes when possible
- Warn about conflicts instead of overwriting
- Suggest extract commands to incorporate changes manually in your code
When you need the --force flag
If external changes are detected, formae will reject the apply to protect you from overwriting changes:
# This will fail if external changes exist
formae apply --mode reconcile main.pkl
# Use --force to proceed with hard reconcile anyway
formae apply --mode reconcile --force main.pkl
The --force
flag tells formae: "I know there are external changes, and I want to overwrite them anyway."
When resources get created, updated, or destroyed
Created: Resource exists in forma but not in AWS
// Adding this to your forma will create it
new s3.Bucket {
bucketName = "my-new-bucket"
}
Updated: Resource exists in both but with different properties
// Changing mutable properties updates the resource in-place
myVpc = new vpc.Vpc {
cidrBlock = "10.0.0.0/16"
tags {
new { key = "Name"; value = "updated-vpc-name" } // This updates in-place
new { key = "Environment"; value = "production" } // This is added
}
}
Replaced: Resource exists but immutable properties changed
// Changing immutable properties like cidrBlock causes replacement
myVpc = new vpc.Vpc {
cidrBlock = "10.1.0.0/16" // This triggers destroy + create
}
Destroyed: Resource exists in AWS but not in forma
// Removing a resource from your forma will destroy it
// (commented out = will be destroyed)
// new subnet.Subnet { ... }
Important: AWS resources have immutable properties that cannot be changed after creation. Changing these properties in your forma will cause the resource to be destroyed and recreated. Always check the AWS documentation to understand which properties are immutable.
Configuration for Classic GitOps
To ensure hard reconcile behavior, disable discovery and synchronization in your configuration:
agent {
synchronization {
enabled = false
}
discovery {
enabled = false
}
}
When to use Classic GitOps
- Automated deployments: Git push triggers infrastructure updates through CI/CD
- Reproducible environments: Same forma creates identical infrastructure anywhere
- Single source of truth: Your code is the only authority - no external changes
- Simplest workflow: No discovery, sync, or patch complexity to manage
- Greenfield projects: Starting fresh with complete control
Best practices
Day-0 (initial deployment)
- Start with reconcile mode
- Define your complete infrastructure in one go
- Test with
formae eval
before applying
Structural changes
- Always use
formae apply --simulate
to identify what will change - Use
--force
flag when you need to overwrite external changes - Consider the blast radius of your changes
# Simulate changes before applying
formae apply --mode reconcile --simulate main.pkl
# Force reconcile when external changes exist
formae apply --mode reconcile --force main.pkl
The magic of properties
Properties are a very powerful feature. They allow total blast radius and detail visibility reduction, enabling platform engineers to prepare reusable formas for developers or CI/CD pipelines to execute by providing only a few parameters. That is why properties also become instant CLI configuration options. Any property you define in a forma automatically becomes a command-line flag with helpful description and defaults.
Auto-generated CLI help
Adding --help
to your apply command reveals the properties you've created:
formae apply --help examples/complete/static-website/main.pkl
This displays standard CLI options plus a Properties section showing every customizable aspect:
Properties:
--bucket-suffix property: bucket-suffix [default: "-uber-unique-id"]
--domain property: domain [default: "test.platform.engineering"]
--env property: env [default: "dev"]
--project-name property: project-name [default: "static-website"]
--region property: region [default: "us-east-2"]
Make everything configurable
This means you can make any aspect of your infrastructure configurable:
properties {
instanceType = new formae.Prop { flag = "instance-type"; default = "t3.micro" }
databaseSize = new formae.Prop { flag = "db-size"; default = "20" }
backupRetention = new formae.Prop { flag = "backup-days"; default = "7" }
// Add as many as you need!
}
Your forma becomes a flexible template that teams can customize and provide to others without touching or even seeing the infrastructure code.
# Development environment
formae apply --mode reconcile --instance-type t3.micro main.pkl
# Production environment
formae apply --mode reconcile --instance-type c5.large --backup-days 30 --db-size 100 main.pkl
What's next
You now understand Classic GitOps workflow with reconcile mode. Explore other workflows and concepts:
- Always up-to-date GitOps - Extend Classic GitOps with discovery and synchronization
- No-Git GitOps - Experimental workflow without Git as source of truth
- Core Concepts: Discovery - Learn how formae discovers existing resources
- Core Concepts: Synchronization - Understand how formae tracks changes