A CloudFormation networking stack exports a subnet ID that three application stacks consume with Fn::ImportValue. What does that coupling stop you from doing later, and how would you loosen it?
answer
- a published value that others read
- the platform refuses to let it move
- account and region bound the namespace
- stop importing before you can change
- indirection through a parameter store
basics
~20 sAn imported export becomes immovable: CloudFormation refuses to delete the exporting stack or to change that output's value while any import exists. Loosen it by publishing the value to SSM Parameter Store and reading it as a template parameter instead.
solid answer
~50 s`Outputs` with an `Export` name publish a value account-wide and region-wide; `Fn::ImportValue` consumes it. The coupling is deliberately rigid: while a consumer imports an export, CloudFormation will not let you delete the producing stack, and will not let you change or remove that output's value — the update fails saying the export cannot be updated because it is in use. So replacing the subnet means updating all three consumers to stop importing first, then changing the export, then repointing them: a three-deployment dance. Export names are also unique per account per region, and imports cannot cross a region or account boundary. The looser alternative is indirection: the networking stack writes the subnet ID to an SSM parameter, and consumers read it via a template parameter of type `AWS::SSM::Parameter::Value<AWS::EC2::Subnet::Id>`, which gives you the value without the hard lock.
code
yaml · 13 linesAWSTemplateFormatVersion: '2010-09-09'
Parameters:
NetworkStackName:
Type: String
Description: Name of the stack that exports the shared subnet
Resources:
Node:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0abcdef1234567890
InstanceType: t3.micro
SubnetId:
Fn::ImportValue: !Sub '${NetworkStackName}-PrivateSubnetId'go deeper
Know that an output with an Export name can be read by another stack with Fn::ImportValue, and that this creates a link between the two stacks.
Explain the three constraints: names unique per account and region, no cross-region or cross-account imports, and no deleting or changing an export while it is imported.
Walk through the migration a live import forces — update consumers off the import, change the export, repoint consumers — and argue for SSM Parameter Store where a value is expected to change.
Own the estate-level rule for what may be exported at all, since every export is a lasting deployment-ordering constraint, and decide where the safety the export provided is re-established by IAM and pipeline discipline instead.
## What an export actually is An output becomes cross-stack when you give it an `Export` name: ```yaml Outputs: PrivateSubnetId: Value: !Ref PrivateSubnet Export: Name: !Sub '${AWS::StackName}-PrivateSubnetId' ``` That name is registered in a **per-account, per-region namespace**. Two stacks in the same account and region cannot export the same name, which is why deriving it from `${AWS::StackName}` is the standard convention. A consumer reads it with `Fn::ImportValue`: ```yaml SubnetId: !ImportValue network-prod-PrivateSubnetId ``` ## The three constraints **You cannot delete a stack whose export is imported.** The delete fails and names the importing stacks. This is a feature: it stops you pulling the ground out from under a running workload. **You cannot change or remove an export's value while it is imported.** An update that would alter that output is rejected with an error to the effect that the export cannot be updated because it is in use by another stack. This is the constraint that bites, because it applies to the *value*, not just the name — repointing the subnet is exactly the change CloudFormation blocks. **Imports never cross a region or an account.** There is no such thing as importing an export from another region. A multi-region estate simply cannot be wired this way. ## What that means operationally Migrating your three application stacks to a new subnet becomes an ordered, multi-deployment procedure: 1. Update each consumer to stop importing — typically by adding a template parameter for the subnet ID, defaulted to the current value, so the import disappears from the template. 2. Now the export is free; update the networking stack to publish the new value, or drop the export entirely. 3. Update each consumer again to take the new value. Every one of those steps is a separate change set and a separate approval, and if the resources involved are replaced rather than updated in place, there is downtime in the middle. That is the real interview point: exports are not merely a naming convention, they are a *deployment-ordering constraint* that shows up months after the template was written, at the worst moment. A second, smaller trap is syntax. You cannot nest two YAML short-form tags, so `!ImportValue !Sub '...'` is invalid. When the export name must itself be built, use the long form: ```yaml SubnetId: Fn::ImportValue: !Sub '${NetworkStackName}-PrivateSubnetId' ``` That pattern — a `NetworkStackName` parameter feeding a substituted import name — is also what makes a consumer template reusable across environments instead of hardcoding one producer. ## The looser alternatives **SSM Parameter Store as the seam.** The producing stack writes the value into an `AWS::SSM::Parameter` under an agreed path such as `/network/prod/private-subnet-id`. A consumer declares a template parameter with an SSM-backed type: ```yaml Parameters: SubnetId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Subnet::Id> Default: /network/prod/private-subnet-id ``` CloudFormation resolves the value at deploy time and validates that it really is a subnet ID. There is now no lock: the producer can change the parameter freely, and a consumer picks up the change on its next deployment. You have traded a guarantee for flexibility — nothing stops someone changing the parameter under a running stack, so the safety the export gave you now has to come from IAM on that parameter path and from process. **Pass it in as an ordinary parameter.** The blunt version: the deployment pipeline reads the value from wherever it lives and supplies it at deploy time. Maximum flexibility, zero coupling, and the pipeline becomes the thing that must be correct. **Look it up.** For values discoverable from tags or naming conventions, a consumer can find the resource itself rather than being told about it — at the cost of the template no longer being explicit about what it attaches to. ## Choosing Use exports for things that genuinely should not move underneath their consumers — a VPC, a core subnet, a shared KMS key — and treat the deletion lock as the point rather than the annoyance. Use SSM or pipeline parameters for values that legitimately change: an AMI ID, a version, an endpoint expected to be repointed. The failure mode to avoid is exporting everything reflexively and discovering, two years in, that no stack in the estate can be deleted without unpicking a dependency chain nobody documented.
- Why is !ImportValue !Sub '...' invalid, and what do you write instead?YAML does not allow one short-form tag to take another as its argument. Use the long form for the outer function: `Fn::ImportValue: !Sub '${NetworkStackName}-PrivateSubnetId'`. That pattern, with the producing stack's name as a parameter, is also what makes one consumer template usable against several environments.
- What do you lose by moving from an export to an SSM parameter?The platform-enforced safety. An export cannot be changed or deleted while imported; an SSM parameter can be overwritten by anyone with permission, and the consuming stack picks up a different value on its next deployment. You have to replace that guarantee with IAM restrictions on the parameter path and with change discipline in the pipeline.
- How do you share a value with a stack in another region?Not with exports — the export namespace is per account and per region and Fn::ImportValue cannot cross either boundary. You replicate the value: write it to an SSM parameter in each target region, pass it in as a stack parameter from the pipeline, or deploy the producing template per region.
saying these in an interview costs you the question
- Believing an export can be updated freely at any time
- Thinking Fn::ImportValue works across regions or accounts
- Assuming export names only need to be unique within a stack
- Writing !ImportValue !Sub and expecting it to parse
- Exporting every output by default as a naming convention