AWS end-to-end run: 2026-09-30¶
Approved by the maintainer on 2026-09-30. The run used the sandbox member account (ID omitted),
in us-east-1, with AWS Copilot CLI v1.34.1 (the official release binary, md5 verified).
Every resource was removed afterwards, and the account matches its pre-run baseline (only the
default VPC). The test's KMS key is in PendingDeletion.
The app¶
ecsodus-e2e had 5 stacks and 49 resources:
| Stack | What it holds |
|---|---|
App stack ecsodus-e2e-infrastructure-roles |
the app roles |
StackSet ecsodus-e2e-infrastructure and its instance |
ECR, KMS, the artifact bucket |
Env stack ecsodus-e2e-test |
VPC, public ALB, cluster, Cloud Map |
Workload stack ecsodus-e2e-test-web |
a Load Balanced Web Service running nginx from Public ECR. Copilot enabled Service Connect by default. |
| Nested addons stack | a DynamoDB table and an S3 bucket, used as sentinels |
Sentinel data was written before migrating: one DynamoDB item and one S3 object.
Result: passed¶
| Step | Outcome |
|---|---|
| Inventory and report | 5 stacks, 49 resources. After the fixes below: ready, all 5 stacks hand off, 44 imports. |
| Read-only dry plan | 41/44 pure imports, then 44/44 after fixes. The gate refused every mismatch before anything changed. |
| 2. Protect | Turned on deletion protection for the table and took a backup. Re-inventoried and regenerated. |
| 3. Retain patches | StackSet patched through update-stack-set (offline diff, polled to SUCCEEDED, verify-retain). App, env and workload stacks patched through change sets: 2 + 23 + 20 policy-only changes accepted. The nested addons stack was patched through the parent's TemplateURL, and its hash was verified. |
| Regeneration | Byte-identical Terraform after patching (§13.9). |
| 4. Import | verify-fresh ok. Plan: 44 to import, 0 to change or destroy. Applied: 44 imported, 0 changed, 0 destroyed. State 44/44. Steady plan: 44/44 no-op. |
| 5. Teardown | Deleted the workload stack, then the orphaned addons stack, then the env, then the StackSet instance (detached with --retain-stacks), then the StackSet, then the app stack. 0 stacks remain. |
| 6. Verify | Steady plan 44/44 no-op after teardown. HTTP 200 from the ALB. DynamoDB and S3 sentinels intact. ECR repo and artifact bucket intact. |
PLAN §6 questions: answered¶
-
Does
DeletionPolicy: Retainon aCustom::*resource suppress its Delete invocation? Yes. Both custom-resource Lambdas (env-controller and rule-priority) had 1 invocation (the Create) before teardown and still had 1 after. The env stack'sALBWorkloadsstayedweb, and the env stack was not updated: the env-controller never ran, and the ALB survived. The neutralizer fallback is not needed. -
How is a policy-only change set reported? As
Modify, withReplacement: False,Scope: [DeletionPolicy, UpdateReplacePolicy], andDetails[].Target.Attribute: DeletionPolicywithAfterValue: Retain. It is applied, so the Metadata fallback is not needed. -
Does a patched nested stack cause
Dynamicentries in the parent? No. The parent shows onlyProperties/TemplateURLwithRequiresRecreation: Never. -
Does the shared ACM validation CNAME survive? Not tested, because the run had no custom domain.
Defects found and fixed during the run¶
Each one was caught by a gate before anything unsafe happened.
-
Service Connect. Copilot enables Service Connect on every LBWS. It is now imported exactly from the live PRIMARY deployment (ADR-0011).
-
Sub whitespace.
Fn::Subwith whitespace inside${ }(Copilot writes${ sentinel.Arn}) is now resolved. -
Cloud Map ARNs. Cloud Map service and namespace ARNs are derived from their IDs.
-
ELB tags. CloudFormation does propagate stack tags onto ELB listeners and rules. Tags are now read live.
-
Forward actions.
- Listener default actions carry the live
forwardblock. - Disabled stickiness is omitted, because the provider rejects
duration = 0. - On listener rules, the provider reads
target_group_arnback inconsistently (present on import, absent after refresh), so it is inignore_changesthere.
- Listener default actions carry the live
-
Bucket policies. S3 bucket policies now use the live
GetBucketPolicydocument, since AWS normalises stored policies. -
Nested change sets. These are
UNAVAILABLEwith the reason "Only executable from the root change set.", which is now accepted for nested change sets only. -
Change-set names.
delete-change-setis asynchronous, so each run now uses a fresh change-set name. -
Trailing blank line.
aws ... --output textappends a blank line, which is no longer treated as a template change. -
Runbook paths. The runbook regenerates in place, and its consistency diff compares only the
.tffiles ecsodus generates. -
StackSet logging. StackSet instance stacks are no longer logged as unrecognised.
Not covered by this run¶
- A custom domain and ACM certificate (PLAN §6 question 4).
- An Aurora addon (the full hand-off test covers it offline).
- A partial migration. Covered offline only.
- NAT gateways and private placement.