Architecture
Oxid is a standalone infrastructure-as-code engine written in Rust. It parses HCL natively and communicates directly with Terraform providers via gRPC - no Terraform binary required.
Pipeline Overview
Every Oxid operation follows the same pipeline:
.tf files
→ Config Parser (HCL → WorkspaceConfig)
→ DAG Builder (WorkspaceConfig → ResourceGraph)
→ Provider Manager (gRPC connections)
→ Planner (diff state vs config)
→ DAG Walker (parallel execution)
→ State Backend (persist results)Core Components
Config Parser
Reads .tf files using hcl-rs and produces a WorkspaceConfig - the unified intermediate representation for all infrastructure configuration. Handles variable resolution, string interpolation, and merging of multiple .tf files.
- Loads
.tfvarsfiles andTF_VAR_*environment variables - Resolves variable references (
var.xxx) and interpolation ($}var.xxx{-suffix) - Applies Terraform-compatible variable precedence
DAG Builder
Converts WorkspaceConfig into a directed acyclic graph (petgraph::DiGraph) where each node is a resource and each edge is a dependency. Handles:
countandfor_eachexpansion into individual resource nodes- Explicit
depends_onedges - Implicit edges from expression references (e.g.,
aws_vpc.main.id) - Cycle detection and validation
Provider Manager
Downloads provider binaries from registry.terraform.io, launches them as child processes, and manages gRPC connections using the tfplugin5 and tfplugin6 protocols.
- Compiled from
proto/tfplugin5.protoandproto/tfplugin6.protoviabuild.rs - Connection pooling with
Arc<RwLock<HashMap>> - Supports all Terraform providers - AWS, GCP, Azure, Kubernetes, and every community provider
- Provider binaries cached in
.oxid/providers/
Planner
Compares the current state against the desired configuration to produce a set of ResourceAction values for each resource:
- Create - Resource exists in config but not in state
- Update - Resource exists in both but attributes differ
- Delete - Resource exists in state but not in config
- Replace - Resource must be destroyed and recreated (ForceNew attribute changed)
- Read - Data source needs to be refreshed
- NoOp - Resource is unchanged
DAG Walker
The execution engine that walks the dependency graph and executes operations in parallel. This is where Oxid's performance advantage comes from.
State Backend
Persists resource state, outputs, dependencies, runs, and history. Implements the StateBackend trait with SQLite and PostgreSQL backends.
Event-Driven Parallelism
Oxid uses event-driven per-resource parallelism instead of Terraform's wave-based approach.
Wave-based (Terraform)
Terraform processes resources in "waves" - all resources at depth 0 must complete before any resource at depth 1 starts. This means the slowest resource in each wave blocks everything else at the same depth.
Wave 0: [VPC] ─────────────────────────────── wait ────── Wave 1: ─── wait ─── [Subnet A] [Subnet B] [IGW] ── wait Wave 2: ─── wait ──────────── wait ─── [Instance A] [Instance B]
Event-driven (Oxid)
In Oxid, each resource starts executing the moment all its dependencies have completed. There are no waves - resources are dispatched individually as they become ready.
[VPC] ──→ [Subnet A] ──→ [Instance A]
├──→ [Subnet B] ──→ [Instance B]
└──→ [IGW] (starts immediately when VPC completes)This results in faster applies, especially for deep dependency graphs with uneven execution times.
Concurrency Model
Oxid uses several concurrency primitives from Tokio and the Rust ecosystem:
Arc<Semaphore>- Controls maximum parallelism in the DAG walker (default 10, configurable via--parallelism)DashMap- Lock-free concurrent hash map for resource state during planning and apply. Multiple tasks can read and write resource states without blocking each other.Arc<RwLock<HashMap>>- Provider connection pooling. Read lock for concurrent gRPC calls, write lock only during provider startup and configuration.tokio::sync::mpsc- Channel-based communication between the DAG walker and task executors. Completed tasks send events back to the walker, which dispatches newly-ready dependents.
#[async_trait], enabling non-blocking database I/O during parallel execution.Expression Evaluation
The EvalContext in the executor handles runtime expression evaluation:
- Variable references -
var.instance_typeresolved from defaults and tfvars - Cross-resource references -
aws_vpc.main.idresolved from the shared DashMap populated during apply - Count index -
count.indexfor indexed resources - Each key/value -
each.keyandeach.valuefor for_each resources - String interpolation -
$}var.prefix{-suffixwith concatenation
Error Handling
Oxid uses a layered error handling strategy:
anyhow::Resultwith.context()chains for detailed error messagesthiserrorfor typed custom errors in the public API- Cascade-skip on failure - When a resource fails, only its transitive dependents are skipped. All other independent resources continue executing.