Alright, let’s get our hands dirty with the actual mechanics of CloudFormation. You’ve got your template—a beautiful, YAML or JSON masterpiece—and now you need to make it real. This is where stack operations come in. Think of a stack as the unit of life for your infrastructure. It’s the bundle of resources CloudFormation creates, manages, and, crucially, destroys as a single entity. You don’t create an EC2 instance; you create a stack that contains an EC2 instance, along with its security group, IAM role, and whatever else it needs. This atomic nature is your best friend and occasional tormentor.

The Grand Creation

Creating a stack is the easy part, right? You point aws cloudformation create-stack at your template and hit go. But let’s talk about the details you’ll immediately trip over.

First, the stack name. It’s not just a label; it’s how you’ll refer to this entire deployment forevermore. It must be unique within your account and region. Pro tip: include an environment suffix (e.g., -prod, -dev). You’ll thank me later when you have six stacks and can’t tell them apart.

Second, parameters. Your template likely has them (Parameters: section, remember?). You must provide values for any without a Default. You can do this via a parameters file (a JSON file that maps parameter names to values), which is my strong preference for anything beyond trivial testing. It’s version-control friendly and saves you from typing out a monstrous command.

# The 'throw it together quickly for a test' method. I'm not judging.
aws cloudformation create-stack \
  --stack-name my-app-dev \
  --template-body file://template.yaml \
  --parameters ParameterKey=InstanceType,ParameterValue=t3.micro ParameterKey=EnvName,ParameterValue=dev

# The 'I am a professional and I like repeatable deployments' method.
aws cloudformation create-stack \
  --stack-name my-app-dev \
  --template-body file://template.yaml \
  --parameters file://dev-params.json

And here’s a sample dev-params.json:

[
  {
    "ParameterKey": "InstanceType",
    "ParameterValue": "t3.micro"
  },
  {
    "ParameterKey": "EnvName",
    "ParameterValue": "dev"
  }
]

Now, you’ll run the command and… nothing will happen. Or so it seems. CloudFormation returns a StackId almost immediately and goes off to do its work asynchronously. You must use describe-stacks or, my favorite, cloudformation wait stack-create-complete to, well, wait for it to finish. Never assume success just because the create command returned without an error.

The Perilous Update

Updating a stack is where the real magic—and heartbreak—happens. You run aws cloudformation update-stack, which is syntactically almost identical to create-stack. The key difference is what happens next. CloudFormation doesn’t just blindly apply your changes. It performs a comparison between your new template and the existing template (the one currently associated with the stack), calculates a change set, and then executes it.

This is a brilliant design… in theory. In practice, its intelligence is sometimes questionable. The number of things that cannot be updated in-place is long and occasionally baffling. Try to change the name of an RDS instance? Nope. Change the name of an S3 bucket? Absolutely not. Alter the fundamental nature of an EC2 instance? Probably a replacement. For these resources, CloudFormation will delete the old resource and create a new one. This is where you learn the importance of the DeletionPolicy attribute the hard way, potentially saying a tearful goodbye to data in an RDS instance you thought was safe.

Your Salvation: Change Sets

Because the direct update-stack is a bit of a gamble, the wise among us use Change Sets. A change set is a preview of what CloudFormation intends to do during an update. It shows you exactly which resources will be added, modified, or, most importantly, replaced. It is the single most valuable tool in your CloudFormation arsenal for preventing catastrophic, unintended downtime.

You create a change set, review its proposed actions, and then choose to execute it or discard it. It’s a dry run. It’s your get-out-of-jail-free card.

# Create the change set to see what would happen
aws cloudformation create-change-set \
  --stack-name my-app-dev \
  --change-set-name my-change-set \
  --template-body file://template-v2.yaml \
  --parameters file://dev-params.json

# Wait for the change set to be created
aws cloudformation wait change-set-create-complete \
  --stack-name my-app-dev \
  --change-set-name my-change-set

# Describe it to see the proposed changes in glorious, terrifying detail
aws cloudformation describe-change-set \
  --stack-name my-app-dev \
  --change-set-name my-change-set

# If it looks good, execute it. If it looks like a nightmare, delete it.
aws cloudformation execute-change-set \
  --stack-name my-app-dev \
  --change-set-name my-change-set

I cannot overstate this: Always use a change set for updates in any non-dev environment. The console makes this easy; the CLI makes it a few extra commands. There is no excuse for blindly running an update.

The Final Delete

Deleting a stack is as simple as aws cloudformation delete-stack --stack-name my-app-dev. But simplicity is a trap. This command will, by default, delete every resource in the stack. Every. Single. One. That S3 bucket full of customer data? Gone. That database with years of analytics? Poof.

This is why you use DeletionPolicy in your templates. Tag a resource as DeletionPolicy: Retain and CloudFormation will leave it alone upon stack deletion. It will remove it from the stack’s management but won’t touch the actual resource. Use this judiciously on resources that contain precious, irreplaceable data. The flip side is that you now have orphaned resources to manage manually, but that’s a far better problem than data annihilation.

The delete operation will also fail spectacularly if a resource can’t be cleaned up. A common example is an S3 bucket that isn’t empty. CloudFormation will just give up and put the stack into a DELETE_FAILED state, leaving you to manually empty the bucket and then retry the deletion. It’s frustrating, but it’s better than silently nuking data.