Sandboxing Claude Code in Docker with read-only AWS credentials
I needed Claude Code to diagnose SQS/Lambda incidents in production — check queue depths, read CloudWatch logs, inspect Lambda configs — but I didn’t want it anywhere near write access. Not even accidentally.
The solution: run Claude Code in a Docker container with nothing but temporary STS credentials for a read-only IAM role. No ~/.aws mount, no profiles, no source credentials. The host assumes the role, extracts the temp creds, and passes only those into the container as env vars.
# Host resolves creds — container never sees your AWS config
creds=$(aws sts assume-role \
--role-arn arn:aws:iam::ACCOUNT:role/readonly-role \
--role-session-name investigator \
--profile my-profile)
export AWS_ACCESS_KEY_ID=$(echo "$creds" | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo "$creds" | jq -r '.Credentials.SessionToken')
docker compose run --rm investigator "why is the DLQ growing"
Three layers of defense, because one is never enough:
1. IAM role — only allows sqs:GetQueueAttributes, lambda:GetFunction*, logs:FilterLogEvents, cloudwatch:GetMetricStatistics, etc. No write actions at all.
2. Claude Code settings.json — a deny list that blocks mutating AWS commands, file deletion, git push, deploys, and more:
{
"permissions": {
"deny": [
"Bash(aws sqs purge-*)",
"Bash(aws sqs delete-*)",
"Bash(aws lambda update-*)",
"Bash(aws lambda invoke*)",
"Bash(rm *)",
"Bash(git push*)"
]
}
}
3. No ~/.aws mount — the container literally can’t escalate to a more powerful profile because it has no config files. Just three env vars that expire in an hour.
What could go wrong without this
Without sandboxing, Claude Code running with your regular AWS profile could:
- Purge a production SQS queue while “investigating” it — all messages gone, no undo
- Invoke a Lambda to “test” a theory — triggering side effects in prod
- Redrive a DLQ before you understand the root cause — reprocessing broken messages that corrupt data
- Delete CloudWatch log groups while cleaning up — destroying the evidence you need
- Read
~/.aws/credentialsand see your admin keys — even if it doesn’t use them, they’re exposed in the conversation context - Push a “fix” to your repo if it has git access — untested code in prod
The read-only IAM role prevents most of this, but the settings.json deny list catches the things IAM can’t — like rm -rf on cloned repos or git push to your codebase. And not mounting ~/.aws means even if something goes sideways, the blast radius is a throwaway STS session that expires in an hour.
Gotchas I hit along the way
--dangerously-skip-permissionswon’t run as root — Claude Code blocks it. Add a non-root user in your Dockerfile.- Onboarding prompts (theme, API key, workspace trust) persist in
~/.claude.json, not~/.claude/settings.json. Pre-populate it in the Dockerfile. - ARM Macs need the aarch64 AWS CLI binary — the x86_64 one won’t run under Docker’s qemu emulation.
- Set
HOMEdifferent fromWORKDIR— Claude Code has both project-level settings (in.claude/settings.jsonrelative to workdir) and user-level settings (in$HOME/.claude.json). If they’re the same directory, they collide.