Skip to content

How I host a static MkDocs site on AWS

And what I learn on the way about architecture, security, and cost control

I tried Amazon Lightsail and WordPress first — the cost was too high. I wanted something simple: a light, static site that I could build myself. The idea came from the FastAPI documentation, which is built with MkDocs and hosted fully on AWS.

But this is not only about publishing a site. It is also a way to learn. To understand the cloud from the inside — through practice, decisions, mistakes, and fixes.


What I chose, and why

Component Description
MkDocs A static site generator — fast, light, great for a blog
AWS S3 Stores the generated HTML, CSS, and JS files
Amazon CloudFront Delivers the site worldwide, handles HTTPS and caching
AWS Route 53 DNS for my domain, andrzejoblong.pl
ACM (SSL) SSL certificate for HTTPS security
AWS WAF Protection against abuse and bots
AWS Budgets Cost alerts, so the bill never surprises me

How I secured it

S3 bucket

  • Public access to S3: BLOCKED!
  • A bucket policy with the condition AWS:SourceArn == CloudFront
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<BUCKET_ID>/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "<CLOUDFRONT_DISTRIBUTION_ARN>"
                }
            }
        }
    ]
}

CloudFront

  • OAC (Origin Access Control): only CloudFront can read the files
  • Object compression
  • Redirect from HTTP to HTTPS
  • Only GET and HEAD methods are allowed for my site
  • Cache key and origin request policies: a) CachingOptimized b) CORS-S3Origin
  • TTL of 24 hours in the cache policy
  • SSL certificate (ACM) plus the domain in Route 53
  • Simpler navigation thanks to a CloudFront Function, which automatically adds "index.html" to URLs that have no file extension:
function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    }
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

Thanks to this function, I can write links like /about, /docs, /blog/, and CloudFront finds the right files in S3 — with no errors.

  • WAF with a Rate-based rule

Estimated monthly cost

Service Cost (USD) Notes
S3 (storage) 0.01–0.10 Static files
S3 (GET requests) 0.01–0.50 Depends on the number of visits
CloudFront 1–3 Transfer plus cache
Route 53 0.50 DNS and domain
ACM SSL 0.00 Free
WAF 5 + 1 USD/rule/month
TOTAL ~8 USD For a light site

What I learn from this project

  • Designing CDN, cache, and access control together
  • Managing costs consciously
  • Separating infrastructure from content
  • Building real cloud architecture awareness

My personal checklist

  • S3: no public access
  • CloudFront: active cache, SSL
  • OAC: only CloudFront can reach S3
  • Route 53: domain set up
  • WAF: rate limit, bot protection
  • Budgets: cost alert at 5 USD

Areas to improve

Deployment automation

mkdocs build

The generated files go into site/.

Upload the site to S3:

aws s3 sync site/ s3://<BUCKET_ID>/ --delete

Invalidate the CloudFront cache:

aws cloudfront create-invalidation \
  --distribution-id <MY_ID> \
  --paths "/index.html" "/"
  • A script to automate this
  • Deploy automation with GitHub Actions
  • Terraform to manage the infrastructure

Summary

I am not building the cheapest setup. I am building it consciously — for myself. To learn, to test, to grow. This is a project where data and decisions meet responsibility.

Sources

Amazon CloudFront

Amazon Simple Storage Service

Amazon Route 53

AWS Certificate Manager

AWS Budgets

AWS WAF