v0.4.0 — Charm

Elemental Infrastructure Framework

Build infrastructure the way nature builds matter

EIF is a provider-agnostic Terraform framework that models infrastructure with the elegance of chemistry. Compose atoms into molecules, molecules into matter — on any cloud. The CLI lives here; the component library lives in eif-library ↗.

Explore the Model View Structure →
eif package install && eif apply aws three-tier-app dev

Founding principles

The philosophy

Modern cloud infrastructure suffers from two opposite extremes: the monolith temptation — a single Terraform repository that holds everything — and the chaotic fragmentation of disconnected modules. EIF proposes a third way, inspired by chemistry: every cloud resource has its own atomic identity, composable with precision into increasingly complex structures, up to fully deployable applications.

"Build infrastructure the way nature builds matter — atom by atom,
molecule by molecule, until complexity emerges from simplicity."

— EIF Design Manifesto

The compositional model

Three levels of abstraction

Each level builds on the previous. Atoms and molecules are internal building blocks — they are never deployed directly. Matter is the only user-facing level, the sole entry point for every deployment.

LEVEL 01 — ATOM

Atom

A single cloud service in plain HCL. The primitive building block of the framework — scoped to one service only. Namespaced by cloud. Atoms are composed by molecules and are never deployed directly.

atoms/aws/compute/lambda/1.0.0/ atoms/aws/networking/cloudfront/1.0.0/ atoms/aws/storage/s3/1.0.0/ · rds/1.0.0/ atoms/aws/security/waf/1.0.0/ · sg/1.0.0/ atoms/azure/storage/blob/1.0.0/ atoms/azure/networking/frontdoor/1.0.0/ atoms/gcp/storage/gcs/1.0.0/ atoms/gcp/networking/cdn/1.0.0/ atoms/gcp/security/armor/1.0.0/

LEVEL 02 — MOLECULE

Molecule

A reusable architectural blueprint that combines atoms into a coherent pattern. Molecules are composed by matter and are never deployed directly.

molecules/aws/single-page-application/1.0.0/ ├── s3/1.0.0 + cloudfront/1.0.0 + waf/1.0.0 molecules/aws/db/1.0.0/ ├── rds/1.0.0 + sg/1.0.0 molecules/aws/lambda-svc/1.0.0/ ├── lambda/1.0.0 + sg/1.0.0 molecules/azure/single-page-application/1.0.0/ ├── blob/1.0.0 + frontdoor/1.0.0 molecules/gcp/single-page-application/1.0.0/ ├── gcs/1.0.0 + cdn/1.0.0 + armor/1.0.0

LEVEL 03 — MATTER

Matter

A complete application composed of molecules. Structure declared once in composition.json with exact semver pins — the composition is the lock. Each <env>.json is a flat variable pool; environment is injected automatically.

matters/three-tier-app/aws/ ├── composition.json # molecule list + exact pins ├── dev.json · prod.json └── main.tf.j2 matters/single-page-application/aws/ matters/single-page-application/azure/ matters/single-page-application/gcp/

Cloud agnostic

Provider abstraction

Each cloud provider is a self-contained directory. Adding support for a new cloud requires no changes to eif.py — just drop a template in the right place.

providers/
├── aws/
│   └── provider.tf.j2    # terraform{} + provider "aws" — profile or assume_role
├── azure/
│   └── provider.tf.j2    # terraform{} + provider "azurerm" — subscription + tenant
└── gcp/
    └── provider.tf.j2    # terraform{} + provider "google" — project + region

The accounts.json entry declares "provider": "aws" (or azure, gcp).
eif render resolves the right template, renders it with account config as context,
and prepends it automatically to every rendered output — matter templates contain only module blocks.

To contribute a new provider: create providers/<cloud>/provider.tf.j2,
add atoms under atoms/<cloud>/ and molecules under molecules/<cloud>/.

Zero core changes required.

Stability guarantee

Versioning

Atoms and molecules use semantic versioning (MAJOR.MINOR.PATCH). Every breaking change creates a new version directory — compositions pinned to old versions are never touched.

patch  →  bug fix, no interface change  1.0.0 → 1.0.1
minor  →  new optional variable or output  1.0.0 → 1.1.0
major  →  breaking change (required var, type change, removed output)  1.0.0 → 2.0.0

atoms/aws/storage/rds/1.0.0/ ← matter stays pinned here
atoms/aws/storage/rds/2.0.0/ ← breaking: new required variable

Each matter composition pins exact versions — upgrade is always a deliberate choice.
# composition.json — the composition is the lock
{
  "matter": "three-tier-app",
  "molecules": [
    { "name": "db",  "source": "aws/db",  "version": "1.2.0" },
    { "name": "spa", "source": "aws/spa", "version": "3.0.0" }
  ]
}

# diff what would break before updating
eif diff molecule aws db 1.2.0 2.0.0

# update with interactive diff + confirmation
eif package update aws/db
eif package update --safe          # skip major bumps

Package management

Packages

eif package is EIF's package manager for infrastructure components. Packages are pre-built molecules from the registry. Atoms are never installed directly — they are bundled automatically as dependencies when their parent molecule is downloaded. Install is explicit: nothing downloads automatically.

MANIFEST

eif.project.json

Project manifest. Declares named registries with type, URL, and priority. Auth lives in eif.secure.json (gitignored).

{ "name": "my-infra", "registries": [ { "name": "official", "type": "github", "url": "https://...", "priority": 0 } ] }

CACHE

eif_packages/

Local store of downloaded molecules and their bundled atom dependencies. Gitignored. Shared across all matters in the project.

eif_packages/ ├── atoms/aws/storage/rds/1.0.0/ └── molecules/aws/db/1.2.0/ └── main.tf · variables.tf outputs.tf
# create a new matter — queries registry live, pins latest versions $ eif new matter my-app querying registry... ◉ db 1.2.0 ◉ single-page-application 3.0.0 aws/db@1.2.0 installing... aws/db@1.2.0 installed aws/single-page-application@3.0.0 installing... aws/single-page-application@3.0.0 installed # after cloning a project — install all pinned packages $ eif package install aws/db@1.2.0 already cached aws/spa@3.0.0 downloading... done # check for updates across all matters $ eif package outdated aws/db 1.2.0 → 1.3.0 available aws/spa 3.0.0 → 4.0.0 available # update with diff + confirmation $ eif package update aws/db + var read_replica_count number (optional) + out replica_endpoints update aws/db 1.2.0 → 1.3.0? (Y/n) yes aws/db@1.3.0 installed # render warns non-blocking when updates are available $ eif render aws my-app dev 🔧 rendered → .rendered/dev/main.tf ⚠ aws/spa 3.0.0 → 4.0.0 available run: eif package update

If a molecule is missing when rendering, eif fails fast with a clear install message.
Atom dependencies are bundled automatically when a molecule is installed — their relative paths are preserved in eif_packages/ so Terraform resolves them without modification.

Local molecules (authored but not yet published) coexist alongside registry packages — the renderer checks eif_packages/ first, then falls back to local molecules/.

Multiple registries, priority-ordered. One cache. No lock file — the composition is the lock.

Developer experience

Scaffold commands

eif init [folder] scaffolds a new project in seconds — pass a folder name and it is created automatically. eif new adds atoms, molecules, and matters. eif remove deletes them with a confirmation prompt. All commands are fully interactive.

# initialise in a new folder (created automatically)
eif init my-infra
# or initialise in the current directory
eif init
  ◻ aws   ◻ azure   ◻ gcp  (space to select)
  ✨ created   → providers/aws/  providers/azure/
  ✨ created   → accounts.json · eif.project.json · .gitignore · matters/

# scaffold a new atom — prompts: name, provider, category, bump type
eif new atom
  📦 atoms/aws/networking/cdn-origin — no existing versions, creating 1.0.0
  ✨ created   → atoms/aws/networking/cdn-origin/1.0.0/{main,variables,outputs}.tf

# scaffold a new matter — queries registry live, pins latest versions
eif new matter serverless-api
  ✨ created   → matters/serverless-api/aws/{composition.json,dev.example.json,main.tf.j2}

# remove a component — shows files, confirms before deleting
eif remove molecule aws db
  remove  molecules/aws/db/
  - molecules/aws/db/1.0.0/main.tf
  delete molecules/aws/db? (y/N)

If an atom or molecule already exists, the command reports the latest version
and asks for the bump type — patch / minor / major — computing the next semver automatically.

Providers are auto-detected from the providers/ directory — no hardcoded list, no config needed.

New providers are automatically available to eif new. eif cache clean wipes eif_packages/ safely.

Deployment lifecycle

State & rollback

EIF wraps the full Terraform lifecycle. Every successful apply saves a snapshot of the rendered main.tf — locally and, if a backend is configured, in the same remote bucket as the Terraform state. Rollback restores any previous snapshot and re-applies.

# plan — render + terraform plan (no changes applied)
eif plan aws three-tier-app dev

# apply — render + init + apply + snapshot on success
eif apply aws three-tier-app dev
  [eif] rendered  → matters/three-tier-app/aws/.rendered/dev/main.tf
  [eif] running   terraform -chdir=... init
  [eif] running   terraform -chdir=... apply
  [eif] snapshot  → .history/dev/20260320T143012Z/main.tf

# rollback — pick a snapshot and re-apply
eif rollback aws three-tier-app dev
  snapshot to restore:
  ❯ 20260320T143012Z
    20260319T091500Z
    20260318T182244Z

# destroy — terraform destroy against last rendered output
eif destroy aws three-tier-app dev

Add a backend key to any account in accounts.json to enable remote state.
EIF injects the correct backend {} block automatically — S3, Azure Blob, or GCS.

Bootstrap the state bucket in one command:
eif config backend aws three-tier-app prod
eif add account ← add an account entry (one-time, per account)

No backend? EIF keeps snapshots in .history/ — gitignored, local-only.

Repository layout

Project structure

This repo is the CLI tool only. The component library — atoms, molecules, and matters — lives in eif-library ↗. Each directory is self-contained and follows a consistent naming convention.

eif/                            # this repo — CLI tool only

├── eif.py                     # renderer, lifecycle, scaffold, package CLI
├── pyproject.toml              # Python project (uv / hatchling)

└── examples/                   # Reference implementation
    ├── accounts.example.json
    ├── eif.project.json      # project manifest
    ├── providers/              # aws · azure · gcp
    ├── atoms/
    ├── molecules/
    └── matters/

Your project repo follows the full component structure:

your-project/                   # your infrastructure repo

├── accounts.json              # env → cloud account config
├── eif.project.json         # project manifest (name + registries)
├── eif_packages/             # gitignored — downloaded atoms + molecules
│   ├── atoms/<cloud>/<cat>/<name>/1.0.0/
│   └── molecules/<cloud>/<name>/1.2.0/

├── providers/                  # Pluggable cloud provider templates
│   ├── aws/provider.tf.j2 · backend.tf.j2
│   └── ...

├── atoms/                     # local atoms (authoring only)
├── molecules/                  # local molecules (authoring only)

└── matters/                    # Deployable applications
    └── <name>/<cloud>/
        ├── composition.json   # molecule list + exact semver pins
        ├── <env>.json         # flat variable pool per environment
        └── main.tf.j2         # wiring template

Open source. Build with it.

Star the repos, open an issue, or contribute atoms and molecules to eif-library.