Local Development

Run validations, previews, and deployments from your workstation with confidence.


Overview

This guide covers the essential commands and workflows for developing CloudFormation templates on your local machine before pushing to CI/CD. Everything runs through helper scripts and the Rain CLI, so the same commands work locally and in your pipelines.

Daily development cycle

The typical workflow for making infrastructure changes:

# 1. Create a feature branch
git checkout -b feature/add-lambda

# 2. Modify templates under templates/
# Edit YAML templates

# 3. Update parameters for your target environment
# Edit parameters/test/<template-name>.yml

# 4. Validate templates locally
./scripts/validate-templates.sh

# 5. Run cfn-lint for quick feedback
cfn-lint templates/*.yml

# 6. Preview changes with Rain changeset
rain deploy templates/my-template.yml --config parameters/test/my-template.yml --changeset

# 7. Deploy changes (if preview looks good)
./scripts/deploy-templates.sh -e test

# 8. Test your infrastructure
# Run manual tests

# 9. Commit and push
git add .
git commit -m "feat: add Lambda function template"
git push origin feature/add-lambda

Essential script commands

The starter kit provides helper scripts for all operations. Run them from the repository root.

Validation

./scripts/validate-templates.sh

This script runs Checkov security scanning:

  • Checks dependencies (Python, pip)
  • Installs requirements if Checkov is not present
  • Runs Checkov with .checkov.yml configuration
  • Returns exit status based on scan results

Note: Run cfn-lint separately (cfn-lint templates/*.yml) for template syntax validation. The CI/CD workflow runs both tools.

Deployment

./scripts/deploy-templates.sh -e test
./scripts/deploy-templates.sh -e staging
./scripts/deploy-templates.sh -e production

Important: The -e flag selects which parameter folder to use (parameters/<environment>/).

The deploy script:

  • Iterates over all templates in templates/
  • Matches each template to its parameter file
  • Builds and executes Rain commands
  • Uses --yes for non-interactive execution

Provisioning new environments

./scripts/provision-repo.sh

Interactive helper that scaffolds:

  • GitHub Actions workflow for the environment
  • Parameter file with OIDC provider configuration

Run this when adding new environments to your deployment matrix.

Working with Rain directly

While scripts are convenient, Rain gives you more control for previews and targeted operations.

Preview changes with changesets

rain deploy templates/oidc-provider.yml --config parameters/test/oidc-provider.yml --changeset

Shows exactly what CloudFormation will create, modify, or destroy. Review carefully before approving.

Deploy a single template

rain deploy templates/my-template.yml --config parameters/test/my-template.yml --yes

The --yes flag suppresses confirmation prompts. Remove it for interactive deployments.

Deploy without parameters

rain deploy templates/simple-template.yml --yes

For templates that don't require parameter files.

Check stack status

rain ls
rain ls --all

Shows deployed stacks and their status.

View stack details

rain cat my-stack-name

Displays the current template for a deployed stack.

Remove a stack

rain rm my-stack-name --yes

Destroys all resources in the stack. CloudFormation handles deletion order.

View stack outputs

aws cloudformation describe-stacks \
  --stack-name my-stack-name \
  --query 'Stacks[0].Outputs'

Code quality checks

cfn-lint

Run cfn-lint for template validation:

# Validate all templates
cfn-lint templates/*.yml

# Validate a single template
cfn-lint templates/oidc-provider.yml

# Validate with specific configuration
cfn-lint --config .cfnlintrc templates/*.yml

Common issues cfn-lint catches:

  • Invalid property names or values
  • Missing required properties
  • Incorrect intrinsic function usage
  • AWS service limits violations
  • Region-specific resource availability

Example output:

E3001: Invalid Property Resources/Bucket/Properties/InvalidProperty
templates/storage.yml:10:7

For detailed configuration options, see the cfn-lint reference.

Checkov

Run Checkov for security scanning:

# Scan all templates
checkov -d templates/ --framework cloudformation

# Scan with configuration file
checkov -d templates/ -c .checkov.yml

# Scan a single template
checkov -f templates/storage.yml --framework cloudformation

Security issues Checkov detects:

  • Unencrypted S3 buckets
  • Publicly accessible resources
  • Missing logging and monitoring
  • Overly permissive IAM policies
  • Insecure network configurations
  • Compliance violations (PCI-DSS, HIPAA, SOC2)

Example output:

Check: CKV_AWS_18: "Ensure S3 bucket has server-side encryption enabled"
  FAILED for resource: AWS::S3::Bucket.DataBucket
  File: /templates/storage.yml:10-15

Suppressing false positives:

Resources:
  LogBucket:
    # checkov:skip=CKV_AWS_18:Encryption not required for access logs
    Type: AWS::S3::Bucket

For detailed configuration options, see the Checkov reference.

Switching environments

Because the deploy script requires an explicit -e flag, you can deploy to multiple accounts in a single session:

./scripts/deploy-templates.sh -e test
./scripts/deploy-templates.sh -e staging
./scripts/deploy-templates.sh -e production

Each command reads from the matching parameters/<environment>/ folder. Keep those files up to date when you introduce new templates.

Working with multiple AWS accounts

Using AWS CLI profiles

Configure profiles in ~/.aws/config:

[profile test-account]
sso_start_url = https://my-org.awsapps.com/start
sso_region = us-east-1
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = us-east-1

[profile production-account]
sso_start_url = https://my-org.awsapps.com/start
sso_region = us-east-1
sso_account_id = 210987654321
sso_role_name = AdministratorAccess
region = us-east-1

Switch profiles:

export AWS_PROFILE=test-account
./scripts/deploy-templates.sh -e test

export AWS_PROFILE=production-account
./scripts/deploy-templates.sh -e production

Rain relies on AWS CLI configuration. Set AWS_PROFILE or AWS_DEFAULT_REGION before running locally.

Using Granted

Granted simplifies multi-account access:

# Install via Homebrew
brew install common-fate/granted/granted

# Configure SSO
granted sso populate

# Assume role
assume test-account

# Deploy to test
./scripts/deploy-templates.sh -e test

# Switch to production
assume production-account
./scripts/deploy-templates.sh -e production

Learn more: Setting up the AWS CLI with AWS SSO

Testing infrastructure

Manual testing

After applying changes, manually verify resources:

# Example: Verify S3 bucket
aws s3 ls s3://my-bucket-name

# Example: Check Lambda function
aws lambda get-function --function-name my-function

# Example: Test IAM role
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/MyRole \
  --role-session-name test

# Example: Describe CloudFormation stack
aws cloudformation describe-stacks --stack-name my-stack

Testing template changes safely

When experimenting with large infrastructure changes:

  1. Create a scratch parameter folder (for example parameters/lab/)
  2. Run Rain directly against it
  3. Keep the folder out of version control by adding it to .gitignore

This lets you iterate freely without affecting shared environments.

Best practices

Before making changes

  • Pull latest from main
  • Review existing templates for patterns
  • Check parameter files for the target environment
  • Plan resource naming conventions

During development

  • Make small, incremental changes
  • Run cfn-lint templates/*.yml frequently
  • Run ./scripts/validate-templates.sh before commits
  • Use Rain changesets to preview changes
  • Tag resources appropriately

Before committing

  • Run full validation: ./scripts/validate-templates.sh
  • Review changeset output carefully
  • Check for sensitive data in templates (no hardcoded secrets)
  • Update parameter files if needed
  • Write clear commit messages

After deploying

  • Verify resources in AWS Console
  • Test functionality manually
  • Document any manual steps required
  • Update outputs if needed

Next steps