Skip to content
cdkd

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

Objects in the cdkd state bucketThe state bucket, cdkd-state-{accountId}, holds five groups of objects. Under the state prefix cdkd/, each stack and region has a directory with state.json (the state record), lock.json (present while a command holds the lock), rollback-journal.json (after a failed or interrupted deploy), create-tokens.json (the create-token ledger), stack-instance.json (the write-ahead stack instance id of a first deploy) and deployments/ (the deployment event history). Also under the state prefix, _index/{region}/exports.json is the exports index, and _refactor/pending.json lists the moves an unfinished cdkd refactor started. Outside the prefix are cdkd-bootstrap/{region}.json (the asset-storage marker), custom-resource-responses/ (one object per custom resource call) and cdkd-migrate-tmp/ (transient CloudFormation templates).s3://cdkd-state-{accountId}/cdkd/{stackName}/{region}/state.json: the state recordlock.json: while acommand holds the lockrollback-journal.json:after a failed orinterrupted deploycreate-tokens.json:create-token ledgerstack-instance.json:write-ahead stack instance iddeployments/: deploymentevent historycdkd/_index/{region}/exports.json: exports indexcdkd/_refactor/pending.json: unfinishedrefactor movescdkd-bootstrap/{region}.json:asset-storage markercustom-resource-responses/One object percustom resource callcdkd-migrate-tmp/Transient CloudFormationtemplatesObjects in the cdkd state bucketThe state bucket, cdkd-state-{accountId}, holds five groups of objects. Under the state prefix cdkd/, each stack and region has a directory with state.json (the state record), lock.json (present while a command holds the lock), rollback-journal.json (after a failed or interrupted deploy), create-tokens.json (the create-token ledger), stack-instance.json (the write-ahead stack instance id of a first deploy) and deployments/ (the deployment event history). Also under the state prefix, _index/{region}/exports.json is the exports index, and _refactor/pending.json lists the moves an unfinished cdkd refactor started. Outside the prefix are cdkd-bootstrap/{region}.json (the asset-storage marker), custom-resource-responses/ (one object per custom resource call) and cdkd-migrate-tmp/ (transient CloudFormation templates).s3://cdkd-state-{accountId}/cdkd/{stackName}/{region}/state.json:the staterecordlock.json:while acommandholds thelockrollback-journal.json: aftera failed orinterrupteddeploycreate-tokens.json:create-tokenledgerstack-instance.json:write-aheadstackinstance iddeployments/: deploymenteventhistorycdkd/_index/{region}/exports.json:exportsindexcdkd/_refactor/pending.json:unfinishedrefactormovescdkd-bootstrap/{region}.json: asset-storagemarkercustom-resource-responses/One objectper customresourcecallcdkd-migrate-tmp/TransientCloudFormationtemplates

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.json is the rollback journal. A failed or interrupted deploy writes it, and cdkd rollback reads it to undo what that deploy did.
  • _index/{region}/exports.json is the exports index. It lists the resolved value of every export in the region, and Fn::ImportValue is answered from it.
  • create-tokens.json lets 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, beside state.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-legacy deletes 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:

  • exportNames decides which outputs another stack can import. Only a name in this list can satisfy an Fn::ImportValue.
  • imports blocks the destroy of the stack that produces the export, for as long as this stack still reads it. outputReads is informational and blocks nothing.
  • orphans lists resources a rollback kept because their Retain policy told it to. On a stack without a stackInstanceId, 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, and cdkd import adopts one.
  • supersededResources lists 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 later cdkd deploy retries the delete once its own changes are done and drops the entry when it succeeds, and cdkd destroy deletes 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 drift compares the live resource with observedProperties. That field is the drift baseline.
  • A destroy deletes resources in the reverse of dependencies, and reads deletionPolicy to decide whether to keep a resource.
  • When observedBaselineRefused is set, cdkd state show prints ObservedBaseline: REFUSED for 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.

When a deploy locks, reads and writes statecdkd deploy first synthesizes the app and publishes assets, before the stack is locked. It then acquires the stack lock by writing lock.json, and loads state.json, upgrading an older schema version. While it creates, updates and deletes resources, state.json is saved after each resource. It then resolves outputs and saves state.json in the current schema version. Last, it releases the lock, which also happens on failure and on the first Ctrl-C.Synthesize the app, publish assetsThe stack is not locked yetAcquire the stack locklock.jsonLoad state.jsonAn older schema version is upgradedCreate, update, delete resourcesstate.json is saved after each resourceResolve outputs, save state.jsonWritten as the current schema versionRelease the lockOn failure and on the first Ctrl-C tooWhen a deploy locks, reads and writes statecdkd deploy first synthesizes the app and publishes assets, before the stack is locked. It then acquires the stack lock by writing lock.json, and loads state.json, upgrading an older schema version. While it creates, updates and deletes resources, state.json is saved after each resource. It then resolves outputs and saves state.json in the current schema version. Last, it releases the lock, which also happens on failure and on the first Ctrl-C.Synthesize the app, publish assetsThe stack is not locked yetAcquire the stack locklock.jsonLoad state.jsonAn older schema version is upgradedCreate, update, delete resourcesstate.json is saved after each resourceResolve outputs, save state.jsonWritten as the current schema versionRelease the lockOn failure and on the first Ctrl-C too

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 physicalId is, the composite IDs joined with |, and how they differ from what Ref returns.
  • 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, exportNames and skippedOutputs fields.
  • 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.

Last updated: