Skip to main content

Destroying Infrastructure

Removing infrastructure is the operation nobody rehearses. The command is easy enough, and the uncertainty is not about the command: it is whether the thing you are about to remove is still holding something up, and whether the list on screen is the whole list.

In Octo you do not run a destroy command. You take the thing out of your intent and run the same four steps as always, and the removal is reported the same way every other change is reported, at the level you removed it.

Removing one piece​

The shop has a checkout server that can read and write the receipts bucket. To remove it, comment out its module:

octo.yaml
#  - moduleId: checkout-server-module
# moduleClass: AwsEcsServerModule
# moduleInputs:
# account: "${{account-module.model.account}}"
# serverKey: checkout
# s3:
# - service: "${{receipts-module.model.service}}"
# accessPrefixes:
# - access: READ_WRITE
# accessPrefix: uploads

Then generate:

npm run octo:generate
INFO: Wrote 0 terragrunt folder(s) to ".octo-tf", emptied 3 (deleted from intent). {"emptied":["receipts-module","checkout-server-module","region-module"],"outputDir":".octo-tf","written":[]}

"Emptied" is the important word. Octo did not delete .octo-tf/checkout-server-module/; it rewrote it with the Terraform block and the providers, and no resources. Terragrunt still finds the folder, Terraform still has its state, and an empty configuration against a populated state is exactly what makes the next apply destroy those resources.

Deleting the folder would do the opposite. Terraform would never look at that state again, and the role would sit in AWS with nothing tracking it. Generate never deletes a folder for this reason; the commit after the destroy does, once the folder's state holds nothing.

Reading what will go​

npm run octo:validate
You removed checkout-server-module (+2 other changes). That destroys or replaces 8 things — check those before anything else.
- 8 destroy ± 0 replace + 0 create ~ 0 update

WHAT WILL CHANGE
- removed checkout-server-module [checkout-server-module | grouped by module | -4]
- iam-role-ServerRole-checkout
- aws_iam_policy.iam-role-ServerRole-checkout_aws-ecs-server-s3-access-overlay-937a53bb02a4
- aws_iam_role_policy_attachment.iam-role-ServerRole-checkout_AmazonECSTaskExecutionRolePolicy
- aws_iam_role_policy_attachment.iam-role-ServerRole-checkout_aws-ecs-server-s3-access-overlay-937a53bb02a4_attach
- aws_iam_role.iam-role-ServerRole-checkout
- removed region-module [region-module | grouped by module | -2]
- vpc-eu-west-1
- aws_vpc.vpc-eu-west-1
- igw-eu-west-1
- aws_internet_gateway.igw-eu-west-1

Destroys sort to the top of the report, and the counters lead with them. A removal card is headed by the module rather than by a model context, because the models are gone from your intent, and the module ID is the only name left that means anything.

Read the whole list before going further. The question worth asking is not whether these should go, but whether anything you did not remove is quietly in there too.

Applying it​

LOCK_ID=$(npm run --silent octo:lock)
npm run octo:apply -- --lockId $LOCK_ID
This apply would DESTROY:
[checkout-server-module] aws_iam_role.iam-role-ServerRole-checkout (delete)
[receipts-module] aws_s3_bucket.bucket-shop-receipts (delete) - the data it holds is lost with it
[region-module] aws_vpc.vpc-eu-west-1 (delete)
...

Error: Refusing to apply. Run "octo validate" to see why each of these is being destroyed, then re-run with --auto-approve to proceed!

Terragrunt is always given -auto-approve, so there is no prompt to answer during an apply. This check is what stands in for one: octo apply plans first, and refuses if anything would be destroyed. A destroy that takes data with it, like the bucket's objects, says so on its line.

npm run octo:apply -- --lockId $LOCK_ID --auto-approve

That flag is required for every destroy, including one you fully intended, except removing an external resource that loses no data, which Octo deletes at commit. It is not a --force; it is an acknowledgement that you have read the list above. The refusal leaves the lock held, so if the list tells you not to go ahead, release it with npm run octo:unlock -- --lockId $LOCK_ID.

Committing the removal​

npm run octo:commit -- --lockId $LOCK_ID
WHAT CHANGED
- removed checkout-server-module [checkout-server-module | grouped by module | -4]
...

MODULES THAT NO LONGER RENDER ANYTHING
checkout-server-module
receipts-module
region-module

Committing after a destroy is not optional. Until it runs, Octo's record still says the bucket and the role exist, and anything Terraform did not delete itself has not been dealt with yet.

The last section names the folders that are now empty. The commit deletes each one whose Terraform state holds nothing, and prints a line for it:

Deleted folder "checkout-server-module", whose module left intent and whose destroy is committed.

A folder whose destroy did not finish, after a failed apply, keeps its state and stays.

Deleting is not renaming​

The one case worth being careful about. Change the bucket name in the shop's definition file and the report says:

WHAT WILL CHANGE
+ added service=shop-receipts-v2-s3-storage [... | receipts-module | +1]
+ bucket-shop-receipts-v2
+ aws_s3_bucket.bucket-shop-receipts-v2
- removed receipts-module [receipts-module | grouped by module | -1]
- bucket-shop-receipts
- aws_s3_bucket.bucket-shop-receipts

A rename is a destroy and a create, because a resource's identity is derived from what you wrote. For a bucket that means the objects inside it. The report tells you before it happens, and the apply refuses without --auto-approve, but nothing will stop you if you insist.

A moduleId is different. Rename receipts-module and the bucket keeps its resource ID, because the ID comes from the models, not from the module; only its folder changes. Octo moves the bucket's Terraform state from receipts-module/ into the new folder before Terraform plans, so the apply destroys nothing and needs no --auto-approve, and the commit deletes the old, empty folder.

Taking everything down​

Comment out every module except the app and the account, which produce nothing in AWS themselves and are what the rest of the file hangs off. A tags entry scoped to one of the modules you commented out goes with it, or generate stops with Module not found! and the ID that entry names:

npm run octo:generate

LOCK_ID=$(npm run --silent octo:lock)
npm run octo:apply -- --lockId $LOCK_ID --auto-approve
npm run octo:commit -- --lockId $LOCK_ID

Afterwards, .octo/ still exists and describes an app with nothing in it, and the commit has deleted every folder in .octo-tf/. That is the correct resting state, and re-adding a module from here works normally.