An LLM built you a website. Now what?
You have a folder of HTML, CSS, and JavaScript that an LLM wrote for you, and a domain name. This guide puts that folder on your domain with HTTPS, and redeploys it every time you push to GitHub. It runs on AWS with no servers and no database, for about $0.50 a month at hobby traffic.
Not on GitHub yet? Start with Ship your site from Claude Code, which gets your files there and sets up the routine this guide deploys from.
What you end up with
- Your files in a private Amazon S3 bucket, served worldwide by Amazon CloudFront with a free HTTPS certificate.
- DNS for your domain in Amazon Route 53.
- Infrastructure written as code with Pulumi (Python), so nothing is clicked together in the AWS console.
- Deploys from GitHub Actions on every push to
main. Each deploy has a log, and no AWS keys are stored anywhere: GitHub signs in to AWS with short-lived OpenID Connect (OIDC) tokens. - Clean URLs (
/about/), awwwredirect, a real 404 page, and security headers.
Hand it to your LLM
The code is open source in
athinktank/skunkyard,
under aws-static-site/. Its README is written for an LLM to follow. Open
your site's repository in an AI coding tool, such as Claude Code, and give it a prompt
like this:
Read https://github.com/athinktank/skunkyard/tree/main/aws-static-site
and set up this repository to deploy to AWS the same way.
My domain is example.com and my GitHub repository is me/my-site.
My static files are in the site/ folder.
Walk me through each step that needs my AWS or GitHub credentials,
and don't run AWS commands yourself.
The LLM copies the infrastructure code and workflow into your repository and fills in your domain. The steps that need your credentials stay with you.
The steps
These are the steps your LLM walks you through. You need an AWS account, a GitHub repository, the AWS CLI, and the GitHub CLI.
1. Run the bootstrap once
GitHub can't sign in to AWS until a role exists for it, so one CloudFormation template creates the role, the Route 53 hosted zone, and a bucket for Pulumi's state. Run it with credentials that can create IAM roles.
First look up the subject GitHub puts in its sign-in tokens for your repository:
gh api repos/OWNER/REPO/actions/oidc/customization/sub
If use_immutable_subject is true, then use the
sub_claim_prefix value without the leading repo:, such as
me@1234/my-site@5678. Otherwise use OWNER/REPO.
aws cloudformation deploy --region us-east-1 \
--stack-name my-site-bootstrap \
--template-file infra/bootstrap.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides DomainName=example.com GitHubRepo=me@1234/my-site@5678
2. Point your domain at Route 53
Read the stack's outputs, then set the four NameServers at your
registrar.
aws cloudformation describe-stacks --region us-east-1 \
--stack-name my-site-bootstrap --query "Stacks[0].Outputs"
Confirm the registry has them. Ask the .com servers directly, because
your own network can cache the old answer for hours:
dig @a.gtld-servers.net example.com NS +norec
3. Give GitHub the outputs
gh variable set AWS_DEPLOY_ROLE_ARN --body "<DeployRoleArn>"
gh variable set PULUMI_STATE_BUCKET --body "<StateBucket>"
gh secret set PULUMI_CONFIG_PASSPHRASE
The passphrase encrypts secrets in Pulumi's state. Keep a copy in your password manager.
4. Push to main
The deploy workflow runs pulumi up, uploads your files, and clears the
CloudFront cache. The first run takes about 5 minutes while CloudFront sets up.
Every push to main after that redeploys.
Problems we hit, so you don't
- "Not authorized to perform sts:AssumeRoleWithWebIdentity"
- GitHub's token subject didn't match the role. New repositories can use an immutable subject with account and repository IDs. Step 1 shows how to look it up.
- CloudFormation says "No changes to deploy"
aws cloudformation deploykeeps a parameter's earlier value unless you pass a new one with--parameter-overrides. Changing the template's default does nothing.- The site works everywhere except your house
- Your router cached "no such record" from before the deploy. It expires on its own. Flushing your computer's DNS cache doesn't help, because the router asks again for you and serves the cached answer.
- Your email stopped
- Moving name servers drops every record at your old DNS host, including mail records. Copy them to Route 53 first, or publish a null MX record if the domain has no mail.
What it costs
Prices from AWS's pricing pages, checked November 1, 2026, for US East. They leave out the 12-month free tier new AWS accounts get. They include CloudFront's always-free tier, which doesn't expire: 1 TB of transfer, 10 million requests, and 2 million CloudFront Function runs a month.
| Item | Price |
|---|---|
| Domain name | Your registrar's yearly fee |
| Route 53 hosted zone | $0.50 a month |
| Route 53 lookups | Free for the site's records, which are aliases to CloudFront; $0.40 per million for others |
| S3 storage | $0.023 per GB a month; $0.005 per 1,000 uploads |
| CloudFront transfer | Free to 1 TB a month, then $0.085 per GB |
| CloudFront requests | Free to 10 million a month, then $0.01 per 10,000 |
| CloudFront Function (clean URLs) | Free to 2 million runs a month, then $0.10 per million |
| Cache clears after a deploy | Free to 1,000 paths a month; a deploy uses 1 |
| HTTPS certificate | Free |
| Pulumi | Free; its state is a file in your S3 bucket |
| GitHub Actions | Free on public repositories; 2,000 minutes a month on private ones, and a deploy takes 1 to 5 minutes |
Monthly totals, assuming each page view loads 5 files and 200 KB:
| Traffic | Requests | Transfer | Monthly cost |
|---|---|---|---|
| 10,000 page views | 50,000 | 2 GB | $0.50 |
| 500,000 page views | 2.5 million | 100 GB | $0.55 |
| 5 million page views | 25 million | 1 TB | $17.80 |
The CloudFront Function runs on every request, so its 2 million free runs are the first limit you pass. Past 1 TB a month, transfer becomes the largest cost.