Skip to content

Give Your AI Agent an Undo Button for AWS State

We gave Claude Code the LocalStack MCP server and a real AWS CDK app, then made a one-line change that looked harmless, which then silently destroyed the data on deployment. The agent caught it, rolled back to a Cloud Pod, and we fixed it forward, all through the MCP server.

LocalStack
Sep 23, 20268 min read

Introduction

A local AWS environment is more than its code. After you deploy the stack and the app starts saving data, the emulator holds valuable state. A small change can destroy that state during the next deploy, even when the deploy succeeds.

We gave Claude Code access to the LocalStack MCP server and a CDK application. The agent deployed the stack and saved its state as a Cloud Pod, a remote snapshot on the LocalStack platform. We then made a small change. The deploy succeeded, but all records were gone. The agent detected the data loss and restored the environment from the Cloud Pod. Once the data was back, we fixed the change, redeployed, and saved a new snapshot.

The MCP server handled every step: deployment, rollback, and snapshots. Here is what happened and how you can try it.

How the Cloud Pods tool works

A snapshot copies the emulator at a specific point in time. It includes its resources and data. You can store it in three ways:

  • Persistence saves it to disk automatically and restores it after a restart.
  • Snapshot files are checkpoints stored on your machine.
  • Cloud Pods are versioned snapshots stored on the LocalStack platform. You can share them with your organization and load them on any machine running LocalStack.

The LocalStack MCP server lets your agent control the emulator. This post uses the following tools:

  • localstack-management starts, stops, restarts, and checks the LocalStack container.
  • localstack-deployer deploys and removes infrastructure built with CDK, Terraform, SAM, or CloudFormation.
  • localstack-aws-client runs AWS CLI commands against LocalStack to inspect its state.
  • localstack-cloud-pods saves, loads, and deletes Cloud Pods. It provides the safety net for this example.

You can also use localstack-state-management to export and import state as a local file. Local snapshots work well for backups on one machine, while Cloud Pods are great for sharing snapshots or loading them on other machines.

This post uses Cloud Pods because you can load them on any machine. Loading one replaces the current state and removes resources created after the snapshot. This provides a clean rollback instead of merging old and new state.

Prerequisites

  • Docker installed and running.
  • A valid LOCALSTACK_AUTH_TOKEN from a LocalStack account. Cloud Pods require a plan or trial that includes them.
  • Node.js 22+ to build the CDK app and run the MCP server with npx.
  • Claude Code or another MCP client.

Step 1: Set up the MCP server

Run the setup wizard to configure your MCP client:

npx -y @localstack/localstack-mcp-server init

The wizard checks Docker, reads LOCALSTACK_AUTH_TOKEN, and finds your installed MCP clients. Select the clients you want to configure, then restart your agent to access the LocalStack tools.

Step 2: Get the sample app

We use localstack-demo, a sample AWS CDK app. POST /requests saves a request to the appRequests DynamoDB table and queues it for background processing. GET /requests lists the saved requests. The stack also includes Lambda functions, an SQS queue, a Step Functions state machine, and a REST API Gateway.

Clone the repository and install its dependencies:

git clone https://github.com/localstack-samples/localstack-demo.git
cd localstack-demo
make install       # CDK dependencies
make bundle-node   # Node.js Lambda dependencies

You do not need to start LocalStack or deploy the app. The agent does both in the next step.

Step 3: Deploy the app and checkpoint it

Open Claude Code in the localstack-demo folder and select a model. Ask it to deploy the app, add sample data, and save the working state:

Working through the LocalStack MCP server:

1. Make sure LocalStack is running.
2. Deploy this CDK app to LocalStack using the deployer tool. The project
   directory is ./cdk.
3. Once it's up, send a dozen sample requests to the POST /requests endpoint so
   the appRequests table has some data, and tell me the item count.
4. Save the current state as a Cloud Pod named demo-baseline so I can restore it
   later.

The agent deployed the stack, added the sample data, and saved the Cloud Pod:

 localstack-deployer(action: "deploy", directory: "./cdk", projectType: "cdk")
 🥾 Bootstrapping CDK for LocalStack... bootstrapped
     🚀 Deploying CDK Stack...
     LocalstackDemoStack | 36/36 | CREATE_COMPLETE   (15.1s)
     ApiEndpoint = https://irwtzymlvm.execute-api.localhost.localstack.cloud:4566/local/

 localstack-aws-client(command: "dynamodb scan --table-name appRequests --select COUNT")
 { "Count": 12 }

 localstack-cloud-pods(action: "save", pod_name: "demo-baseline")
 Cloud Pod 'demo-baseline' was saved successfully.

The deployer bootstrapped CDK and created all 36 resources in one call. The working state is now safely stored on the LocalStack platform.

Step 4: Make a change that breaks it

Now make a risky change: rename the DynamoDB table. It takes only one line in the CDK stack:

- tableName: 'appRequests',
+ tableName: 'requests',

The change looks harmless. Ask the agent to deploy it, check the data, and roll back if needed:

I renamed the DynamoDB table from appRequests to requests. Deploy the change,
then confirm that GET /requests still returns 12 records. If the app breaks or
loses data, restore the demo-baseline Cloud Pod and explain what happened.

The deploy succeeded, but the API returned no records. The agent detected the data loss and restored the Cloud Pod:

 localstack-deployer(action: "deploy", directory: "./cdk", projectType: "cdk")
 LocalstackDemoStack | UPDATE_COMPLETE
     AWS::DynamoDB::Table AppRequests: created 'requests', then DELETE_COMPLETE on the old table
 deployed successfully

 Bash(curl .../local/requests)
 { "result": [] }                            # was 12, now empty

 localstack-aws-client(command: "dynamodb list-tables")
 { "TableNames": ["requests"] }              # appRequests is gone

 localstack-cloud-pods(action: "load", pod_name: "demo-baseline")
 Cloud Pod 'demo-baseline' was loaded.
     Your LocalStack instance has been restored to this snapshot.

 localstack-aws-client(command: "dynamodb scan --table-name appRequests --select COUNT")
 { "Count": 12 }                             # data is back

CloudFormation cannot rename a resource in place because tableName is a physical name. Instead, it created the new requests table, connected the Lambda functions to it, and deleted the old appRequests table. Because the old table used removalPolicy: DESTROY, it also deleted its 12 records.

The successful deploy and 200 response did not reveal the data loss. The record count did. Loading the demo-baseline Cloud Pod restored the table, its data, the CloudFormation stack, and the API at the original endpoint.

ℹ️
Loading a Cloud Pod replaces the current state. It restores only what existed when you saved the snapshot, so it loses any later changes.

Step 5: Fix the code and save a new checkpoint

The environment works again, but the code still contains the bad change. Revert the rename and set the table's removal policy to RETAIN. This protects the old table if a future update replaces it.

- tableName: 'requests',
+ tableName: 'appRequests',
- removalPolicy: cdk.RemovalPolicy.DESTROY,
+ removalPolicy: cdk.RemovalPolicy.RETAIN,

Ask the agent to deploy the fix, check the data, and save a new checkpoint:

I reverted the rename and set the table's removalPolicy to RETAIN. Deploy the fix,
confirm all 12 records are still served, then save a new Cloud Pod called
demo-hardened.
 localstack-deployer(action: "deploy", directory: "./cdk", projectType: "cdk")
 LocalstackDemoStack | UPDATE_COMPLETE       # in-place update, no table replacement

 localstack-aws-client(command: "dynamodb scan --table-name appRequests --select COUNT")
 { "Count": 12 }                             # nothing lost

 localstack-aws-client(command: "cloudformation get-template ... AppRequests")
 "DeletionPolicy": "Retain", "UpdateReplacePolicy": "Retain"

 localstack-cloud-pods(action: "save", pod_name: "demo-hardened")
 Cloud Pod 'demo-hardened' was saved successfully.

This deploy updated the stack without replacing the table, so all 12 records remained. The agent also checked the deployed template to confirm the fix.

RemovalPolicy.RETAIN sets both DeletionPolicy and UpdateReplacePolicy to Retain. The second setting protects against this failure: if another update replaces the table, CloudFormation keeps the old one instead of deleting it. The agent saved the fixed environment as demo-hardened alongside demo-baseline.

What it cost

The three prompts used claude-opus-5 and cost about $2.44 total. The initial deployment cost the most.

PromptWhat the agent didCostTime
Deploy and checkpointDeployed 36 resources, added data, and saved demo-baseline$1.173m 25s
Break and recoverDeployed the rename, detected data loss, and restored demo-baseline$0.622m 31s
Fix and checkpointDeployed the fix, checked the data, and saved demo-hardened$0.652m 15s

The rename deleted local data without causing a deployment error. Without the checkpoint, those records would have been lost. Loading the Cloud Pod restored the infrastructure and data together. The agent handled the deployment, found the problem, and completed the rollback.

Conclusion

Cloud Pods are more than a personal undo button. Because they run on the LocalStack platform, your team and CI jobs can load the same working environment without redeploying the stack or re-adding test data. Each save creates a new version, so the shared baseline can grow with your app. You can inspect these versions from the CLI or the LocalStack web app.

The LocalStack MCP server lets an agent manage this safety net. It can deploy a stack, save a checkpoint before a risky change, detect data loss, and restore the full environment. After you fix the problem, it can deploy again and save a new checkpoint.

A successful deployment is not always a safe one. A change can pass CloudFormation and return 200 while deleting data. A restorable checkpoint turns that failure into a quick rollback instead of a rebuild from scratch.

Learn More

Did you enjoy this article?

Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.

Across the AtmosphereDiscussions