Migrating from Rain / Raw CloudFormation
Rain is no longer actively maintained. If you built a deploy workflow around it — rain deploy, rain watch, a Makefile calling rain fmt in CI — that workflow now has no owner. Your CloudFormation templates are still good. What's gone is the CLI, the artifact-bucket bootstrapping, and the drift/changeset ergonomics Rain gave you on top of the raw AWS CLI. Atmos's native aws/cloudformation component type replaces that layer without asking you to rewrite a single resource.
Why This Guide?
AWS archived Rain upstream in 2026. If you're one of its users, you're likely facing one of two situations:
- You used Rain's CLI (
deploy,diff,watch,fmt,ls,bootstrap) to wrap the raw AWS CLI/API with something more usable, and now that tool has no future. - You used Rain's
!Rain::template directives (Constant,Env,Include,Embed,S3,Module) to work around gaps in plain CloudFormation, and your templates won't deploy anywhere without first resolving those directives.
This guide covers both. There is no Rain compatibility layer in Atmos — no !Rain:: directive
preprocessing, no Rain config-file support — so if your templates use directives, resolving them
is the first real step, not an afterthought.
Crawl, Walk, Run
- Crawl: Get a template deploying through Atmos in about 20 minutes (this guide)
- Walk: Directive resolution, artifact-bucket provisioning, and cross-component Outputs
- Run: Advanced features when you need them (StackSets, provision targets, inheritance)
Crawl: Get Running in 20 Minutes
What You're Going To Do
- Install Atmos
- Resolve any
!Rain::directives in your template (skip if you're on raw CloudFormation already) - Create a minimal
atmos.yaml - Create one stack YAML file
- Run
atmos aws cloudformation plan
Step 1: Install Atmos
See the full Installation Guide for all options.
Step 2: Resolve !Rain:: Directives (If Any)
If your templates don't use !Rain:: directives, skip to Step 3.
Atmos never preprocesses a file-based CloudFormation template — path: points at a file that gets
read and submitted to the CloudFormation API as-is. That means a template still containing !Rain::*
tags is not valid CloudFormation as far as Atmos (or CloudFormation itself) is concerned. Resolve
each directive to a plain-CloudFormation or Atmos-native equivalent first:
!Rain:: directive | What it did | Atmos-native replacement |
|---|---|---|
Constant | Injected a named constant's value into the template at preprocess time | A CloudFormation Parameters: entry, fed from the component's parameters: section in stack config |
Env | Injected an environment variable's value at preprocess time | Same as Constant, with the stack-config value set via !env |
Include | Merged an external JSON/YAML fragment into the template at preprocess time | CloudFormation's own native Fn::Transform/AWS::Include intrinsic for fragment reuse (a CloudFormation feature, not Rain- or Atmos-specific); AWS CDK for anything more structural |
Embed | Inlined a local file's contents as a string literal (e.g. Lambda inline code) at preprocess time | Inline the content directly with a YAML block scalar for small scripts; pre-upload larger assets to S3 and reference the URL directly |
S3 | Uploaded a local file/directory to S3 and rewrote the reference at preprocess time | Partial support today — see S3 Packaging Is Size-Triggered, Not Asset-Aware below |
Module | Client-side, multi-file template composition (marked experimental in Rain's own docs) | Not supported — use AWS CDK for real modular template composition |
Before/After: Constant/Env → Parameters
- Before (Rain)
- After (Atmos)
# template.yaml (Rain-preprocessed)
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Rain::Constant BucketNamePrefix-bucket
Tags:
- Key: Owner
Value: !Rain::Env DEPLOY_OWNER
# template.yaml (plain CloudFormation — no directives)
Parameters:
BucketNamePrefix:
Type: String
DeployOwner:
Type: String
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${BucketNamePrefix}-bucket"
Tags:
- Key: Owner
Value: !Ref DeployOwner
# stacks/dev.yaml
components:
"aws/cloudformation":
my-bucket:
path: template.yaml
parameters:
BucketNamePrefix: acme-plat
DeployOwner: !env DEPLOY_OWNER
Before/After: Embed → Inline Block Scalar
- Before (Rain)
- After (Atmos)
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: !Rain::Embed handler.py
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
def handler(event, context):
return {"statusCode": 200}
This is a one-time manual flatten. For anything beyond a few lines, package the function as a real S3-hosted deployment artifact instead of inlining it.
Step 3: Create Minimal atmos.yaml
Point Atmos to where your CloudFormation templates live:
If your templates already live in a directory like cloudformation/ or infra/, point
components."aws/cloudformation".base_path there directly instead of moving files.
Step 4: Create Your First Stack
Atmos parameters: requires a map from parameter names to values. If your existing JSON file
already uses that format, include it directly. If it uses the AWS CLI
ParameterKey/ParameterValue array format, convert it to a map first and keep the original
file for your existing workflow.
For example, convert this AWS CLI parameter array:
[
{"ParameterKey": "CidrBlock", "ParameterValue": "10.0.0.0/16"},
{"ParameterKey": "Environment", "ParameterValue": "dev"}
]
into a new parameter-map file:
Include the converted file in the stack:
The !include function parses JSON/YAML natively and resolves paths
relative to the stack file. It does not convert AWS CLI parameter arrays into maps. Do not include
an AWS CLI array directly: the current parameter handling silently ignores its values, leaving
those parameters unspecified.
The AWS API's UsePreviousValue flag cannot be set through Atmos's parameters: map. Supply
explicit values instead; entries using that flag must be replaced with the intended values before
conversion. UsePreviousValue is not a literal parameter value.
Step 5: Run Atmos
- Before (Rain)
- After (Atmos)
rain deploy template.yaml acme-plat-dev-vpc --params CidrBlock=10.0.0.0/16
rain diff template.yaml acme-plat-dev-vpc
atmos aws cloudformation plan vpc -s dev
atmos aws cloudformation deploy vpc -s dev
The cfn alias works everywhere cloudformation does: atmos aws cfn deploy vpc -s dev.
deploy streams per-resource stack events live as they converge — the same "watch it happen"
experience Rain's own deploy animation gave you — and ends with a rendered summary of the stack's
Outputs, so you get that view both at deploy time and on demand afterward with
atmos aws cloudformation output vpc -s dev.
You're now running Atmos.
What Just Happened?
Key Differences at a Glance
| Aspect | Rain | Atmos |
|---|---|---|
| Templates | .yaml/.json, optionally with !Rain::* directives | Same templates, directives resolved to plain CloudFormation |
| CLI | rain deploy/diff/watch/fmt/ls/bootstrap/... | atmos aws cloudformation <verb> (alias cfn) — see the verb cross-reference |
| Config | --params flags or a parameters JSON file | Stack YAML parameters: map (or !include a JSON map; convert AWS CLI arrays first); UsePreviousValue is unsupported |
| Artifact bucket | Auto-created silently on first use | Explicit atmos aws cloudformation backend create, or opt-in provision.backend.enabled: true |
| Environments | Separate command invocations per stack name | Stack YAML files, with inheritance and imports |
| Cross-stack values | Manual aws cloudformation describe-stacks calls | atmos aws cloudformation output, or !aws.cloudformation.output for cross-component consumption |
What Stays The Same
- Your CloudFormation templates work as-is, once any
!Rain::directives are resolved - Your resource definitions, intrinsic functions (
!Ref,!GetAtt,!Sub, ...), and capabilities are untouched - Parameter values can be reused through
!include: convert AWS CLI arrays to maps and replaceUsePreviousValueentries with explicit values
What You Added
atmos.yaml— tells Atmos where your templates live- Stack YAML files — one per environment, replacing per-invocation
--paramsflags
Walk: Immediate Value
1. Rain → Atmos Verb Cross-Reference
| Rain command | Atmos-native equivalent | Notes |
|---|---|---|
rain deploy | atmos aws cloudformation deploy | deploy = apply with --auto-approve implied |
rain diff | atmos aws cloudformation diff (or plan) | Same operation, two names — both create and render a changeset without executing it |
rain fmt | atmos aws cloudformation fmt [--check] | Native comment-preserving YAML round-trip — there's no cfn-format binary to shell out to anymore |
rain cat | atmos aws cloudformation get template [--original] | Fetches the deployed stack's template via GetTemplate |
rain ls | atmos aws cloudformation list | Account-wide, annotated with whether each stack matches a configured component's stack_name |
rain log | atmos aws cloudformation logs [--chart] | Combined event log across a stack and its nested stacks |
rain watch | atmos aws cloudformation watch | Attaches to a stack's in-progress or already-terminal operation |
rain tree | atmos aws cloudformation tree | Nested-stack/resource dependency graph |
rain rm | atmos aws cloudformation delete | Respects termination_protection — never silently disables it |
rain bootstrap | atmos aws cloudformation backend create | Provisions the S3 artifact bucket; provision.backend.enabled: true does it automatically instead |
rain build | Not supported — use atmos scaffold | Skeleton-template generation is a dev-time concern, not a component-type verb |
rain forecast | Not supported — use plan/diff + validate | Changeset review plus server-side validation cover the same question |
rain cc (Cloud Control passthrough) | Not supported | No equivalent planned |
This table covers the Rain verbs referenced above. If you relied on a Rain subcommand not listed
here, check the full CLI command reference —
atmos aws cloudformation also ships verbs with no Rain precedent at all, like output and
drift detect/drift describe, covered next.
2. Outputs, On Demand
Rain showed you a stack's Outputs at the end of rain deploy, but offered no way to get that view
back afterward without a manual describe-stacks call. Atmos renders the same summary both at
deploy time and on demand:
Other components — Terraform, Helm, another CloudFormation stack — can consume those Outputs directly:
See the !aws.cloudformation.output function for
the full cross-component story, including the Terraform↔CloudFormation interop bridge.
3. Drift Detection as a First-Class Verb
Rain only implied drift support through its stack-monitoring workflow. Atmos exposes it directly:
4. Provisioning the Artifact Bucket
Rain auto-created its artifact bucket silently on first use. Atmos never does this by default —
provision it explicitly, mirroring atmos terraform backend create:
Or opt into the same automatic provisioning Terraform components get:
S3 Packaging Is Size-Triggered, Not Asset-Aware
Be precise with yourself about this one before promising it to a team: apply/deploy
automatically uploads the template body itself to the component's kind: aws/s3 provision
target when it exceeds CloudFormation's 51,200-byte inline limit — the same problem aws cloudformation package/Rain's pkg solve. What it does not do yet is rewrite local-asset
references inside the template (Lambda source zips, nested-stack templates referenced by relative
path) the way !Rain::S3, aws cloudformation package, or Rain's pkg do. If your templates
depend on that, pre-upload those assets to S3 out-of-band — a hook, a build step, an existing
pipeline — and reference the resulting S3 location directly in the template until asset-aware
packaging ships.
Run: When You're Ready
StackSets
Multi-account/multi-region orchestration, once you need it:
Changesets, Managed Explicitly
apply/deploy create and execute a changeset implicitly. For a two-phase pipeline (create and
review in one job, execute in another), manage changesets directly:
Component Inheritance and Catalogs
Reuse configuration across environments the same way Terraform/Helm components do:
See Component Inheritance and the full
aws/cloudformation stack-configuration reference for
every available section.
Why It's Worth It
Real Benefits You'll Feel Immediately
- No orphaned tool —
aws/cloudformationis SDK-native and maintained as part of Atmos, not a third-party binary that can be archived out from under you - Same "watch it happen" experience — live per-resource event streaming during
apply/deploy - Outputs on demand — the deploy-time summary is also a standing command, not a one-time view
- Drift detection as a first-class verb, not an inferred side effect of monitoring
- One orchestration model — the same
--all/--affected/depends_onDAG ordering your Terraform and Helm components already use, now covering CloudFormation stacks too
What Atmos Won't Do
- Won't preprocess your templates — every
!Rain::directive must be resolved to plain CloudFormation or Atmos-native config before a template is accepted - Won't rewrite local asset references automatically — see S3 Packaging Is Size-Triggered above
- Won't give you a client-side module system — use AWS CDK for genuine multi-file template composition
Migration Checklist
- Install Atmos CLI (Installation Guide)
- Resolve any
!Rain::directives in templates being migrated - Create
atmos.yamlpointing at your existing template location - Create your first stack YAML (start with dev)
- Test with
atmos aws cloudformation plan <component> -s dev - Deploy with
atmos aws cloudformation deploy <component> -s dev - Confirm Outputs match what was already deployed (
atmos aws cloudformation output) - Create remaining stack files (staging, prod)
- (Optional) Provision the artifact bucket with
backend createorprovision.backend.enabled: true - (Optional) Explore inheritance and catalogs for shared configuration
- (Optional) Set up StackSets for multi-account/multi-region delivery
Next Steps
You just did Crawl — you're running Atmos!
Walk: Explore these next:
!aws.cloudformation.outputfor cross-component values- Drift Detection
- Artifact Bucket Provisioning
Run: When you're ready for advanced features: