4 minutes
Static Website AWS Hosting
I prepared this article after completing the S3 section of the AWS Solution Architect - Associate course. I’m aware of AWS Amplify, which allows you to host a similar site in just a few clicks, but I wanted to set everything up myself for practice.
Prerequisite
This article assumes that you already have a domain purchased or connected to Route53.
S3 Bucket
Let’s create a bucket to host the website. When using a domain, it’s important that the bucket name matches the domain name. For example, if your domain is my-personal-blog.com, the bucket name must also be my-personal-blog.com. When creating the bucket, it is important to uncheck the Block public access option.

Next, we need to enable Static Website Hosting in the bucket’s properties.

The final step in configuring the bucket is to add a Bucket Policy. By unchecking public access in the previous step, we have allowed public access to the objects in the bucket in principle. However, by default, it is denied. We need to explicitly allow it by adding a bucket policy with the following content (replace your-domain with your actual domain):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-domain/*"
}
]
}
This policy allows all users (Principal: "*") to perform the s3:GetObject action (i.e., read/download files) from your bucket.
AWS Deploy User
Deploy Policy
In the next step, we will set up CI/CD using Github Actions, which will update the site after a pull request is merged. In this case, I chose to create a separate policy and attach it directly to the user.
Go to IAM -> Policies and click “Create policy”. Give it a name of your choice, add the following permissions, and don’t forget to replace your-domain:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::your-domain.com",
"arn:aws:s3:::your-domain.com/*"
]
}
]
}
This policy grants the necessary permissions to deploy the site: s3:ListBucket to view the bucket’s contents, and s3:GetObject, s3:PutObject, and s3:DeleteObject to manage files (read, add/update, and delete).
User
Next, create a user in IAM -> Users, select “Attach policies directly”, find the policy you created earlier, and select it. Once the user is created, go into their details and navigate to the security credentials tab.

Click “Create access key”, and select “Third-party services”. We won’t bother with IAM Roles for now. It is crucial to save the generated ACCESS KEY and SECRET KEY, as it is not possible to retrieve them later.
Github Actions workflow
Repository secrets
First, add the keys obtained in the previous step as repository secrets named AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:

Deployment configuration
Create a new branch and add a file named .github/workflows/deploy.yml with the following content (don’t forget to change s3://your-bucket and the region):
name: Deploy to S3
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.154.0'
extended: true
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- run: hugo --minify
- run: aws s3 sync public/ s3://your-bucket --delete
Create a new Pull Request and merge it. This should trigger the deployment. After that, the files should appear in your S3 bucket. You can go to the bucket properties and click on the Bucket website endpoint to verify that the site opens and looks as expected.
Routing settings
The final step is to configure Route53. Go to Route53 -> Hosting Zones, select your domain, and click “Create record”. For “Value/Route traffic to”, choose “Alias to S3 website endpoint”, then select the region and the S3 website. If everything was done correctly (the region and bucket name are important), the dropdown list will contain the correct endpoint.

That’s it! Your site should now be accessible via the URL. However, there is one issue: the site is available over HTTP without SSL, but that’s a topic for another article.