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
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
--forceis needed, how to read the plan, and howcdkd importdiffers fromcdk import. - Importing by resource type: the value
--resourceexpects for each type, and the types with special behaviour. - After an import: what the first
cdkd diffshows, 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.
-
Preview the import
cdkd import MyStack --dry-runA plain
--dry-runreads 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. -
Adopt the stack and retire the CloudFormation stack
cdkd import MyStack --migrate-from-cloudformationcdkd asks for confirmation twice: to write state, and to change and then delete the CloudFormation stack.
--yesanswers 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. -
Compare the adopted state with your template
cdkd diff MyStackA property that differs between AWS and the template shows as a change the next deploy will apply.
-
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
- cdkd reads every logical ID and physical id from the CloudFormation stack
with
DescribeStackResources. A--resourcevalue you pass takes precedence over CloudFormation's. - cdkd adopts each resource by that physical id.
- cdkd updates the CloudFormation stack so that every resource has
DeletionPolicy: RetainandUpdateReplacePolicy: Retain. - 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.
- 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.
- 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: RetainandUpdateReplacePolicy: Retainon every resource, (2) runcdkd importwithout 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 resourcenames 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-runtogether with the flag. This combination is not supported, because the stack update and delete cannot be simulated. Use a plaincdkd import --dry-runfor 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.
-
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, }); -
Find its logical ID
Run
cdkd synthand read the logical ID, such asUploads5E5E9B2F, from the template incdk.out/. -
Preview, then import
cdkd import MyStack --resource Uploads5E5E9B2F=acme-uploads --dry-run cdkd import MyStack --resource Uploads5E5E9B2F=acme-uploadsIf the key is not a logical ID in the template, the run stops before any AWS call.
-
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
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 MyStackreturns 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. |
Related
cdkd exportand Hand a Stack to CloudFormation: the opposite direction- State Store: state records, physical id formats, the bucket policy
- Supported Resources: which types cdkd deploys
cdkd bootstrap: cdkd-owned asset storage