Infrastructure as Code

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Examples on this page target AWS CLI 2.37 (2.37.7 was current when checked), the AWS CDK CLI 2.1143 together with aws-cdk-lib 2.272, AWS SAM CLI 1.166 and the Terraform hashicorp/aws provider 6.67, all checked on 2026-10-01. The CDK CLI and the construct library are versioned separately, so their numbers no longer match. Re-verify version numbers with aws --version, cdk --version and sam --version before relying on exact behaviour. The Bookshelf scenario used here (a static front end, catalog-api, orders-api and the notifier worker) is described in the section introduction; the region us-east-1 and the account ID 111122223333 are placeholders.

Declarative vs. imperative management

AWS resources can be managed imperatively, by running a sequence of commands such as aws ec2 create-vpc …​, or declaratively, by submitting a template that describes the desired end state and letting a service work out which resources to create, update or delete. An imperative script only performs the action it was written for: rerunning it against an environment that has changed either fails or does the wrong thing. A declarative definition can be applied again and again, and the engine compares it with the current state, which is what makes previews (change sets), rollback and drift detection possible.

AWS offers its own declarative engine, AWS CloudFormation, and three authoring layers on top of or beside it:

Tool What you write What runs

CloudFormation

YAML or JSON templates

CloudFormation itself, as stacks

AWS CDK v2

Java, TypeScript, Python, .NET or Go code

cdk synth generates a CloudFormation template that is deployed as a stack

AWS SAM

A CloudFormation template with a short Transform for serverless resources

SAM expands it to plain CloudFormation, then deploys it as a stack

Terraform

HCL, with the hashicorp/aws provider

Terraform, which calls the AWS APIs directly and keeps its own state

Older guides and tutorials sometimes present the AWS CDK v2 as a developer preview, the AWS Copilot CLI as the easiest way to deploy containers, or AWS Application Composer as the visual editor. The CDK v2 has been generally available since December 2021, the Copilot CLI is out of support (see AWS Copilot CLI end of support), and Application Composer has been renamed AWS Infrastructure Composer.

CloudFormation

Template anatomy

A template is a YAML or JSON document. Only Resources is required; the other top-level sections are optional:

Section Purpose

Parameters

Inputs supplied at deploy time, with types, defaults, allowed values and constraints

Mappings

Static lookup tables, for example per-region values

Conditions

Boolean expressions that switch resources or properties on and off

Resources

The AWS resources to create (required)

Outputs

Values to show after deployment or to export to other stacks

Transform

Macros such as AWS::Serverless-2016-10-31 (SAM) that rewrite the template

Intrinsic functions connect the pieces: !Ref returns a parameter value or a resource identifier, !GetAtt returns an attribute such as an ARN, !Sub substitutes variables into a string, and !Select with !GetAZs picks Availability Zones. Pseudo parameters such as ${AWS::Region}, ${AWS::AccountId} and ${AWS::StackName} are resolved by CloudFormation, so a template never needs to hard-code an account ID or a region.

Stacks

A stack is the unit CloudFormation creates, updates and deletes: every resource in the template belongs to exactly one stack, and deleting the stack deletes the resources it created, in dependency order. If a create or update fails, CloudFormation rolls the stack back to its last known good state. Resources are identified inside the stack by their logical ID (for example CatalogApiRepository); the physical name AWS assigns is generated unless the template sets it. Fixing a physical name, as the Bookshelf template does for ECR repositories and log groups, makes names predictable but means a replacement cannot be created before the old resource is deleted.

Change sets

A change set is a preview: CloudFormation compares the submitted template and parameters with the running stack and lists every resource it would add, modify or remove, and whether a modification needs a replacement. Nothing changes until the change set is executed, so it is the equivalent of a plan in other tools and the safest way to update a shared stack. Newer drift-aware change sets can also compare the template with the real, drifted state of the resources and, when requested, revert the differences; check the current CloudFormation documentation for the exact mode names.

Drift detection

Drift is any difference between what the template says and what exists, usually caused by someone editing a resource in the console or with the CLI. Drift detection compares the actual configuration of the supported resources with the stack definition and reports each resource as IN_SYNC or MODIFIED, listing the differing properties. Drift detection reports; it does not repair. Fix drift either by editing the resource back, by updating the template to accept the new state, or by running a drift-aware change set.

Other CloudFormation features

  • Stack refactoring moves resources between stacks, or renames logical IDs, without recreating them.

  • The IaC generator scans resources that already exist in an account and produces a template for them, which is the usual way to bring hand-built infrastructure under management.

  • Pre-deployment validation checks a template or change set for errors that would otherwise only appear while a stack is being created, such as resource name conflicts.

  • StackSets deploy one template to many accounts and Regions, typically across an organization, see Accounts and Organizations.

  • Rollback triggers tie a stack update to CloudWatch alarms so a failing deployment rolls back automatically.

In the AWS Management Console

  1. Open the CloudFormation console and use the Region selector (top right) to choose the Region the stack will live in.

  2. Choose Stacks, then Create stack and With new resources (standard).

  3. Under Prerequisite - Prepare template choose Choose an existing template, then upload the template file and choose Next.

  4. Enter a stack name and fill in the parameters, then choose Next twice and review the summary. Acknowledge any IAM capability prompt, which appears only when the template creates IAM resources.

  5. Choose Submit and watch the Events tab until the stack reaches CREATE_COMPLETE.

  6. To update, open the stack, choose Update stack, then Make a direct update or Create change set. Choosing a change set shows the Changes tab, where Execute change set applies it.

  7. To check drift, choose Stack actions, then Detect drift, and afterwards View drift results.

  8. To pick a different tool, Infrastructure Composer in the same console lets you assemble a template visually, and IaC generator in the left navigation scans existing resources into a template.

With the AWS CLI

aws cloudformation deploy is the one-command route: it creates a change set, waits for it and executes it, creating the stack when it does not exist yet.

STACK="bookshelf-baseline"

aws cloudformation validate-template \
  --template-body file://bookshelf-baseline.yaml

aws cloudformation deploy \
  --stack-name "$STACK" \
  --template-file bookshelf-baseline.yaml \
  --parameter-overrides EnvironmentName=bookshelf \
  --tags project=bookshelf

See the aws cloudformation deploy and validate-template command references.

To review before applying, create, inspect and execute a change set explicitly. Use --change-set-type CREATE for a stack that does not exist yet and UPDATE afterwards:

STACK="bookshelf-baseline"
CHANGE_SET="add-private-subnets"

aws cloudformation create-change-set \
  --stack-name "$STACK" \
  --change-set-name "$CHANGE_SET" \
  --change-set-type UPDATE \
  --template-body file://bookshelf-baseline.yaml

aws cloudformation wait change-set-create-complete \
  --stack-name "$STACK" --change-set-name "$CHANGE_SET"

aws cloudformation describe-change-set \
  --stack-name "$STACK" --change-set-name "$CHANGE_SET" \
  --query 'Changes[].ResourceChange.[Action,LogicalResourceId,Replacement]' \
  --output table

aws cloudformation execute-change-set \
  --stack-name "$STACK" --change-set-name "$CHANGE_SET"

Follow a deployment and detect drift:

STACK="bookshelf-baseline"

aws cloudformation describe-stack-events \
  --stack-name "$STACK" \
  --query 'StackEvents[0:10].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]' \
  --output table

DRIFT_ID=$(aws cloudformation detect-stack-drift \
  --stack-name "$STACK" --query StackDriftDetectionId --output text)

aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id "$DRIFT_ID"

aws cloudformation describe-stack-resource-drifts \
  --stack-name "$STACK" \
  --stack-resource-drift-status-filters MODIFIED DELETED

AWS CDK v2

The AWS Cloud Development Kit (CDK) lets you define infrastructure in a general-purpose language and generates CloudFormation from it, so loops, functions, types and unit tests are available. A CDK app contains one or more stacks, and each stack contains constructs, in three levels:

  • L1 constructs (CfnVpc, CfnRepository) map one to one to CloudFormation resources.

  • L2 constructs (Vpc, Repository, Cluster) wrap a resource with sensible defaults and helper methods, for example repository.grantPull(role).

  • L3 constructs (patterns) combine several resources, such as an ECS service behind a load balancer.

The CLI is installed separately from the library (npm install -g aws-cdk), and since the CLI and aws-cdk-lib are versioned independently, a newer CLI can deploy apps built with an older library. The commands are:

Command Purpose

cdk bootstrap

One-time, per account and Region: creates the CDKToolkit stack with the staging bucket, repository and deployment roles the CLI uses

cdk synth

Generates the CloudFormation template(s) into cdk.out

cdk diff

Compares the synthesized template with the deployed stack, like a change set

cdk deploy

Synthesizes and deploys through CloudFormation

cdk destroy

Deletes the stack

The following Java app defines the same Bookshelf baseline as the CloudFormation template later on this page (network, registries and cluster):

package com.example.bookshelf;

import java.util.List;
import software.amazon.awscdk.App;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.ec2.SubnetConfiguration;
import software.amazon.awscdk.services.ec2.SubnetType;
import software.amazon.awscdk.services.ec2.Vpc;
import software.amazon.awscdk.services.ecr.Repository;
import software.amazon.awscdk.services.ecr.TagMutability;
import software.amazon.awscdk.services.ecs.Cluster;
import software.constructs.Construct;

public class BaselineStack extends Stack {

    public BaselineStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);

        Vpc vpc = Vpc.Builder.create(this, "Vpc")
                .maxAzs(2)
                .natGateways(0)
                .subnetConfiguration(List.of(
                        SubnetConfiguration.builder().name("public")
                                .subnetType(SubnetType.PUBLIC).cidrMask(24).build(),
                        SubnetConfiguration.builder().name("private")
                                .subnetType(SubnetType.PRIVATE_ISOLATED).cidrMask(24).build()))
                .build();

        for (String name : List.of("catalog-api", "orders-api", "notifier")) {
            Repository.Builder.create(this, "Repo-" + name)
                    .repositoryName("bookshelf/" + name)
                    .imageTagMutability(TagMutability.IMMUTABLE)
                    .removalPolicy(RemovalPolicy.DESTROY)
                    .emptyOnDelete(true)
                    .build();
        }

        Cluster.Builder.create(this, "Cluster")
                .clusterName("bookshelf")
                .vpc(vpc)
                .build();
    }

    public static void main(final String[] args) {
        App app = new App();
        new BaselineStack(app, "BookshelfBaseline", StackProps.builder().build());
        app.synth();
    }
}

See the AWS CDK v2 Developer Guide and the CDK Java API reference. Leaving env unset makes the stack environment-agnostic, so the account and Region come from the CLI profile at deploy time.

In the AWS Management Console

The CDK has no console workflow of its own, but everything it deploys is a normal CloudFormation stack. Open the CloudFormation console, find the stack (BookshelfBaseline, plus the CDKToolkit stack created by bootstrapping), and use the Template tab to see the generated template and the Resources tab to see what was created.

With the AWS CLI and the CDK CLI

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
AWS_REGION="us-east-1"

npm install -g aws-cdk
cdk init app --language java        # once, in an empty directory
cdk bootstrap "aws://$ACCOUNT_ID/$AWS_REGION"
cdk synth
cdk diff
cdk deploy

AWS SAM

The AWS Serverless Application Model (SAM) is a CloudFormation extension for serverless applications. A template that declares Transform: AWS::Serverless-2016-10-31 can use short resource types such as AWS::Serverless::Function, AWS::Serverless::Api, AWS::Serverless::HttpApi and AWS::Serverless::SimpleTable, which SAM expands into the Lambda functions, IAM roles, event source mappings and API resources they imply. The SAM CLI adds local development commands on top. The Bookshelf notifier (an SQS-triggered function) looks like this:

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Bookshelf notifier worker (SQS-triggered Lambda)

Resources:
  NotifierQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: bookshelf-notifier
      VisibilityTimeout: 60

  Notifier:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: notifier
      CodeUri: notifier/
      Handler: com.example.bookshelf.NotifierHandler::handleRequest
      Runtime: java25
      Architectures: [arm64]
      MemorySize: 512
      Timeout: 30
      SnapStart:
        ApplyOn: PublishedVersions
      AutoPublishAlias: live
      Events:
        Orders:
          Type: SQS
          Properties:
            Queue: !GetAtt NotifierQueue.Arn
            BatchSize: 10

See the AWS::Serverless::Function reference. The function itself is covered in AWS Lambda and the event wiring in Lambda: Event-Driven and Serverless Apps.

In the AWS Management Console

  1. Open the Lambda console, choose Create function, and use Use a blueprint or Author from scratch; the console can also export a function to Infrastructure Composer, which generates a SAM template.

  2. Open Infrastructure Composer in the console to drag resources onto a canvas and download or sync the generated SAM or CloudFormation template.

  3. A deployed SAM application is an ordinary CloudFormation stack: find it under CloudFormation Stacks, and use View in Application Composer (or Infrastructure Composer) on its Template tab to see it as a diagram.

With the SAM CLI

sam init                      # pick a quick-start template and runtime
sam build                     # compile and stage artifacts in .aws-sam/build
sam local invoke Notifier --event events/order.json
sam deploy --guided           # first deployment, saves samconfig.toml
sam sync --stack-name bookshelf-notifier --watch   # fast dev loop, not for production
sam delete --stack-name bookshelf-notifier

See the SAM CLI command reference. sam local invoke and sam local start-api need Docker running.

Terraform

Terraform is a third-party tool from HashiCorp that calls the AWS APIs through the hashicorp/aws provider. Unlike CloudFormation, there is no AWS-managed state: Terraform stores its own state file, usually in an S3 bucket with locking. The example below provisions two of the Bookshelf baseline resources, an ECR repository and a log group, with provider version 6:

terraform {
  required_version = ">= 1.10"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "us-east-1" # placeholder Region
}

resource "aws_ecr_repository" "catalog_api" {
  name                 = "bookshelf/catalog-api"
  image_tag_mutability = "IMMUTABLE"
  force_delete         = true # allow destroy even when images exist

  encryption_configuration {
    encryption_type = "AES256"
  }
}

resource "aws_cloudwatch_log_group" "catalog_api" {
  name              = "/bookshelf/catalog-api"
  retention_in_days = 30
}

See the hashicorp/aws provider documentation and the version 6 upgrade guide. In-depth Terraform coverage, including remote state, modules and pipelines, is tracked separately in GitHub issue #171 of this documentation repository and is not repeated here. The usual cycle is terraform init, terraform plan, terraform apply and terraform destroy; credentials come from the same CLI profile or environment variables as every other AWS tool, see Getting Started with AWS.

AWS Copilot CLI end of support

The AWS Copilot CLI, a command-line tool that generated CloudFormation to deploy containerized applications on ECS and Fargate (copilot init, copilot deploy), reached end of support on 2026-06-12. It no longer receives fixes or new features. The CloudFormation stacks it created are ordinary stacks and continue to run, but they should be managed another way going forward. The current options are:

  • ECS Express Mode for a fast path from a container image to a load-balanced Fargate service, see ECS and Fargate.

  • A CloudFormation template or CDK app for full control, as in the Bookshelf baseline below.

  • The AWS CDK ECS patterns for the same load-balanced-service shape Copilot produced.

Tutorials that still start with copilot init should be treated as outdated. See the end-of-support announcement and its migration guidance.

Bookshelf baseline: a runnable CloudFormation template

The template below creates the shared Bookshelf baseline that later pages build on: a VPC with two public and two isolated private subnets across two Availability Zones, an internet gateway and public route table, three ECR repositories (bookshelf/catalog-api, bookshelf/orders-api, bookshelf/notifier), an ECS cluster named bookshelf with the Fargate and Fargate Spot capacity providers, and one CloudWatch log group per service. It creates no IAM resources and no NAT gateway, so deploying it needs no capability acknowledgement and no hourly network charge, though the VPC itself, the repositories and the log groups cost nothing until used. Save it as bookshelf-baseline.yaml.

AWSTemplateFormatVersion: "2010-09-09"
Description: Bookshelf baseline - VPC, ECR repositories, ECS cluster and log groups

Parameters:
  EnvironmentName:
    Type: String
    Default: bookshelf
    AllowedPattern: "[a-z][a-z0-9-]{1,30}"
    Description: Prefix used in resource names and tags.
  VpcCidr:
    Type: String
    Default: 10.0.0.0/16
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(1[6-9]|2[0-4])"
    Description: CIDR block of the VPC (a /16 to /24 range).
  LogRetentionDays:
    Type: Number
    Default: 30
    AllowedValues: [7, 14, 30, 60, 90, 180, 365]
    Description: Retention for the service log groups.

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-vpc"

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-igw"

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway

  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 4, 8]]
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-public-a"

  PublicSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !Select [1, !Cidr [!Ref VpcCidr, 4, 8]]
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-public-b"

  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Select [2, !Cidr [!Ref VpcCidr, 4, 8]]
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-private-a"

  PrivateSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !Select [3, !Cidr [!Ref VpcCidr, 4, 8]]
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-private-b"

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-public-rt"

  PublicDefaultRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetB
      RouteTableId: !Ref PublicRouteTable

  CatalogApiRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: bookshelf/catalog-api
      ImageTagMutability: IMMUTABLE
      EmptyOnDelete: true
      EncryptionConfiguration:
        EncryptionType: AES256

  OrdersApiRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: bookshelf/orders-api
      ImageTagMutability: IMMUTABLE
      EmptyOnDelete: true
      EncryptionConfiguration:
        EncryptionType: AES256

  NotifierRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: bookshelf/notifier
      ImageTagMutability: IMMUTABLE
      EmptyOnDelete: true
      EncryptionConfiguration:
        EncryptionType: AES256

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref EnvironmentName

  ClusterCapacityProviders:
    Type: AWS::ECS::ClusterCapacityProviderAssociations
    Properties:
      Cluster: !Ref Cluster
      CapacityProviders: [FARGATE, FARGATE_SPOT]
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Weight: 1

  CatalogApiLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/${EnvironmentName}/catalog-api"
      RetentionInDays: !Ref LogRetentionDays

  OrdersApiLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/${EnvironmentName}/orders-api"
      RetentionInDays: !Ref LogRetentionDays

  NotifierLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/${EnvironmentName}/notifier"
      RetentionInDays: !Ref LogRetentionDays

Outputs:
  VpcId:
    Value: !Ref Vpc
    Export:
      Name: !Sub "${AWS::StackName}-VpcId"
  PublicSubnetIds:
    Value: !Join [",", [!Ref PublicSubnetA, !Ref PublicSubnetB]]
    Export:
      Name: !Sub "${AWS::StackName}-PublicSubnetIds"
  PrivateSubnetIds:
    Value: !Join [",", [!Ref PrivateSubnetA, !Ref PrivateSubnetB]]
    Export:
      Name: !Sub "${AWS::StackName}-PrivateSubnetIds"
  ClusterName:
    Value: !Ref Cluster
    Export:
      Name: !Sub "${AWS::StackName}-ClusterName"
  CatalogApiRepositoryUri:
    Value: !GetAtt CatalogApiRepository.RepositoryUri

See the AWS::EC2::VPC, AWS::ECR::Repository, AWS::ECS::Cluster and AWS::Logs::LogGroup template references. Note how the template uses ${AWS::StackName} in export names so that two stacks created from it never collide, and Fn::Cidr to carve four /24 subnets out of the VPC range instead of hard-coding them. The subnets are explained in VPC Networking, the repositories in Container Registry, and the cluster in ECS and Fargate.

In the AWS Management Console

  1. Select the Region in the console Region selector (the template is Region-neutral; us-east-1 is a placeholder) and open CloudFormation.

  2. Choose Create stack, With new resources (standard), Upload a template file, and select bookshelf-baseline.yaml.

  3. Name the stack bookshelf-baseline, keep the default parameters, and choose Next until Submit.

  4. Wait for CREATE_COMPLETE, then open the Outputs tab for the VPC ID, subnet IDs, cluster name and repository URI.

  5. Verify in the VPC, ECR, ECS and CloudWatch consoles that the resources exist.

With the AWS CLI

AWS_REGION="us-east-1"
STACK="bookshelf-baseline"

aws cloudformation validate-template \
  --region "$AWS_REGION" \
  --template-body file://bookshelf-baseline.yaml

aws cloudformation deploy \
  --region "$AWS_REGION" \
  --stack-name "$STACK" \
  --template-file bookshelf-baseline.yaml \
  --parameter-overrides EnvironmentName=bookshelf LogRetentionDays=30

aws cloudformation describe-stacks \
  --region "$AWS_REGION" \
  --stack-name "$STACK" \
  --query 'Stacks[0].Outputs[].[OutputKey,OutputValue]' \
  --output table

See deploy and describe-stacks. If a repository or log group with the same fixed name already exists in the Region (for example from a manual experiment), the stack creation fails and rolls back; delete the conflicting resource or change the name and retry.

Pricing and limits

CloudFormation itself has no charge for managing AWS resources with the built-in resource types; third-party and registry extensions are billed per handler operation, and the resources a template creates are billed at their own rates. Consult the CloudFormation pricing page for the current model. The CDK and SAM CLIs are free; the CDK bootstrap stack creates a small staging bucket and repository whose storage is billed like any other. Terraform’s cost depends on the edition and backend chosen. Account limits that affect larger templates include the maximum number of resources per stack, the template body size and the number of stacks per Region; see CloudFormation quotas for current values, and split large systems into several stacks or nested stacks.

Clean up

Deleting the stack removes everything created by the Bookshelf baseline. The ECR repositories use EmptyOnDelete, so they are deleted even when images were pushed to them, and the log groups are deleted with their data:

AWS_REGION="us-east-1"
STACK="bookshelf-baseline"

aws cloudformation delete-stack --region "$AWS_REGION" --stack-name "$STACK"
aws cloudformation wait stack-delete-complete --region "$AWS_REGION" --stack-name "$STACK"

See delete-stack. For the other tools shown here, use cdk destroy for the CDK app, sam delete --stack-name bookshelf-notifier for the SAM application (this also removes the artifacts bucket objects it uploaded), and terraform destroy for the Terraform example. If other stacks import this stack’s exports, delete those first, otherwise the deletion is refused. To remove the CDK bootstrap resources as well, delete the CDKToolkit stack once it is empty; the bootstrap staging bucket retains objects and must be emptied first. In the console, select the stack in CloudFormation and choose Delete.