
Stack
In AWS CDK (Cloud Development Kit), a Stack is a unit of deployment that contains one or more AWS resources defined as part of your infrastructure. A stack corresponds to an AWS CloudFormation stack and is the primary way of organizing and deploying your AWS resources using AWS CDK.

Example
An AWS CDK application can have one or more Stacks, for example:
from aws_cdk import App app = App() MyFirstStack(app, "stack1") MySecondStack(app, "stack2") app.synth()
List add stacks. you can use the command: cdk ls
stack1 stack2
When we want to run the synth
command to create an AWS CloudFormation for a Stack application, pass in the name of the Stack:
cdk synth stack1
Key Concepts of a Stack in AWS CDK
- Logical Grouping of Resources:
- A stack is a group-related resource logically.
- For example, all resources for a web application backend (like Lambda functions, DynamoDB tables, and API Gateway endpoints) can be grouped into a single stack.
- Unit of Deployment:
- When you deploy a CDK application, each stack in your application is first synthesized into a CloudFormation template. Then, we will deploy it as an independent CloudFormation stack. It ensures a clear separation of resources for efficient management.
- Scope and Isolation:
- Each stack operates within its scope and remains isolated from other stacks unless you explicitly link them. This helps in managing resources and avoiding unintended changes.
- Supports Cross-Stack References:
- You can reference resources in one stack from another stack by using cross-stack outputs and imports.
- Defined in Code:
- A stack is a class that extends the
Stack
class in AWS CDK. Within the class, you define resources as constructs.
- A stack is a class that extends the
When to Use Multiple Stacks
- Separation of Concerns: Divide resources into logical groups (e.g., frontend and backend).
- Resource Limits: CloudFormation has a limit of 500 resources per stack. Use multiple stacks if you exceed this limit.
- Ease of Deployment: Update specific parts of your infrastructure without affecting others.
Benefits of Using Stacks
- Reusable: You can parameterize stacks and reuse them across multiple environments, such as dev, test, and prod.
- Modular: Simplifies management by dividing infrastructure into smaller, manageable units.
- Efficient Deployment: The system updates only the modified stacks during deployment, ensuring faster and more efficient changes.
Example
Create multiple environments(Dev, Prod)
With AWS CDK and Stacks, you can easily structure resources for different environments. For example, consider an application requiring infrastructure deployment for both development and production environments. The infrastructure consists of three Stacks:
- Application Stack
- Monitoring Stack
- CI/CD Stack
In the development environment, you might omit the Monitoring Stack to save costs. CDK makes it straightforward to create infrastructure for both environments with these requirements. You can define three functions to handle the three Stacks, as shown below:
from aws_cdk import ( App, Stack, Environment ) from constructs import Construct class MonitoringStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) class AppStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) class CICDStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) def get_env() -> Environment: return None app = App() app.synth()
Next, we write another function to combine the 3 Stacks above and use the if
statement to compare if it is prod then we will create the Monitoring Stack:
@dataclass class ServiceProps: prod: bool = False class Service(Stage): def __init__(self, scope: Construct, construct_id: str, props: ServiceProps = None, **kwargs): super().__init__(scope, construct_id, env=get_env(), **kwargs ) # Create monitoring stack only in production if props and props.prod: MonitoringStack(self, "monitoring") # Create app and CICD stacks always AppStack(self, "app") CICDStack(self, "cicd")
Update main function
from aws_cdk import ( App, Stack, Stage, Environment ) from constructs import Construct from dataclasses import dataclass @dataclass class ServiceProps: prod: bool = False class MonitoringStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) class AppStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) class CICDStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) class Service(Stage): def __init__(self, scope: Construct, construct_id: str, props: ServiceProps = None, **kwargs) -> None: super().__init__(scope, construct_id, env=get_env(), **kwargs ) if props and props.prod: MonitoringStack(self, "monitoring") AppStack(self, "app") CICDStack(self, "cicd") def get_env() -> Environment: return None # Initialize the CDK app app = App() # Create dev and prod services Service(app, "dev") # No props means prod=False by default Service(app, "prod", ServiceProps(prod=True)) app.synth()
Run cdk ls
to view all stacks
dev/app dev/cicd prod/app prod/cicd prod/monitoring
Use Construct between Stacks
One common organizational challenge when using Infrastructure as Code (IaC) tools is defining shared resources that can be used across different infrastructures.
For example, consider the following scenario: your project is built on AWS with a microservices architecture, and you decide to use Terraform for infrastructure management. You create a source code repository for each service and write the necessary infrastructure code. Typically, you would create an AWS VPC (Virtual Private Cloud) first, and then other resources within that VPC.
As the project grows, you add a new service. You create another source code repository and write the infrastructure code for the new service, but you realize that the new service needs to reside in the same VPC as the previous service. At this point, the problem arises: how can you reuse the existing VPC?
Solution
Firstly, If you don’t set up CDK AWS. You can go to the link
Create a stack folder with 3 files as follows:
└── stack ├── globalStack.py ├── userService.py └── postService.py
Code in globalStack.py
from aws_cdk import ( Stack, aws_ec2 as ec2 ) from constructs import Construct from dataclasses import dataclass @dataclass class GlobalStackResource: vpc: ec2.IVpc class GlobalStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # Create Vpc vpc = ec2.Vpc(self, "MyVpc", max_azs=2) # Store resources that need to be accessed by other stacks self.resource = GlobalStackResource(vpc=vpc) def get_resource(self): return self.resource
Code in userService.py
from aws_cdk import ( Stack, aws_ec2 as ec2 ) from constructs import Construct from dataclasses import dataclass @dataclass class UserServiceStackProps: vpc: ec2.IVpc class UserServiceStack(Stack): def __init__(self, scope: Construct, construct_id: str, props: UserServiceStackProps, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # EC2 Instance ec2.Instance( self, "Server", instance_type=ec2.InstanceType("t3.micro"), machine_image=ec2.AmazonLinuxImage( generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 ), vpc=props.vpc )
Code in postService.py
from aws_cdk import ( Stack, aws_ec2 as ec2 ) from constructs import Construct from dataclasses import dataclass @dataclass class PostServiceStackProps: vpc: ec2.IVpc class PostServiceStack(Stack): def __init__(self, scope: Construct, construct_id: str, props: PostServiceStackProps, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # Ec2 Instance ec2.Instance( self, "Server", instance_type=ec2.InstanceType("t3.micro"), machine_image=ec2.AmazonLinuxImage( generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 ), vpc=props.vpc )
Code in App.py
# #!/usr/bin/env python3 from aws_cdk import ( App, Environment ) from stacks.globalStack import GlobalStack from stacks.userService import UserServiceStack, UserServiceStackProps from stacks.postService import PostServiceStack, PostServiceStackProps def get_env() -> Environment: return None # Initialize the CDK app app = App() # Create Global Stack and get its resources global_stack = GlobalStack( app, "GlobalStack", env=get_env() ) global_resources = global_stack.get_resource() # Create User Service Stack UserServiceStack( app, "UserServiceStack", props=UserServiceStackProps( vpc=global_resources.vpc ), env=get_env() ) # Create Post Service Stack PostServiceStack( app, "PostServiceStack", props=PostServiceStackProps( vpc=global_resources.vpc ), env=get_env() ) app.synth()
Run cdk ls
GlobalStack UserServiceStack PostServiceStack
Finally, run the deploy
command to create the resources. Note that since both
and UserServiceStack
depend on the resources in PostServiceStack
, you need to create GlobalStack
first.GlobalStack
You can run
to let CDK automatically create the infrastructure in the correct order. However, in real-world scenarios, it’s recommended to deploy each Stack individually for greater control and to ensure a reliable deployment process.cdk deploy --all
Summary
AWS CDK Stacks logically group and deploy AWS resources, corresponding to CloudFormation stacks. They enable modular, reusable, and efficient infrastructure management, supporting resource isolation and cross-stack references.
Use multiple stacks to separate concerns, manage resource limits, and streamline deployments across environments like dev and prod.
References
RELATED POSTS
View all