You’re going to create a deploy bot—an IAM user whose sole purpose is to deploy your frontend. This user can sync files to a specific S3 bucket and invalidate a specific CloudFront distribution’s cache. It can’t do anything else. No reading DynamoDB tables, no creating Lambda functions, no changing IAM permissions.
This is the same deploy bot you’ll wire into a GitHub Actions pipeline later in the course. I want you to build it now so you understand exactly what permissions it has and why.
If you want the AWS version of the policy mechanics while you work, keep the IAM JSON policy reference and the aws iam create-policy command reference open.
Why It Matters
Every CI/CD pipeline needs credentials. On Vercel, the platform handles this for you—you never think about what permissions the deploy process has. On AWS, you control those permissions explicitly. And the permissions you choose determine the blast radius if those credentials leak.
A deploy bot with AdministratorAccess can delete your database. A deploy bot with a scoped policy can, at worst, overwrite your static files. The difference is a bad day versus a catastrophic one.
Your Task
Create an IAM user named deploy-bot with:
- No console access—this user exists purely for CLI/API use.
- Access keys for programmatic access.
- A custom IAM policy that allows exactly these operations and nothing more:
- Sync files to the
my-frontend-app-assetsS3 bucket (upload, delete, and list) - Create cache invalidations on the CloudFront distribution with ID
E1A2B3C4D5E6F7
- Sync files to the
Use the account ID 123456789012 and region us-east-1 for all ARNs.
Step-by-Step
Create the IAM User
In the AWS Console, navigate to IAM > Users > Create user.
- Set the username to
deploy-bot. - Do not enable console access. This user will only authenticate via access keys.
- Skip the permissions step for now—you’ll attach a policy after creating it.
- Click through to create the user.
Alternatively, use the CLI:
aws iam create-user \
--user-name deploy-bot \
--region us-east-1 \
--output jsonCreate Access Keys for the User
Navigate to the deploy-bot user’s Security credentials tab and create an access key. Choose Other as the use case.
Or via CLI:
aws iam create-access-key \
--user-name deploy-bot \
--region us-east-1 \
--output jsonSave the Access Key ID and Secret Access Key somewhere secure. You’ll need them later when configuring the deploy pipeline.
Write the Policy
Create a file called deploy-bot-policy.json with a policy that:
- Allows
s3:PutObjectands3:DeleteObjecton objects in themy-frontend-app-assetsbucket. - Allows
s3:ListBucketon themy-frontend-app-assetsbucket itself. - Allows
cloudfront:CreateInvalidationon the distributionE1A2B3C4D5E6F7.
Remember:
s3:PutObjectands3:DeleteObjectoperate on objects, so the resource ARN needs/*at the end.s3:ListBucketoperates on the bucket, so the resource ARN doesn’t have/*.- CloudFront distribution ARNs have no region (use an empty region segment) and follow the pattern
arn:aws:cloudfront::<account-id>:distribution/<distribution-id>. - Every statement needs
Effect,Action, andResource. - The policy needs
"Version": "2012-10-17".
Create the Policy in IAM
aws iam create-policy \
--policy-name DeployBotPolicy \
--policy-document file://deploy-bot-policy.json \
--region us-east-1 \
--output jsonNote the policy ARN from the output—you’ll need it for the next step.
Attach the Policy to the User
aws iam attach-user-policy \
--user-name deploy-bot \
--policy-arn arn:aws:iam::123456789012:policy/DeployBotPolicy \
--region us-east-1 \
--output jsonVerify the Setup
Configure a named profile for the deploy bot:
aws configure --profile deploy-botEnter the access key ID and secret access key you saved earlier, us-east-1 as the region, and json as the output format.
Verify the identity:
aws sts get-caller-identity \
--profile deploy-bot \
--region us-east-1 \
--output jsonYou should see the deploy-bot user’s ARN in the response.
Checkpoints
-
deploy-botIAM user exists with no console access - Access keys are created and stored securely
-
DeployBotPolicyis a customer-managed policy in your account - The policy allows exactly four actions:
s3:PutObject,s3:DeleteObject,s3:ListBucket, andcloudfront:CreateInvalidation - The S3 actions are scoped to
my-frontend-app-assets(bucket and objects, with correct ARN patterns) - The CloudFront action is scoped to distribution
E1A2B3C4D5E6F7 - The policy is attached to the
deploy-botuser -
aws sts get-caller-identity --profile deploy-botreturns the correct identity
Failure Diagnosis
aws sts get-caller-identityfails withInvalidClientTokenId: The named profile is using the wrong access key pair, or the keys were copied incorrectly when you created them.- Later deploy commands fail with
AccessDeniedon S3: Double-check that object actions usearn:aws:s3:::my-frontend-app-assets/*whiles3:ListBucketuses the bucket ARN without/*. - CloudFront invalidation fails even though S3 sync works: The distribution ARN is wrong. CloudFront ARNs omit the region segment and must point at
E1A2B3C4D5E6F7in account123456789012.
Stretch Goals
Test the boundaries. Try running a command the deploy bot shouldn’t have access to—like
aws iam list-users --profile deploy-bot --region us-east-1 --output json. Confirm that you get anAccessDeniederror. This is the policy doing its job.Add a Deny statement. Add an explicit Deny statement that prevents the deploy bot from deleting the bucket itself (
s3:DeleteBucket). Technically the bot already can’t do this (the action isn’t in the Allow statements), but an explicit Deny acts as a guardrail even if someone later expands the Allow statements.Use Sids. If you didn’t include
Sidfields in your policy statements, add descriptive ones. When you revisit this policy in three months,"Sid": "AllowS3DeploySync"is worth more than staring at a list of actions trying to remember what they’re for.
When you’re ready, check your work against the Solution: IAM Policy for a Deploy Bot.