Claude Code and AWS S3: sync, image uploads, backups, and presigned URLs with least privilege
Claude Code and AWS S3 guide: IAM least privilege, aws s3 sync, static sites, uploads, backups, and presigned URLs.
S3 looks simple because it stores files, but real applications need more than a place to drop objects. You need a clear boundary between public assets, private documents, backups, generated files, and temporary downloads. Claude Code can help you build that wiring quickly, but it can also generate dangerously broad IAM policies if the prompt is vague.
This guide is for beginners who want to connect Claude Code with AWS S3 without turning the bucket into a security problem. We will use S3 for four practical use cases: static site deployment, image uploads, backups, and presigned URLs. The thread running through all of them is least privilege: give every script only the prefix and action it needs.
The command examples follow the official AWS CLI S3 reference, especially aws s3 sync. For presigned URLs, the Boto3 presigned URL guide is useful even if your application code is TypeScript, because it explains the expiration model clearly. For the agent workflow, I keep the request style close to Claude Code common workflows.
If you need the IAM background first, read the Claude Code AWS IAM guide. For the security review checklist, use Claude Code security best practices.
Separate the S3 use cases before writing code
The biggest beginner mistake is asking Claude Code for an S3 integration as one large task. S3 integrations are not one thing. A static site deployment needs repeatable sync. An image upload flow needs safe object names and cache headers. A backup flow needs append-only history. A private report flow needs a short-lived presigned URL. Those flows should not share the same permissions.
A practical prefix design might be site/, assets/images/, uploads/, backups/, and assets/private-reports/. You can use one bucket at first, but the prefixes let you write narrow IAM policies and easier review notes. When Claude Code sees those prefixes in the prompt, it has a much better chance of generating a safe helper instead of s3:*.
Use case one is static hosting behind CloudFront. The build output goes to site/, and only the deploy role writes there. Use case two is image management. The admin tool uploads to uploads/ or assets/images/, and the site reads through CloudFront. Use case three is backup. Backups are date-prefixed and should never use —delete. Use case four is private delivery. The bucket stays private, and the app returns a presigned URL that expires quickly.
Start with identity checks and least-privilege IAM
Put the bucket and region in variables first so both you and Claude Code can review the target.
export AWS_REGION=ap-northeast-1
export S3_BUCKET=claudecode-lab-assets-prod
aws sts get-caller-identity
aws s3 ls "s3://${S3_BUCKET}/" --region "${AWS_REGION}"
This policy reads only under assets, writes only under uploads, and avoids broad delete permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadPublicAssetsOnly",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::claudecode-lab-assets-prod/assets/*"
},
{
"Sid": "WriteUploadsOnly",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:AbortMultipartUpload"],
"Resource": "arn:aws:s3:::claudecode-lab-assets-prod/uploads/*"
},
{
"Sid": "ListLimitedPrefixes",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::claudecode-lab-assets-prod",
"Condition": {
"StringLike": {
"s3:prefix": ["assets/*", "uploads/*", "backups/*"]
}
}
}
]
}
This is the basic shape for static sites or image folders. Always start with dryrun.
# 1. Preview changes first. This should become a habit.
aws s3 sync ./dist "s3://${S3_BUCKET}/site/" \
--region "${AWS_REGION}" \
--delete \
--cache-control "public,max-age=300" \
--dryrun
# 2. Deploy only after the preview looks right.
aws s3 sync ./dist "s3://${S3_BUCKET}/site/" \
--region "${AWS_REGION}" \
--delete \
--cache-control "public,max-age=300"
# 3. Upload long-lived images with a different cache policy.
aws s3 sync ./public/images "s3://${S3_BUCKET}/assets/images/" \
--region "${AWS_REGION}" \
--exclude "*.psd" \
--cache-control "public,max-age=31536000,immutable"
A presigned URL lets you keep the bucket private while allowing temporary download access.
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: process.env.AWS_REGION ?? "ap-northeast-1" });
export async function createDownloadUrl(key: string, filename: string) {
if (!key.startsWith("assets/private-reports/")) {
throw new Error(`Unexpected S3 key prefix: ${key}`);
}
const command = new GetObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: key,
ResponseContentDisposition: `attachment; filename="${filename}"`,
});
return getSignedUrl(s3, command, { expiresIn: 900 });
}
When you ask Claude Code, include the use case, forbidden actions, and verification commands.
このリポジトリに AWS S3 連携を追加してください。
目的: public/images を S3 の assets/images/ に同期し、private-reports/ のPDFだけ署名付きURLで配布する。
制約: バケット全体公開は禁止。s3:DeleteObject は付けない。aws s3 sync は必ず --dryrun を先に出す。
成果物: scripts/s3-sync-assets.mjs、lib/s3-presigned-url.ts、READMEの手順、確認コマンド。
確認: npm test、aws s3 ls、aws s3 sync --dryrun の出力で説明してください。
参照: AWS CLI s3/sync docs と Anthropic Claude Code common workflows。
The IAM policy is intentionally boring. That is a feature. It has GetObject for one prefix, PutObject for another prefix, and ListBucket constrained by prefix. It does not include s3:* and it does not include DeleteObject. If deletion is required later, put it in a separate role or a script with explicit confirmation.
The most common pitfall is granting ListBucket on an object ARN. ListBucket belongs to the bucket ARN, while GetObject and PutObject belong to object ARNs. Claude Code usually gets this right when the prompt mentions both the bucket and object resources, but you should still review it. The second pitfall is forgetting AbortMultipartUpload when large uploads can fail halfway through.
Use aws s3 sync carefully
aws s3 sync is the workhorse command. It compares a local path and an S3 prefix, then copies what changed. With —delete, it also removes remote files that no longer exist locally. That behavior is perfect for deterministic static deployments and dangerous for backups or shared media folders.
My default pattern is dryrun first, deploy second. Claude Code should produce a script that prints the target bucket, region, prefix, and dryrun output before any real write. For production, I also like a guard that stops when the dryrun contains too many deletes. It is not fancy, but it prevents the exact failure mode that hurts: a wrong local folder plus —delete.
# Backup use case: append-only, no --delete.
BACKUP_DATE=$(date +%Y-%m-%d)
aws s3 sync ./backups "s3://${S3_BUCKET}/backups/${BACKUP_DATE}/" \
--region "${AWS_REGION}" \
--storage-class STANDARD_IA \
--exclude "*.tmp"
aws s3 ls "s3://${S3_BUCKET}/backups/${BACKUP_DATE}/" --recursive --summarize
Presigned URLs are temporary permission, not public hosting
A presigned URL lets someone perform an S3 action for a limited time. It is not the same as making a bucket public. This matters for invoices, private PDFs, exported reports, or user-specific downloads. The bucket remains private, but the application can hand out a short-lived link after checking authentication.
In the TypeScript example above, the URL expires after 900 seconds. I also check the key prefix before generating the URL. That small guard prevents a bug where a caller asks for a signed URL to an unexpected object. If you generate upload URLs, also validate the content type and create the S3 key on the server side. Do not trust a raw filename from the browser.
The pitfall is convenience. A one-day presigned URL feels easier during testing, then it quietly ships to production. For private documents, start with fifteen minutes and extend only when the product need is real. Add logs around who requested the URL and which key was signed.
Cost, cache, and public access mistakes
S3 cost is not only storage. You also pay for requests and data transfer. If the files are public website assets, put CloudFront in front of S3 and design cache headers deliberately. Small images with weak cache headers can create a surprising number of requests. Large downloads without CloudFront can make transfer cost harder to reason about.
Another common mistake is confusing S3 static website hosting with a private bucket plus CloudFront. Many older tutorials publish a bucket directly. For modern production work, I prefer keeping S3 private and letting CloudFront access it through a controlled origin setup. That gives you a cleaner story for public access blocks and future security review.
DRYRUN_OUTPUT=$(aws s3 sync ./dist "s3://${S3_BUCKET}/site/" --delete --dryrun)
echo "$DRYRUN_OUTPUT"
DELETE_COUNT=$(echo "$DRYRUN_OUTPUT" | grep -c "delete:" || true)
if [ "$DELETE_COUNT" -gt 20 ]; then
echo "Too many deletes: ${DELETE_COUNT}. Stop and review."
exit 1
fi
What Claude Code should own, and what humans should review
Claude Code is good at generating the repetitive parts: CLI scripts, TypeScript helpers, README steps, environment variable checks, and error messages. It is also useful for turning AWS documentation into a project-specific runbook. The human review should focus on account identity, bucket name, prefix, delete permissions, public access, and whether the generated command matches the intended environment.
For longer sessions, keep the S3 operating rules in your project memory. The approach in Claude Code context management works well: record that production S3 sync must dryrun first and DeleteObject is not allowed unless a separate approval step exists.
Masa verification note
The most useful part of this pattern in my own testing was prefix separation. Once site, assets, uploads, backups, and private reports were separated, the IAM review became much easier. Claude Code also became more reliable because the prompt had concrete boundaries. The weakest version of the workflow was the vague one: one bucket, one broad role, and one sync script with —delete everywhere.
The second useful habit was asking Claude Code to write the verification steps, not just the code. A script that works once is nice. A script that prints the account, bucket, dryrun diff, and final ls summary is much easier to trust.
Summary
Claude Code and AWS S3 are a strong combination when you keep the permissions boring. Separate use cases by prefix, use aws s3 sync with dryrun, avoid broad delete rights, keep buckets private by default, and issue presigned URLs with short expirations. The result is not only working automation, but automation you can review without holding your breath.
If your team wants this pattern adapted to a real repository, the Claude Code training and consultation can cover S3, IAM, deployment scripts, and review checklists together.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.