An AWS Lambda function that listens for GitHub push webhooks and automatically syncs your static site to S3 and invalidates the CloudFront cache.
GitHub push
│
▼
GitHub sends POST /webhook (Lambda Function URL)
│
▼
Lambda verifies HMAC-SHA256 signature
│
▼
Downloads repo tarball via GitHub API
│
▼
Syncs files to S3 (upload new/changed, delete stale)
│
▼
CloudFront invalidation (/*)
No external dependencies — only Python stdlib and boto3 (pre-installed in the Lambda runtime).
- An S3 bucket and CloudFront distribution already set up
- A GitHub personal access token with read access to the repo (
Contents: read)
- Go to the AWS Lambda console → Create function
- Choose Author from scratch
- Set Function name to
site-deploy-agent(or whatever you like) - Set Runtime to
Python 3.12 - Set Architecture to
arm64 - Click Create function
- In the Code tab, open
lambda_function.pyin the inline editor - Replace its contents with the contents of
agent.pyfrom this repo - Click Deploy
- In Runtime settings → Edit, set Handler to
lambda_function.handler
In the Configuration tab:
General configuration → Edit:
- Timeout:
5 min 0 sec - Memory:
512 MB
Environment variables → Edit — add the following:
| Key | Value |
|---|---|
GITHUB_WEBHOOK_SECRET |
A secret you generate (e.g. a random string); use the same value as the webhook Secret in GitHub |
GITHUB_REPO |
Repository in owner/repo format |
GITHUB_TOKEN |
GitHub token with read access to the repo |
DEPLOY_BRANCH |
Branch that triggers a deploy (e.g. main) |
SITE_SUBDIR |
Subdirectory inside the repo to sync (blank = whole repo) |
S3_BUCKET |
S3 bucket name |
S3_PREFIX |
Key prefix inside the bucket (blank = bucket root) |
CLOUDFRONT_DISTRIBUTION_ID |
CloudFront distribution ID |
The function's execution role needs permission to read/write your S3 bucket and invalidate your CloudFront distribution.
In Configuration → Permissions, click the execution role link to open IAM, then attach an inline policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject", "s3:GetObject"],
"Resource": "arn:aws:s3:::YOUR_BUCKET/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR_BUCKET"
},
{
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
}
]
}Replace YOUR_BUCKET, YOUR_ACCOUNT_ID, and YOUR_DISTRIBUTION_ID with your actual values.
- In Configuration → Function URL → Create function URL
- Set Auth type to
NONE - Click Save
Copy the Function URL — it will look like https://<id>.lambda-url.<region>.on.aws/.
Your two endpoints are:
POST https://<id>.lambda-url.<region>.on.aws/webhook— GitHub webhook receiverGET https://<id>.lambda-url.<region>.on.aws/health— health check
- Go to your GitHub repo → Settings → Webhooks → Add webhook
- Set Payload URL to
https://<id>.lambda-url.<region>.on.aws/webhook - Set Content type to
application/json - Set Secret to the same value you set for
GITHUB_WEBHOOK_SECRET(the secret you generated) - Under Which events, choose Just the push event
- Click Add webhook
Push to your configured branch — CloudWatch Logs will show the deploy progress.