State Store
cdkd records what it deployed in an S3 bucket, as one JSON document per stack and region. That document is the stack's state record. This page says which bucket cdkd uses, what the bucket contains, and how to read a record. The other subjects each have their own page, listed under In this section.
# which bucket cdkd resolved, and how
cdkd state info
# every stack, with resource count and lock status
cdkd state list --long
# one record: outputs, resources, properties
cdkd state show MyStack
# the raw record and lock as JSON
cdkd state show MyStack --json
Manage Stacks Without the App is the task-level introduction, and
cdkd state documents every subcommand and flag.
Where state lives
State lives in one S3 bucket per AWS account. cdkd bootstrap creates the
bucket, and every CDK app deployed to the account shares it.
A cdkd deploy that finds no state bucket fails and tells you to run
cdkd bootstrap. A deploy never creates the bucket for you.
Which bucket cdkd uses
cdkd takes the bucket name from the first of these that is set:
| Source | Example |
|---|---|
--state-bucket |
cdkd deploy --state-bucket my-team-state |
CDKD_STATE_BUCKET |
export CDKD_STATE_BUCKET=my-team-state |
cdk.json |
"context": { "cdkd": { "stateBucket": "my-team-state" } } |
| Default | cdkd-state-{accountId}, from the caller's STS account ID |
The default name contains no region. S3 bucket names are global, so teammates whose profiles use different regions still resolve the same bucket and see the same state.
cdkd state info prints the bucket cdkd resolved and which of the four
sources supplied the name.
Every key in the bucket that belongs to a stack starts with a prefix, which is
cdkd by default. Change it with --state-prefix.
What is in the bucket
state.json is the object you will read. The others support one command or
one feature each:
| Object | What it holds | When it goes away |
|---|---|---|
state.json |
The stack's resources and outputs | The stack is destroyed or orphaned; its earlier versions go too only with cdkd state destroy |
lock.json |
Who is working on the stack | The command releases the lock |
rollback-journal.json |
What a failed deploy completed | The next successful deploy, a clean rollback, or a destroy |
create-tokens.json |
Tokens for retrying an interrupted create | Deleted with state.json |
stack-instance.json |
A first deploy's stack instance id, written before its first create | Deleted once state.json holds the id, or with state.json |
deployments/ |
Deployment events | Kept after a destroy unless you pass --purge-events |
_index/{region}/exports.json |
Exported values of every stack in the region | Rewritten by each deploy |
_refactor/pending.json |
The moves a cdkd refactor started between stacks and has not finished |
Deleted when the last listed move finishes |
cdkd-bootstrap/{region}.json |
The region's asset bucket and ECR repository names | cdkd bootstrap --destroy |
custom-resource-responses/{requestId}.json |
One custom resource handler's response | Deleted after the call, or by a lifecycle rule within days |
cdkd-migrate-tmp/ |
A template too large to send inline to CloudFormation | Deleted by the command that uploaded it |
Three of these need a word more:
rollback-journal.jsonis the rollback journal. A failed or interrupted deploy writes it, andcdkd rollbackreads it to undo what that deploy did._index/{region}/exports.jsonis the exports index. It lists the resolved value of every export in the region, andFn::ImportValueis answered from it.create-tokens.jsonlets a re-run find an EFS, FSx or CloudFront origin access identity that an interrupted create already made. It holds no secret.
Two more objects exist to keep one stack name to one deployment. They are described with the checks that use them:
_cdkd-registry/{region}/{stackName}.json, at the top of the bucket, is the stack's marker in the stack registry.retained.json, besidestate.json, lists resources the stack kept in AWS when it let go of them. The name check reads it.
A custom resource response that is left in the bucket, because the deploy that
would have deleted it died, is collected by cdkd gc.
One record per stack and region
A record's key contains the stack name and the region, so a stack name deployed to two regions has two independent records:
cdkd/DatabaseStack/us-east-1/state.json
cdkd/DatabaseStack/us-west-2/state.json
The same stack name under two state prefixes, or in two buckets, is a different matter. In one account and region it is unsupported, and cdkd refuses it where it can see it. See One stack name per account and region.
A nested stack has its own record. Its key joins the parent stack name and the
child's logical ID with ~:
cdkd/{parentStack}~{childLogicalId}/{region}/state.json
The bucket can be in any region
The state bucket does not have to be in your profile's region or in a region
you deploy stacks into. cdkd looks up the bucket's region with
GetBucketLocation and addresses the bucket there. Resources are still
created in the region the CDK app declares.
Move off the old bucket name with cdkd state migrate
Before v0.8 the default bucket name ended in the region:
cdkd-state-{accountId}-{region}. If only that bucket exists, cdkd uses it and
prints a deprecation warning. Support for the old name will be removed in a
future release, so copy the state to the current default:
# preview
cdkd state migrate --region us-east-1 --dry-run
# copy, keep the source
cdkd state migrate --region us-east-1
# copy, then delete the source
cdkd state migrate --region us-east-1 --remove-legacy
Run the command once for each region that has an old bucket. Every run copies into the same destination bucket, and running it again for the same region is safe.
If the destination cdkd-state-{accountId} does not exist, the command
creates it with the same configuration cdkd bootstrap uses.
The command refuses to start while any lock.json exists in the source
bucket, because a lock means a command may still be writing there. Clear a
lock that a dead process left behind with cdkd force-unlock first.
Warning
--remove-legacydeletes every object version in the source bucket, so the history of every record in it is gone. Check the destination before you pass it. Without the flag the source bucket is kept.
Edge cases
A record's region field disagrees with its key. The key decides which
region a record belongs to. cdkd writes the same region into the record's
region field, so the two agree on every record cdkd wrote. They can disagree
only after a hand edit. cdkd then uses the key's region and warns.
cdkd destroy, cdkd state destroy and cdkd rollback refuse such a record
while it still lists resources. A delete sent to the wrong region would find
nothing, report every resource as already gone, and remove the record while
the resources stay live. To proceed, destroy against the region the resources
are in, or correct the region field so that it matches the key.
cdkd state orphan still drops the record and leaves the resources standing.
A record with no region in its key. Early cdkd versions wrote
cdkd/{stackName}/state.json. cdkd still reads that key. The next write saves
the record under the key that contains the region and deletes the old object.
What a state record contains
A state record is one JSON document that lists the stack's resources and its
outputs. cdkd state show MyStack prints it in readable form, and
cdkd state show MyStack --json prints the document as stored:
{
"version": 11,
"stackName": "MyAppStack",
"region": "us-east-1",
"resources": {
"MyBucket": {
"physicalId": "myappstack-mybucket-abc123xyz",
"resourceType": "AWS::S3::Bucket",
"properties": {
"BucketName": "myappstack-mybucket-abc123xyz",
"VersioningConfiguration": { "Status": "Enabled" }
},
"attributes": {
"Arn": "arn:aws:s3:::myappstack-mybucket-abc123xyz"
},
"dependencies": [],
"provisionedBy": "sdk"
},
"MyFunction": {
"physicalId":
"arn:aws:lambda:us-east-1:123456789012:function:MyAppStack-MyFunction",
"resourceType": "AWS::Lambda::Function",
"properties": {
"FunctionName": "MyAppStack-MyFunction",
"Runtime": "nodejs20.x",
"Handler": "index.handler",
"Role": "arn:aws:iam::123456789012:role/MyAppStack-MyFunctionRole"
},
"attributes": {
"Arn":
"arn:aws:lambda:us-east-1:123456789012:function:MyAppStack-MyFunction"
},
"dependencies": ["MyFunctionRole", "MyBucket"],
"provisionedBy": "sdk"
}
},
"outputs": {
"BucketArn": "arn:aws:s3:::myappstack-mybucket-abc123xyz"
},
"exportNames": ["BucketArn"],
"lastModified": 1710835200000
}
The top level describes the stack. Each entry under resources describes one
resource, keyed by its logical ID from the template.
Stack-level fields
| Field | Meaning |
|---|---|
version |
The schema version; currently 13 |
stackName, region |
The stack, and the region its resources are in |
resources |
One entry per resource, keyed by logical ID |
outputs |
Each stack output's name and resolved value |
exportNames |
The outputs keys that are export names |
imports |
The exports of other stacks that this stack read with Fn::ImportValue |
outputReads |
The outputs of other stacks that this stack read with Fn::GetStackOutput |
skippedOutputs |
Outputs the last deploy could not resolve |
orphans |
Resources a rollback left in AWS and stopped listing under resources |
supersededResources |
The old copy of a replaced resource whose delete has not succeeded yet |
parentStack, parentLogicalId, parentRegion |
Present only on a nested stack's record |
stackInstanceId |
The id of the stack's current lifetime; absent on a stack created before the field existed |
retiredNameGenerations |
For each logical ID whose resource left the stack, the name generation it left at; only on a stack with a stackInstanceId |
lastModified |
Time of the last write, in Unix milliseconds |
Four of these fields change what a command does:
exportNamesdecides which outputs another stack can import. Only a name in this list can satisfy anFn::ImportValue.importsblocks the destroy of the stack that produces the export, for as long as this stack still reads it.outputReadsis informational and blocks nothing.orphanslists resources a rollback kept because theirRetainpolicy told it to. On a stack without astackInstanceId, a later deploy adopts them again; on a stack with one, a later deploy only warns about them and creates new resources under new names, andcdkd importadopts one.supersededResourceslists the old copy of a replaced resource that was still in use when the deploy tried to delete it, such as a target group a listener still pointed at. Every latercdkd deployretries the delete once its own changes are done and drops the entry when it succeeds, andcdkd destroydeletes the resource after the stack's own resources. While the delete keeps failing, the entry stays and each attempt warns.
An outputs value is not always a string. Where CloudFormation defines an
attribute as a list, such as a Route 53 hosted zone's NameServers, the
output is stored as a JSON array. The Outputs: block that cdkd deploy
prints joins the array with commas, so read the stored shape from
cdkd state show, which prints a value that is not a string or number as
JSON.
Resource fields
| Field | Meaning |
|---|---|
physicalId |
The ID cdkd uses to address the resource in AWS |
resourceType |
The CloudFormation type |
properties |
The resolved template values cdkd sent to AWS |
observedProperties |
What AWS reported after the last create or update |
attributes |
The values Fn::GetAtt returns for this resource |
dependencies |
The logical IDs this resource depends on |
deletionPolicy, updateReplacePolicy |
The template attributes as of the last deploy |
provisionedBy |
sdk or cc-api: which route created the resource |
observedBaselineRefused |
cdkd import did not record observedProperties for this resource |
constructPath |
The resource's CDK construct path at its last deploy; informational |
nameGeneration, nameGenerationHigh |
The resource's name generation, and the highest one a rolled-back replacement reached; absent means 0 and none |
How commands use these fields:
cdkd driftcompares the live resource withobservedProperties. That field is the drift baseline.- A destroy deletes resources in the reverse of
dependencies, and readsdeletionPolicyto decide whether to keep a resource. - When
observedBaselineRefusedis set,cdkd state showprintsObservedBaseline: REFUSEDfor the resource.
properties holds what cdkd sent, which can be less than the template
declares. This happens when --prefer-sdk-route keeps a resource on cdkd's
own SDK provider and that provider cannot write one of the properties. cdkd
does not send the property and, unless the property is create-only, does not
record it. cdkd export rebuilds a template from
properties, so the property is missing from the exported template too.
A record can also carry bookkeeping fields that no command asks you to read or
edit: noEchoLeaves, noEchoAttributeNames, noEchoExactEchoLeaves,
maskedPropertyFingerprints, maskedPropertyInputFingerprints,
acceptedCreateOnlyDrops, observedBaselineRefusalReason,
referenceProvenance, secretNamedAttributes, and the stack's conditionVerdicts and
outputReferenceProvenance. The contributor page
State schema internals documents
each one.
How a deploy reads and writes state
A deploy locks the stack, loads the record, saves it after every resource operation, and releases the lock at the end.
Because the record is saved after each resource, a deploy that dies partway
leaves a record of everything it finished. On the next deploy, the resources
that were never recorded are planned as CREATE and tried again.
A failed or interrupted deploy also writes the rollback journal.
cdkd rollback replays the journal without synthesizing
the app.
A destroy works the same way in reverse. It deletes resources in reverse
dependency order and saves the shortened record after each delete. A destroy
that deletes everything also deletes state.json.
A destroy that stops partway keeps the record with the resources that remain,
and clears outputs. Clearing them stops another stack from importing a value
whose resource is already gone.
Upgrading cdkd
A newer cdkd reads every older record and saves it in the current schema version the next time it writes. There is no migration command to run.
An older cdkd refuses a record that a newer one has written. So upgrade every machine and CI job that deploys a stack at the same time. Schema versions and upgrades lists the versions and what the first deploy after an upgrade does.
In this section
- Physical IDs in state: what a
physicalIdis, the composite IDs joined with|, and how they differ from whatRefreturns. - Secrets in state: which secret values cdkd
stores as
***, which it stores in the clear, and what a masked value changes for later deploys. - State locking: which commands lock a stack, how long a lock lasts, and how to clear one a dead process left behind.
- Schema versions and upgrades: the
version table, what happens when cdkd versions are mixed, and the
provisionedBy,exportNamesandskippedOutputsfields. - Malformed state records: what each command does with a damaged record, and how to repair one.
- State backup and bucket security: restoring an earlier record, the bucket configuration, the IAM actions cdkd needs, and S3 replication.
Related
- Manage Stacks Without the App: the introduction to the state store
cdkd state: every subcommand and flag- Orphan Instead of Destroy: the commands that remove a record, with and without deleting the resources
- State schema internals: contributor page with the version history, the full field reference and the implementation