skip to content

Apps, Stacks, and Environments

An App holds Stacks, and each Stack is one CloudFormation stack bound to an account and region. Cross-stack references look free in code but create real deployment ordering constraints.

on this pageshow

questions

5

In the AWS CDK, what are an App, a Stack and a Construct, and which of the three corresponds to something AWS actually creates when you deploy?

level: juniorimportance: must knowfreq 78%

answer

  1. root, unit, building block
  2. only one layer is deployable
  3. one instance equals one CloudFormation stack
  4. the App is never deployed
  5. synth writes one template per stack

basics

~20 s

Constructs are the composable building blocks of the tree; a Stack is the deployment unit that synthesizes into exactly one CloudFormation stack; the App is the root object holding every stack. Only stacks become real AWS deployments.

solid answer

~50 s

An AWS CDK program builds a tree of **constructs** — objects constructed with `(scope, id, props)`, where `scope` is the parent. Two nodes in that tree have special meaning. `App` is the root you create first and never deploy directly. `Stack` is the boundary where the description becomes deployable: one `Stack` instance synthesizes into exactly one CloudFormation template and deploys as exactly one CloudFormation stack, in one account and one region. Everything else is a construct that contributes resources to whichever stack it is scoped under. `cdk synth` walks the tree and writes a cloud assembly — one template per stack, in `cdk.out`. After that, AWS only ever sees CloudFormation; the App, the classes and the construct tree are gone. That is why stack boundaries are an architectural decision: the stack is the unit of deploy, of rollback and of resource limits.

code

typescript · 16 lines
typescript
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

class StorageStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'Data', { versioned: true });
  }
}

const app = new cdk.App();
const env = { account: '111122223333', region: 'eu-west-1' };
new StorageStack(app, 'Storage', { env });
new StorageStack(app, 'StorageBackup', { env: { ...env, region: 'eu-central-1' } });
app.synth();

go deeper

for a junior

Be able to name the three layers in order and say plainly that the Stack is the one that becomes a CloudFormation stack in AWS. Know that you run cdk deploy against a stack name, not against the app.

for a middle

Explain what synthesis produces — a cloud assembly with one template per stack — and that AWS never sees your constructs. Be ready to say where env attaches and why one app can span accounts and regions.

for a senior

Justify a stack split on lifecycle, blast radius and resource limits rather than on code tidiness, and explain why frequently deployed services should not share a stack with long-lived networking.

for a principal

Own the estate shape: how many stacks per service, which boundaries teams can deploy independently, and the tradeoff between few large stacks with simple wiring and many small stacks with more inter-stack coupling.

## Three layers, one tree An AWS CDK program is not a script that calls AWS APIs. It is a program that builds an in-memory tree of objects and then prints CloudFormation. The nodes of that tree are **constructs**: objects whose constructor signature is `(scope, id, props)`, where `scope` is the parent construct. A construct by itself creates nothing in AWS — it is a piece of a description. Two constructs carry special meaning. **`Stack`** is the boundary at which description turns into a deployable artifact. One `Stack` instance synthesizes into exactly one CloudFormation template and is deployed as exactly one CloudFormation stack, into one AWS account and one region. **`App`** is the root. You create it first, scope your stacks to it, and never deploy it — there is no AWS object called an App. ```typescript const app = new cdk.App(); const env = { account: '111122223333', region: 'eu-west-1' }; new NetworkStack(app, 'Network', { env }); new ServiceStack(app, 'Service', { env }); app.synth(); ``` ## What synthesis produces `cdk synth` walks the tree, lets each construct contribute CloudFormation resources to the stack it lives under, and writes a **cloud assembly**: a directory (`cdk.out` by default) containing one template file per stack plus metadata the CLI uses. From that moment on, AWS sees only CloudFormation. Your classes, your loops, your `if` statements and the construct tree itself do not exist at deploy time — they ran already, during synthesis, on your machine or your build runner. This is the reason the question has exactly one answer: the Stack is the thing AWS creates. `cdk deploy Network` calls CloudFormation with the template that stack produced. ## Why the Stack boundary is the one that matters Because a stack is the CloudFormation unit, it is simultaneously the unit of several things you care about: - **Deployment.** `cdk deploy Service` deploys one stack; `cdk deploy --all` deploys every stack in the app. `cdk list` shows you the names. - **Rollback and blast radius.** A failed update rolls that stack back. Resources in a different stack are untouched. - **Limits.** CloudFormation caps the number of resources in a single stack, so a large estate has to be split. - **Lifecycle.** A VPC that changes twice a year and a service that deploys twice a day do not belong in the same stack, because every deploy of the fast-moving thing puts the slow-moving thing into an update. So splitting an app into stacks is an architectural decision, not code tidying. The rule of thumb is: things that must deploy together and roll back together go in one stack; things with independent lifecycles go in separate stacks. Constructs are the opposite — they are free to reorganise. Wrapping several resources in your own construct class does not create a new deployment unit; those resources still belong to whichever stack the construct is scoped under. Composition is a code-structure tool; stacks are a deployment tool. ## Where the environment attaches `env` — the account and region a stack targets — is a property of a **Stack**, not of the App and not of an individual construct. Two stacks in one app can target two different accounts, or two regions, because each one becomes its own CloudFormation stack in its own environment. This is also why an App is not "an account": one app routinely spans several. Between App and Stack there is an optional layer, `Stage`: a group of stacks you can instantiate more than once, typically once per environment. `App` is itself a subclass of `Stage`, which is why stages nest naturally under it. ## What candidates get wrong The two common errors are collapsing the layers in either direction. One is treating the App as the deployable thing ("the app is my CloudFormation stack"), which makes stack boundaries look like folders and leads to one enormous stack. The other is imagining that each construct maps to a stack, which makes the resource limit and rollback behaviour incomprehensible. A third, subtler error is believing the CDK talks to AWS while your code runs. It does not — apart from synth-time context lookups, the CDK's entire job is to emit templates, and every deployment decision is CloudFormation's.

  • If you instantiate a construct but it is not scoped under any stack, what gets deployed?
    Nothing. Only constructs in a stack's scope contribute resources to a template. Most higher-level constructs resolve their stack internally via `Stack.of(this)` and fail at synthesis time if there is no stack ancestor, so the mistake usually shows up as a synth error rather than a silent omission.
  • What is a Stage, and where does it sit relative to App and Stack?
    A `Stage` is a composable group of stacks that can be instantiated more than once — typically once per environment — and it synthesizes into its own cloud assembly. It sits between the App and the stacks. `App` is itself a subclass of `Stage`, which is why a stage nests under an app naturally.
  • How does the CDK CLI decide which stacks to deploy?
    `cdk list` enumerates the stacks in the synthesized assembly by their construct path. You then name one (`cdk deploy Service`), pass a glob, or use `--all`. The CLI orders deployments by the dependencies between stacks, so a producer stack goes before a stack that references it.

The App is the build; the constructs are source files and functions; the Stack is the artifact that actually gets shipped and can be rolled back on its own.

saying these in an interview costs you the question

  • Says one App equals one CloudFormation stack
  • Thinks every construct becomes its own stack
  • Assumes all stacks in an app must deploy together
  • Says a Stack maps to an AWS account, not a CloudFormation stack
  • Believes the CDK calls AWS resource APIs directly at runtime

context

open as a page

You define an AWS CDK stack without passing the `env` property. Where does it deploy, what do `stack.account` and `stack.region` resolve to, and what stops working?

level: middleimportance: must knowfreq 68%

basics

~20 s

An environment-agnostic stack deploys wherever the CDK CLI's credentials point. Its account and region become unresolved tokens backed by CloudFormation pseudo-parameters, so synth-time lookups such as Vpc.fromLookup fail and the stack assumes only two availability zones.

open as a page

The AWS CDK writes a file called `cdk.context.json` in your project. What goes into it, and should it be committed to version control?

level: middleimportance: should knowfreq 45%

basics

~20 s

It caches the results of synthesis-time lookups against a real AWS account — VPC ids, availability zones, hosted zones, AMI ids, SSM values. Commit it, so synthesis is reproducible without AWS access; the cost is staleness until you reset an entry.

open as a page

In one AWS CDK app, stack B uses `bucket.bucketArn` from a bucket defined in stack A. What do the two synthesized CloudFormation templates contain, and why can removing that reference later break your deployment?

level: seniorimportance: should knowfreq 48%

basics

~20 s

The CDK adds an Output with an Export to stack A and an Fn::ImportValue to stack B. CloudFormation refuses to delete an export that is still imported, so dropping the reference fails unless the consumer is updated first or the export is pinned for one deploy.

open as a page

In the AWS CDK, when is a `NestedStack` the right choice instead of another top-level `Stack`, and what do you give up by nesting?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

A NestedStack becomes a single AWS::CloudFormation::Stack resource inside its parent, which buys room under the per-stack resource limit and parameter-based wiring instead of exports. You give up independent deployment: every change goes through a parent update.

open as a page