Coming from Terraform, Pulumi or CDK¶
Atlantide occupies the same slot as these tools: declare infrastructure, diff it against recorded state, reconcile the difference. What differs is where the determinism guarantee comes from, and how much of the engine is visible to you.
Vocabulary¶
| You know | Here it is called | Notes |
|---|---|---|
terraform plan / apply |
atlantide plan / apply |
plan needs no credentials. |
| HCL / TypeScript program | Atlas-lang config (infra.py) |
Real Python, executed by a bounded interpreter. |
| Provider | Provider | Python packages, discovered by entry point. |
Module / ComponentResource / Construct |
Component | Python; config uses them but cannot define them. |
terraform init |
atlantide component add |
Pins to a commit + content hash. |
| Backend / remote state | State backend | SQLite, S3+DynamoDB, or Postgres. |
terraform import |
atlantide import |
Refuses to import a resource that differs from config. |
terraform refresh |
atlantide refresh |
Read-only unless --write. |
depends_on |
depends_on |
Rarely needed; reading a field creates the edge. |
lifecycle { prevent_destroy } |
Lifecycle(prevent_destroy=True) |
Plus create_before_destroy, ignore_changes, aliases. |
moved block |
Lifecycle(aliases=(...)) |
Rename without replace. |
terraform output |
atlantide output |
Sensitive values need --reveal. |
| Workspace | Stack and/or profile | Stacks are in-config; profiles are in atlantide.toml. |
| Sentinel / OPA | Policies | Native Python, evaluated at plan time. |
| Terraform Registry | No central registry | Components are git URLs, pinned by commit. |
The substantive differences¶
A real language, but a bounded one. Pulumi and CDK give you a full
programming language and, with it, the ability to write a config whose plan
depends on the clock, the environment or a network call. Terraform avoids that
by not being a programming language. Atlantide takes a third route: configs are
Python — typed, mypy-checkable, refactorable — but they run under
Atlas-lang, an interpreter with
no clock, randomness, environment, network or filesystem. Determinism is not a
convention authors must observe; the interpreter provides no way to break it.
The diff is a Merkle comparison, not a per-resource read. Each node's hash folds in its dependencies' hashes, so an unchanged subgraph is skipped without a single provider call. See The Merkle hash.
plan needs no credentials. Everything up to the diff is pure computation
over the config and the recorded state. validate is cheap enough for a
pre-commit hook, and plan can run on a pull request without being handed a
state backend it should not have.
Per-field mutability is in the type. Every field is declared mutable(),
immutable() or computed(), so whether a change costs an in-place update or a
recreate is visible before anything runs — not discovered from the plan output.
Build once, deploy anywhere. atlantide build compiles a config into a
content-hashed .atlas artifact with provider versions pinned. deploy applies
the artifact without re-executing the config, so the bytes promoted from staging
to production are identical.
Where atlantide is behind¶
Worth knowing before you migrate anything real:
- Provider coverage is small. Three built-in providers and a couple of dozen
AWS resource types, against Terraform's thousands.
atlantide resourceslists exactly what exists. The plugin interface is open, but there is no ecosystem to draw on yet. - No central registry. Components are git URLs you pin yourself.
- No HCL interoperability. There is no path that reads Terraform
configuration or state. Adoption goes through
atlantide import, resource by resource. - The state format is versioned and still moving. It migrates forward automatically and an older atlantide refuses a newer store, but it can change between minor releases.
- Beta. The classifier says so.
What migration actually looks like¶
There is no converter. The realistic path:
- Write the config for a slice of existing infrastructure.
atlantide validate, thenatlantide plan— everything will show asCREATE, because state knows nothing yet.atlantide importeach resource. Anything whose live values differ from config is refused rather than adopted, so this doubles as a check that the config you wrote matches what is actually deployed.atlantide planagain. The slice should now be entirelyNOOP.
Step 3 is the honest part of the work, and --dry-run makes it free to attempt.