Infrastructure as Code Deep Dive
A comprehensive guide to managing MolnOS infrastructure declaratively with molnos.json.
Table of Contents
Section titled “Table of Contents”- Mental Model
- File Structure
- Contexts
- Resource Definitions
- Bindings and References
- Execution Model
- Reconciliation
- Best Practices
- Troubleshooting
Mental Model
Section titled “Mental Model”MolnOS IaC is not Terraform. It’s closer to kubectl apply or docker compose—a thin declarative layer over the MolnOS control plane API.
Key differences from traditional IaC:
| Aspect | Traditional IaC | MolnOS IaC |
|---|---|---|
| State | Local state file | Live control plane |
| Execution | Plan → Apply | Direct apply |
| Complexity | DAG solver, providers | Simple reconciliation |
| Scope | Multi-cloud | MolnOS only |
The file is the desired state. MolnOS is the actual state. Apply reconciles them.
File Structure
Section titled “File Structure”Every molnos.json follows this structure:
{ "$schema": "https://schemas.molnos.cloud/schema-iac-v1.json", "version": "1", "context": { "name": "my-service", "intent": "Description of what this context does." }, "resources": { "functions": { }, "databases": { }, "storage": { }, "sites": { }, "applications": { }, "schemas": { } }, "identities": { "users": { }, "serviceAccounts": { } }}Required Fields
Section titled “Required Fields”version- Schema version (currently"1")
Optional Fields
Section titled “Optional Fields”$schema- JSON Schema URL for editor support (autocomplete, validation)context- Context metadata (defaults todefaultcontext)resources- Resource definitionsidentities- User and service account definitions
Contexts
Section titled “Contexts”A context is a logical grouping of related resources—an application, service, or workload.
Why Contexts Matter
Section titled “Why Contexts Matter”- Ownership: Every resource belongs to exactly one context
- Safe Pruning:
--pruneonly affects resources in the current context - Isolation: Different teams can manage different contexts independently
- Lifecycle: Destroy a context to remove all its resources
Context Definition
Section titled “Context Definition”{ "context": { "name": "billing-service", "intent": "Handles invoicing and payment processing for EU customers.", "attributes": { "data": { "sensitivity": "high", "residency": "eu" }, "availability": "business-critical", "regulatory": ["gdpr"], } }}Fields:
name- Unique identifier for the contextintent- Human-readable description of purposeattributes- Freeform metadata (not enforced, but queryable)
The Default Context
Section titled “The Default Context”If you omit context, resources go to the default context:
{ "version": "1", "resources": { "databases": { "scratch-data": {} } }}This is equivalent to "context": { "name": "default" }.
Destroying a Context
Section titled “Destroying a Context”To completely remove a context and all its resources, use the two-step process:
# Step 1: Delete all resources in the contextDELETE /contexts/billing-service/resources
# Step 2: Delete the context metadataDELETE /contexts/billing-serviceOr via CLI:
# Delete resources first, then the contextmolnos context delete-resources billing-servicemolnos context delete billing-service
# Or use destroy to do both in one commandmolnos context destroy billing-serviceThe delete-resources operation returns a detailed result showing what was deleted:
{ "success": true, "context": "billing-service", "deleted": [ { "type": "function", "name": "invoice-api" }, { "type": "database", "name": "invoices" }, { "type": "storage", "name": "pdfs" } ], "errors": [], "summary": { "totalResources": 3, "deletedCount": 3, "failedCount": 0 }}If some resources fail to delete, success will be false and HTTP status will be 207 Multi-Status. The errors array will contain details about what failed.
Cannot Delete Default Context
The default context cannot be deleted. You can delete its resources, but the context itself will remain.
Resource Definitions
Section titled “Resource Definitions”Functions
Section titled “Functions”{ "functions": { "invoice-api": { "source": "./dist/invoice.js", "methods": ["GET", "POST", "PUT"], "triggers": [ { "type": "http" }, { "type": "event", "eventName": "invoice-requested" } ], "allowUnauthenticated": false, "passAllHeaders": false, "bindings": [ { "service": "databases", "resource": "invoices", "permissions": ["read", "write"] }, { "service": "storage", "resource": "pdfs", "permissions": ["read"] } ] } }}Fields:
| Field | Required | Description |
|---|---|---|
source | * | Path to pre-built JavaScript file (for CLI use) |
code | * | Inline function code (for API use) |
methods | No | Allowed HTTP methods (default: all) |
triggers | No | Array of trigger configs: { type: "http" } or { type: "event", eventName: "..." } |
allowUnauthenticated | No | Allow public access (default: false) |
passAllHeaders | No | Pass internal headers (default: false) |
bindings | No | Service bindings for resource access |
* Either source or code is required. When using the CLI, specify source and the CLI will read the file and send code to the API. When calling the API directly, provide code with the inline function content.
Important: Source files must be pre-built. MolnOS does not run build tools.
Databases
Section titled “Databases”{ "databases": { "invoices": {}, "customers": {}, "audit-log": {} }}Tables are defined by name only. Empty object {} declares the table exists.
MolnOS uses PikoDB—no schema definition, no tuning parameters.
Storage Buckets
Section titled “Storage Buckets”{ "storage": { "invoice-pdfs": { "public": false }, "static-assets": { "public": true } }}Fields:
| Field | Required | Description |
|---|---|---|
public | No | Whether bucket is publicly accessible (default: false) |
{ "sites": { "billing-dashboard": { "source": "./dist/site" } }}Fields:
| Field | Required | Description |
|---|---|---|
source | * | Path to directory containing static site files (for CLI use) |
files | * | Array of { path, content } objects with base64-encoded content (for API use) |
* Either source or files is required. When using the CLI, specify source and the CLI will read the directory, base64-encode file content, and send files to the API. When calling the API directly, provide files with base64-encoded content.
The directory is content-addressed. Uploads are skipped if content hasn’t changed.
Applications
Section titled “Applications”{ "resources": { "applications": { "inbox-app": { "description": "MolnOS Inbox Application", "redirectUris": ["http://localhost:3000/auth-callback"] } } }}Fields:
| Field | Required | Description |
|---|---|---|
description | No | Human-readable description of the application |
redirectUris | Yes | Allowed OAuth redirect URIs for the application |
Natural key: The object key (application name)
Schemas
Section titled “Schemas”{ "resources": { "schemas": { "order-created": { "description": "Emitted when a new order is placed", "schema": { "properties": { "orderId": { "type": "string" }, "amount": { "type": "number" }, "currency": { "type": "string" } }, "required": ["orderId", "amount", "currency"], "additionalProperties": false } } } }}Fields:
| Field | Required | Description |
|---|---|---|
description | No | Human-readable description of the schema |
schema | Yes | Schema definition with properties, optional required, and additionalProperties |
Natural key: The object key (schema name, must be kebab-case)
Schemas are always considered for update on apply—if the schema exists, the definition is updated and the version is incremented automatically.
{ "identities": { "users": { "billing-admin": { "roles": ["administrator"] }, "support-agent": { "roles": ["user"] } } }}Natural key: email
Service Accounts
Section titled “Service Accounts”{ "identities": { "serviceAccounts": { "invoice-processor": { "roles": ["user"], "description": "Automated invoice processing" }, "backup-agent": { "roles": ["user"], "description": "Nightly backup automation" } } }}Natural key: The object key (e.g., invoice-processor)
Bindings and References
Section titled “Bindings and References”Within-Context References
Section titled “Within-Context References”Bindings reference resources by name:
{ "bindings": [ { "service": "databases", "resource": "invoices", "permissions": ["read", "write"] } ]}The invoices database must be defined in the same context.
Cross-Context References
Section titled “Cross-Context References”For resources in other contexts, use qualified names:
{ "bindings": [ { "service": "databases", "resource": "shared-data:audit-log", "permissions": ["write"] } ]}Format: context-name:resource-name
Cross-context references require the target resource to already exist. IaC does not create resources in other contexts.
Permission Levels
Section titled “Permission Levels”{ "permissions": ["read", "write", "delete"]}Available permissions vary by service:
| Service | Permissions |
|---|---|
| databases | read, write, delete |
| storage | read, write, delete |
Execution Model
Section titled “Execution Model”CLI Commands
Section titled “CLI Commands”# Validate without applyingmolnos validatemolnos validate path/to/molnos.json
# Show what would changemolnos diffmolnos diff path/to/molnos.json
# Apply configurationmolnos applymolnos apply path/to/molnos.json
# Apply and prune orphaned resourcesmolnos apply --prune
# Apply to specific endpointmolnos apply --endpoint http://localhost:8080Validate
Section titled “Validate”Checks JSON syntax and schema compliance. Does not contact MolnOS.
Catches:
- Invalid JSON
- Missing required fields
- Invalid resource configurations
- Binding references to undefined resources (warning)
Shows what would change without modifying anything:
+ database: invoices (create)~ function: invoice-api (update)- storage: old-bucket (delete, requires --prune)= database: customers (unchanged)For each resource type:
- Query live state by natural key
- If missing → create
- If exists → update
- If
--pruneand extra resources exist → delete
--prune is context-scoped. It only removes resources that:
- Belong to the current context
- Are not declared in the IaC file
Resources in other contexts are never affected.
Reconciliation
Section titled “Reconciliation”Natural Keys
Section titled “Natural Keys”Each resource type has a natural key used for matching:
| Resource Type | Natural Key |
|---|---|
| Function | name (object key) |
| Database | table name (object key) |
| Storage Bucket | bucket name (object key) |
| Site | projectId (object key) |
| Application | name (object key) |
| Schema | name (object key) |
| User | email |
| Service Account | name (object key) |
No State File
Section titled “No State File”MolnOS IaC is stateless. The reconciliation algorithm:
- Read desired state from
molnos.json - Read actual state from live MolnOS control plane
- Compute delta
- Apply changes
There’s no local state to corrupt, sync, or version.
Drift Resolution
Section titled “Drift Resolution”Drift is resolved by overwrite. If live state differs from declared state:
- The declared state wins
- MolnOS updates the resource to match the file
This is intentional. The file is the source of truth.
Best Practices
Section titled “Best Practices”1. One Context Per Service
Section titled “1. One Context Per Service”Keep related resources together:
{ "context": { "name": "user-service" }, "resources": { "functions": { "user-api": { } }, "databases": { "users": {}, "sessions": {} } }}2. Use Intent and Attributes
Section titled “2. Use Intent and Attributes”Document purpose for future maintainers:
{ "context": { "name": "billing-service", "intent": "Handles invoicing. Processes ~10k invoices/month.", "attributes": { "data": { "sensitivity": "high" } } }}3. Version Control Your IaC Files
Section titled “3. Version Control Your IaC Files”Store molnos.json alongside application code:
my-service/├── src/├── dist/├── molnos.json└── package.json4. Build Before Apply
Section titled “4. Build Before Apply”Always build your functions and sites before applying:
npm run buildmolnos applyMolnOS does not run build tools.
5. Validate in CI
Section titled “5. Validate in CI”Add validation to your CI pipeline:
molnos validate molnos.json6. Use Specific Binding Permissions
Section titled “6. Use Specific Binding Permissions”Request only the permissions you need:
{ "bindings": [ { "service": "databases", "resource": "users", "permissions": ["read"] } ]}Not:
{ "bindings": [ { "service": "databases", "resource": "users", "permissions": ["read", "write", "delete"] } ]}7. Separate Environments
Section titled “7. Separate Environments”Use different contexts or files per environment:
molnos apply molnos.cloud.jsonmolnos apply molnos.prod.jsonOr same file, different endpoints:
molnos apply --endpoint http://dev.internal:3000molnos apply --endpoint http://prod.internal:3000Troubleshooting
Section titled “Troubleshooting””Invalid configuration” errors
Section titled “”Invalid configuration” errors”Check JSON syntax:
cat molnos.json | jq .Validate against schema:
molnos validate“Resource not found” for bindings
Section titled ““Resource not found” for bindings”Bindings reference resources by name. Ensure the target exists:
{ "databases": { "invoices": {} }, "functions": { "api": { "bindings": [ { "service": "databases", "resource": "invoices", "permissions": ["read"] } ] } }}“Environment variable not set”
Section titled ““Environment variable not set””Check your shell environment before applying:
printenv | grep STRIPEexport STRIPE_API_KEY=sk_test_...molnos applyApply succeeds but function fails
Section titled “Apply succeeds but function fails”Ensure source files are built:
npm run buildls -la dist/molnos applyUnexpected deletions with —prune
Section titled “Unexpected deletions with —prune”--prune only affects the current context. Check which context you’re applying:
molnos diffResources marked for deletion should only be those in your context that aren’t in the file.
Cross-context reference fails
Section titled “Cross-context reference fails”Cross-context references require the target to exist first:
# First, apply the shared contextmolnos apply shared-data.json
# Then apply the dependent contextmolnos apply billing-service.json