Skip to content

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

  1. Does DeletionPolicy: Retain on a Custom::* 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's ALBWorkloads stayed web, and the env stack was not updated: the env-controller never ran, and the ALB survived. The neutralizer fallback is not needed.

  2. How is a policy-only change set reported? As Modify, with Replacement: False, Scope: [DeletionPolicy, UpdateReplacePolicy], and Details[].Target.Attribute: DeletionPolicy with AfterValue: Retain. It is applied, so the Metadata fallback is not needed.

  3. Does a patched nested stack cause Dynamic entries in the parent? No. The parent shows only Properties/TemplateURL with RequiresRecreation: Never.

  4. 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::Sub with 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 forward block.
    • Disabled stickiness is omitted, because the provider rejects duration = 0.
    • On listener rules, the provider reads target_group_arn back inconsistently (present on import, absent after refresh), so it is in ignore_changes there.
  • Bucket policies. S3 bucket policies now use the live GetBucketPolicy document, since AWS normalises stored policies.

  • Nested change sets. These are UNAVAILABLE with the reason "Only executable from the root change set.", which is now accepted for nested change sets only.

  • Change-set names. delete-change-set is asynchronous, so each run now uses a fresh change-set name.

  • Trailing blank line. aws ... --output text appends 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 .tf files 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.