Skip to content
cdkd

Import Existing Resources

cdkd import makes cdkd the manager of AWS resources that already exist. It reads your CDK app, finds the real resource behind each one the app declares, and writes a state record for it. The next cdkd deploy then updates those resources in place instead of trying to create duplicates. An import does not create, change or delete any AWS resource.

# preview what would be adopted
cdkd import MyStack --dry-run

# adopt a cdk deploy stack and retire it
cdkd import MyStack --migrate-from-cloudformation

# adopt one hand-made resource
cdkd import MyStack --resource MyBucket=acme-uploads

# adopt everything cdkd can find
cdkd import MyStack
What cdkd import does to ownershipBefore an import, a CloudFormation stack (or nobody) manages the AWS resources. cdkd import reads each physical id and writes a state record for it, without changing the resource. With --migrate-from-cloudformation it first sets every resource in the CloudFormation stack to Retain and reads the ids again, since that update can replace a resource; it writes state from those ids and then deletes the stack, which leaves the resources in place. Afterwards only cdkd state manages them, and the next cdkd deploy updates them in place.AWS resources, managed byCloudFormation or by nobodyDeployed with cdk deploy, or created by handcdkd importReads each physical idMigrating: sets Retain, then re-reads the idscdkd stateOne record per adopted resourceWritten after you confirmCloudFormation stack retiredRetain was set before the state writeDeleteStack after itOnly with --migrate-from-cloudformationSame AWS resources, managed by cdkdThe next cdkd deploy updates them in placeWhat cdkd import does to ownershipBefore an import, a CloudFormation stack (or nobody) manages the AWS resources. cdkd import reads each physical id and writes a state record for it, without changing the resource. With --migrate-from-cloudformation it first sets every resource in the CloudFormation stack to Retain and reads the ids again, since that update can replace a resource; it writes state from those ids and then deletes the stack, which leaves the resources in place. Afterwards only cdkd state manages them, and the next cdkd deploy updates them in place.AWS resources, managed byCloudFormation or by nobodyDeployed with cdk deploy, or created by handcdkd importReads each physical idMigrating: sets Retain, then re-reads the idscdkd stateOne record per adopted resourceWritten after you confirmCloudFormation stack retiredRetain was set before the state writeDeleteStack after itOnly with --migrate-from-cloudformationSame AWS resources, managed by cdkdThe next cdkd deploy updates them in place

Two words appear throughout these pages. A resource's logical ID is its name in the synthesized template, such as Uploads5E5E9B2F. Its physical id is what AWS calls the real resource, such as the bucket name acme-uploads. An import pairs each logical ID with a physical id.

The examples assume cdkd reads the CDK app command from cdk.json. Pass --app "<command>" when you run cdkd outside the CDK project directory.

Pick your scenario

You have Run Section
A stack deployed with cdk deploy that cdkd should take over cdkd import MyStack --migrate-from-cloudformation Migrating from cdk deploy
One or a few resources created by hand or by another tool cdkd import MyStack --resource <logicalId>=<physicalId> Adopt hand-made resources
A whole stack of existing resources with explicit names cdkd import MyStack Adopt every resource cdkd can find
An import you want to undo cdkd state orphan MyStack or cdkd export MyStack Go back

In this section

  • Import options and the import plan: every flag, when --force is needed, how to read the plan, and how cdkd import differs from cdk import.
  • Importing by resource type: the value --resource expects for each type, and the types with special behaviour.
  • After an import: what the first cdkd diff shows, what cdkd records for drift detection, and how to remove a secret that reached state.

Migrating from cdk deploy (CloudFormation) to cdkd

--migrate-from-cloudformation moves a stack from CloudFormation to cdkd in one command. It adopts every resource of the CloudFormation stack into cdkd state and then deletes the CloudFormation stack while keeping its resources. You pass no physical ids, because cdkd reads them from CloudFormation.

  1. Preview the import

    cdkd import MyStack --dry-run
    

    A plain --dry-run reads the physical ids from the CloudFormation stack of the same name and writes nothing. Check that every row of the plan is marked ✓. A row with any other mark would be left unmanaged by the migration; Read the import plan explains each mark.

  2. Adopt the stack and retire the CloudFormation stack

    cdkd import MyStack --migrate-from-cloudformation
    

    cdkd asks for confirmation twice: to write state, and to change and then delete the CloudFormation stack. --yes answers both. CI needs --yes, because the prompts refuse to run without a terminal. The run ends with:

    [1/4] Inspecting CloudFormation stack 'MyStack'...
    [2/4] Injected DeletionPolicy=Retain and UpdateReplacePolicy=Retain.
    [3/4] Updating CloudFormation stack with Retain policies...
    ✓ State written: MyStack (us-east-1)
    [4/4] Deleting CloudFormation stack 'MyStack' (resources retained)...
    ✓ CloudFormation stack 'MyStack' retired. AWS resources are now solely managed by cdkd.
    
  3. Compare the adopted state with your template

    cdkd diff MyStack
    

    A property that differs between AWS and the template shows as a change the next deploy will apply.

  4. Deploy with cdkd from now on

    cdkd deploy MyStack
    

When the CloudFormation stack has a different name from the cdkd stack, pass that name after the flag. The preview in step 1 cannot read such a stack, because a plain import looks only for a CloudFormation stack of the same name.

cdkd import MyStack --migrate-from-cloudformation LegacyCfnStackName --yes

What the migration does, in order

  1. cdkd reads every logical ID and physical id from the CloudFormation stack with DescribeStackResources. A --resource value you pass takes precedence over CloudFormation's.
  2. cdkd adopts each resource by that physical id.
  3. cdkd updates the CloudFormation stack so that every resource has DeletionPolicy: Retain and UpdateReplacePolicy: Retain.
  4. When that update completed, cdkd reads the ids again. If a root resource's id changed, it adopts every root resource again by the new ids; a resource it can no longer import stops the run before any state is written. If the re-import adopts a different number of resources than you confirmed, cdkd warns with both counts.
  5. cdkd writes state: the root stack first, then each nested stack. A nested stack's replaced resource is adopted by its new id when that nested stack is written; one it can no longer import stops the run there.
  6. cdkd deletes the CloudFormation stack. Every resource is retained, so CloudFormation deletes none of them.

The update in step 3 usually changes template metadata only. CloudFormation can still decide that it replaces a resource: an auto-named AWS::SSM::Document with object Content is a known case. cdkd then warns, naming the old and the new physical id, and state records the new resource that the stack now holds. UpdateReplacePolicy: Retain keeps the old copy and its data, so nothing is deleted. That copy is managed by neither tool: delete it yourself once you no longer need it.

cdkd holds the stack lock from before the update through the retirement, so a concurrent cdkd deploy cannot run in between.

If the migration stops partway

If the run stops before step 5, cdkd has written no state.

  • The update failed. CloudFormation rolled it back, and the error says why. A re-run repeats the update. When the cause is a replacement CloudFormation cannot perform (a custom-named resource whose update requires replacing it), the update fails the same way by hand too: give that resource no custom name or a new one, or take it out of the stack, then re-run. To retire the stack by hand instead: (1) update it with DeletionPolicy: Retain and UpdateReplacePolicy: Retain on every resource, (2) run cdkd import without the flag, which reads the ids from a same-named CloudFormation stack, (3) delete it. The import must adopt every resource before you delete the stack: check its summary.
  • The update completed, but the run stopped after it (a failed re-read, an interrupted wait). A re-run skips the update, because the template already carries the policies, so it cannot report a replacement. The error says how to find replaced copies: in the stack's events, a row whose reason reads Requested update requires the creation of a new physical resource names the old physical id, which is retained and managed by neither tool.

If the run stops during step 5 because a nested stack's replaced resource could not be imported, the root stack's state and every nested stack written before it are saved, the rest are not, and the CloudFormation stack is not deleted. Run the same command again with --force, which is needed because state now exists.

If step 6 fails, cdkd state is written and correct, and every resource carries DeletionPolicy: Retain, so deleting the stack deletes no resource. The error gives the stack's status when cdkd knows it. Delete the stack by hand once the cause is resolved, for example an export that another stack still imports.

If you decline the second prompt, cdkd writes the state you confirmed and leaves the CloudFormation stack unchanged.

Nested stacks

A CloudFormation stack that contains AWS::CloudFormation::Stack children is migrated as a tree, to any depth, and each nested stack gets its own cdkd state file. Only the migration flag does this: a plain cdkd import reports each nested stack row as unsupported. See Nested stacks.

Edge cases

  • A resource could not be imported. cdkd warns before it retires the CloudFormation stack. If you continue, that resource stays in AWS and neither tool manages it. Import it first with --resource, or accept that.
  • --dry-run together with the flag. This combination is not supported, because the stack update and delete cannot be simulated. Use a plain cdkd import --dry-run for the preview.
  • A large or YAML template. Both work up to CloudFormation's 1 MB limit; see Template size and format.

Adopt hand-made resources

Use --resource when a few resources already exist and the rest of the stack does not: a bucket someone made in the console, or a queue another tool created. cdkd imports only the resources you list.

  1. Declare the resource in your CDK app

    import * as cdk from 'aws-cdk-lib';
    import * as s3 from 'aws-cdk-lib/aws-s3';
    
    new s3.Bucket(this, 'Uploads', {
      bucketName: 'acme-uploads',
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });
    
  2. Find its logical ID

    Run cdkd synth and read the logical ID, such as Uploads5E5E9B2F, from the template in cdk.out/.

  3. Preview, then import

    cdkd import MyStack --resource Uploads5E5E9B2F=acme-uploads --dry-run
    cdkd import MyStack --resource Uploads5E5E9B2F=acme-uploads
    

    If the key is not a logical ID in the template, the run stops before any AWS call.

  4. Review and deploy

    cdkd diff MyStack     # resources you did not list show as to create
    cdkd deploy MyStack
    

The plan reports every resource you did not list as out of scope and leaves it out of state, so the next cdkd deploy creates it. cdk import --resource-mapping treats unlisted resources the same way.

When the stack already has state, cdkd adds the listed resources to it and keeps the records already there. You need --force only when a listed resource is already in state, because its record would be overwritten.

Several resources, or a mapping file

# --resource is repeatable
cdkd import MyStack \
  --resource Uploads5E5E9B2F=acme-uploads \
  --resource ResizeFn9A1B2C3D=acme-resize

# The same mapping as a JSON file: { "Uploads5E5E9B2F": "acme-uploads" }
cdkd import MyStack --resource-mapping mapping.json

# Or inline, for a CI script that does not want a file
cdkd import MyStack \
  --resource-mapping-inline '{"Uploads5E5E9B2F":"acme-uploads"}'

--resource-mapping and --resource-mapping-inline cannot be combined.

Physical ids that contain |

Some resource types have a physical id made of several parts joined with |, such as <databaseName>|<tableName> for a Glue table. Quote such a value, because the shell reads an unquoted | as a pipe:

cdkd import MyStack \
  --resource 'EventsTable7C1D2E3F=analytics_db|events' \
  --resource 'GetMethod4B5C6D7E=a1b2c3d4e5|xy9z8w|GET'

A --resource-mapping JSON file needs no escaping. The joined form is what cdkd state show and cdkd state resources print, and it is not always the id CloudFormation shows. Importing by resource type lists the form for each type.

Adopt every resource cdkd can find

With no --resource flag, cdkd tries to import every resource in the template:

cdkd import MyStack --dry-run
cdkd import MyStack

This is also how you adopt a cdk deploy stack while leaving its CloudFormation stack in place. Both tools then hold a record of the same resources, so treat this as a preview or as a step towards --migrate-from-cloudformation.

When the stack already has state, this form rebuilds every record from the template and needs --force.

How cdkd finds each resource

How cdkd import finds one resourceFor each resource in the template, cdkd import uses the physical id from --resource or a mapping file when one names the resource. Otherwise, in auto and hybrid mode, it looks the resource up by the name property the template sets. If that finds nothing, it reads the physical id from a CloudFormation stack of the same name. A resource none of the three resolves is reported as not found.not namedno matchno match--resource or a mapping file names itUsed as given, in every modeThe template sets a name propertyBucketName, RoleName, TopicName and the likeA CloudFormation stackof the same name existsDescribeStackResources supplies the physical idReported as not foundPass --resource for it and run againHow cdkd import finds one resourceFor each resource in the template, cdkd import uses the physical id from --resource or a mapping file when one names the resource. Otherwise, in auto and hybrid mode, it looks the resource up by the name property the template sets. If that finds nothing, it reads the physical id from a CloudFormation stack of the same name. A resource none of the three resolves is reported as not found.not namedno matchno match--resource or a mapping file names itUsed as given, in every modeThe template sets a name propertyBucketName, RoleName, TopicName and the likeA CloudFormation stackof the same name existsDescribeStackResources supplies the physical idReported as not foundPass --resource for it and run again

cdkd first looks a resource up by the name your template sets for it. Most CDK apps set no names, so CloudFormation generated them. For those, cdkd reads the physical id from a CloudFormation stack that has the same name as the cdkd stack, and prints the name of the stack it read from.

A CDK app that sets no explicit names and has no CloudFormation stack of the same name must use --resource. cdkd cannot find resources by the aws:cdk:path tag, because AWS resources do not carry it: CloudFormation keeps the construct path in template metadata.

How the lookups can fall short covers a missing permission and a name built from the account or region.

Name some resources and look up the rest: --auto

When one resource cannot be found automatically, name that one and add --auto. cdkd uses the ids you give and looks up every other resource:

cdkd import MyStack --resource Uploads5E5E9B2F=acme-uploads --auto

Without --auto, the same command would import the bucket alone.

Go back

An import writes only cdkd state, so undoing it is a state operation.

You want to Run
Drop the whole imported record cdkd state orphan MyStack
Drop one imported resource cdkd state orphan MyStack --resource Uploads5E5E9B2F
Hand the stack back to CloudFormation cdkd export MyStack

cdkd state orphan removes state records and leaves the AWS resources alone. With --resource it removes one record and rewrites the references other records hold to it.

What orphaning leaves you with depends on how you imported:

  • Without --migrate-from-cloudformation, the CloudFormation stack is still there and still manages the resources. cdkd state orphan MyStack returns you to where you started.
  • After a migration, the CloudFormation stack is gone, so orphaning leaves the resources managed by nobody. To return them to CloudFormation, run cdkd export, which imports the resources into a CloudFormation stack and deletes cdkd state. Hand a Stack to CloudFormation covers the types CloudFormation cannot import and Custom Resources.

After the import

Run cdkd diff MyStack before the first deploy. A property that differs between AWS and the template shows as a change the next cdkd deploy applies, and a resource you did not import shows as to create. After an import covers the less common cases.

An import that adopts the physical id the record already holds keeps the record's name generation. One that adopts a different resource for the logical ID records no generation of its own, and remembers the highest one the replaced record reached, so the next replacement still generates a name neither resource holds.

A replacement refused on its new name

On a stack with a stackInstanceId, cdkd gives a resource your template does not name a generated name that ends in a suffix built from the stack's id, the logical ID and the resource's name generation. A replacement creates the new resource at the next generation, so the new name differs from the old one, the new resource is created first and the old one is deleted after it, as CloudFormation does. No --replace is needed.

If a resource already holds the next-generation name, the deploy refuses with NAMED_REPLACEMENT_COLLISION and deletes nothing. The holder is most likely the new resource an earlier attempt of the same replacement created before it was interrupted. The message prints the command that adopts it:

cdkd import MyStack --resource MyQueue=<physicalId>   # adopt the new copy

Then delete the old resource by hand, or delete the holder instead, and run the deploy again. --replace does not help here: cdkd cannot show that the old resource holds the new name, so it deletes nothing.

For an SQS queue, an SNS topic, a log group, an S3 bucket, an EventBridge rule, a CloudWatch alarm, an ECS cluster, an ELBv2 load balancer or target group, and a Step Functions state machine, the deploy usually never gets there: the name check looks the new name up before the deploy starts and records it, so a re-run after a crash takes back the resource the killed run made.

A value your template never references is recorded as AWS holds it

An import copies each adopted resource's current AWS properties into state.json, so that cdkd drift can later report what changed. A secret that sits in one of those properties is copied too, unless your template names it through a {{resolve:...}} reference, in which case cdkd stores the reference in its place.

A secret reaches state.json this way when:

  • the template declares a placeholder literal such as CHANGEME, and someone later set the real value in the console or through an API call;
  • AWS reports a field or list element that has no counterpart in the template;
  • the resource was imported through the Cloud Control API fallback, which records every property AWS returns.

This is by design. The copy has to hold what AWS holds, changes made outside cdkd included, or cdkd drift could not report them. So treat state.json as sensitive, and limit who can read the state bucket.

cdkd scrub does not find such a value, because it recognises a secret only through a reference the template spells. Treat a value that was ever stored this way as exposed and rotate it. A secret set outside your template has the details and the steps that remove the value from state.

Which resource types can be imported

Whether cdkd can import a resource, and whether you must name it, depends on its type. Search this section for your type, for example AWS::Lambda::Function.

Group What you pass
Found without a flag Nothing, when the template names the resource or a CloudFormation stack of the same name exists
Need --resource --resource <logicalId>=<physicalId>, unless a CloudFormation stack supplies the id
Any other type --resource, when the Cloud Control API supports the type
Cannot be imported Nothing works; the plan reports unsupported

Importing by resource type lists the --resource value each type expects and describes the Cloud Control API fallback.

Found without a flag

cdkd finds these types by the lookups above when you run cdkd import with no --resource, or with --auto.

Service Types
ACM AWS::CertificateManager::Certificate
API Gateway v2 AWS::ApiGatewayV2::Api
AppSync AWS::AppSync::GraphQLApi
Bedrock AgentCore AWS::BedrockAgentCore::Browser, AWS::BedrockAgentCore::CodeInterpreter
Budgets AWS::Budgets::Budget
CloudFront AWS::CloudFront::Distribution
CloudTrail AWS::CloudTrail::Trail
CloudWatch, Logs AWS::CloudWatch::Alarm, AWS::Logs::LogGroup
CodeBuild, CodeCommit AWS::CodeBuild::Project, AWS::CodeCommit::Repository
Cognito AWS::Cognito::UserPool
DLM AWS::DLM::LifecyclePolicy
DocumentDB AWS::DocDB::DBInstance, AWS::DocDB::DBCluster, AWS::DocDB::DBSubnetGroup
DynamoDB AWS::DynamoDB::Table, AWS::DynamoDB::GlobalTable
EC2 AWS::EC2::VPC, AWS::EC2::Subnet, AWS::EC2::SecurityGroup, AWS::EC2::NatGateway, AWS::EC2::EIP, AWS::EC2::InternetGateway, AWS::EC2::RouteTable, AWS::EC2::NetworkAcl, AWS::EC2::Instance
ECR, ECS AWS::ECR::Repository, AWS::ECS::Cluster, AWS::ECS::Service, AWS::ECS::TaskDefinition
EFS, FSx AWS::EFS::FileSystem, AWS::EFS::AccessPoint, AWS::FSx::FileSystem
ElastiCache AWS::ElastiCache::CacheCluster, AWS::ElastiCache::SubnetGroup
ELB v2 AWS::ElasticLoadBalancingV2::LoadBalancer, AWS::ElasticLoadBalancingV2::TargetGroup
EMR AWS::EMR::Cluster
EventBridge, Pipes AWS::Events::EventBus, AWS::Events::Rule, AWS::Pipes::Pipe
Glue AWS::Glue::Database, AWS::Glue::Table, AWS::Glue::Job, AWS::Glue::Crawler, AWS::Glue::Connection, AWS::Glue::Trigger, AWS::Glue::Workflow, AWS::Glue::SecurityConfiguration
IAM AWS::IAM::Role, AWS::IAM::ManagedPolicy, AWS::IAM::InstanceProfile, AWS::IAM::User, AWS::IAM::Group
Kinesis, Firehose AWS::Kinesis::Stream, AWS::Kinesis::StreamConsumer, AWS::KinesisFirehose::DeliveryStream
KMS AWS::KMS::Key, AWS::KMS::Alias
Lambda AWS::Lambda::Function, AWS::Lambda::LayerVersion
Neptune AWS::Neptune::DBInstance, AWS::Neptune::DBCluster, AWS::Neptune::DBSubnetGroup
RDS AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::RDS::DBProxy, AWS::RDS::DBProxyEndpoint, AWS::RDS::DBSubnetGroup
Route 53 AWS::Route53::HostedZone
S3 AWS::S3::Bucket, AWS::S3Express::DirectoryBucket, AWS::S3Tables::TableBucket, AWS::S3Vectors::VectorBucket
Secrets Manager, SSM AWS::SecretsManager::Secret, AWS::SSM::Parameter
Service Discovery AWS::ServiceDiscovery::Service, AWS::ServiceDiscovery::PrivateDnsNamespace, AWS::ServiceDiscovery::HttpNamespace, AWS::ServiceDiscovery::PublicDnsNamespace
SNS, SQS AWS::SNS::Topic, AWS::SQS::Queue
Step Functions AWS::StepFunctions::StateMachine
WAF v2 AWS::WAFv2::WebACL

Need --resource

cdkd has nothing to look these types up by. Each one has no name of its own, belongs to a parent resource, or links two other resources. Pass the physical id with --resource, --resource-mapping or --resource-mapping-inline.

You pass nothing when CloudFormation can supply the id: under --migrate-from-cloudformation, or with --auto and a CloudFormation stack of the same name.

Service Types
API Gateway AWS::ApiGateway::Account, AWS::ApiGateway::Authorizer, AWS::ApiGateway::Deployment, AWS::ApiGateway::Method, AWS::ApiGateway::Resource, AWS::ApiGateway::Stage
API Gateway v2 AWS::ApiGatewayV2::Authorizer, AWS::ApiGatewayV2::Integration, AWS::ApiGatewayV2::Route, AWS::ApiGatewayV2::Stage
AppSync AWS::AppSync::ApiKey, AWS::AppSync::DataSource, AWS::AppSync::GraphQLSchema, AWS::AppSync::Resolver
Bedrock AgentCore AWS::BedrockAgentCore::Evaluator, AWS::BedrockAgentCore::Runtime
CloudFormation AWS::CloudFormation::CustomResource, AWS::CloudFormation::WaitConditionHandle
CloudFront AWS::CloudFront::CloudFrontOriginAccessIdentity, AWS::CloudFront::OriginAccessControl
CloudWatch AWS::CloudWatch::AnomalyDetector
EC2 AWS::EC2::NetworkAclEntry, AWS::EC2::Route, AWS::EC2::SecurityGroupIngress, AWS::EC2::SubnetNetworkAclAssociation, AWS::EC2::SubnetRouteTableAssociation, AWS::EC2::VPCGatewayAttachment
EFS AWS::EFS::MountTarget
ELB v2 AWS::ElasticLoadBalancingV2::Listener
EventBridge Scheduler AWS::Scheduler::Schedule
IAM AWS::IAM::AccessKey, AWS::IAM::Policy, AWS::IAM::UserToGroupAddition
Lambda AWS::Lambda::EventInvokeConfig, AWS::Lambda::EventSourceMapping, AWS::Lambda::MicrovmImage, AWS::Lambda::NetworkConnector, AWS::Lambda::Permission, AWS::Lambda::Url, AWS::Lambda::WebFunctionEndpoint, AWS::Lambda::WebFunctionRevision
RDS AWS::RDS::DBProxyTargetGroup
Route 53 AWS::Route53::RecordSet
S3 AWS::S3::BucketPolicy
S3 Tables AWS::S3Tables::Namespace, AWS::S3Tables::Table
SNS, SQS AWS::SNS::Subscription, AWS::SNS::TopicPolicy, AWS::SQS::QueuePolicy

Cannot be imported

The plan reports these types as unsupported and skips them.

Resource type Why
AWS::CloudFormation::Stack A plain import cannot adopt a nested stack. Use --migrate-from-cloudformation, which walks the tree.
AWS::AutoScaling::AutoScalingGroup cdkd can create, update, delete and detect drift on the type, but cannot import it.
AWS::EMR::InstanceGroupConfig, AWS::EMR::InstanceFleetConfig Each is part of a cluster and has no name of its own.

Last updated: