How to Build a Blog with Terraform and AWS
This guide walks you through deploying a serverless blog on AWS using Terraform. You’ll define your infrastructure as code, provision an S3 bucket for static content, CloudFront for content delivery, and a Lambda function for dynamic processing (like contact forms or API calls). By the end, you’ll have a fully automated, scalable blog that costs pennies to run.
What You’ll Need
- An AWS account with administrative access (for creating IAM roles and resources).
- Terraform installed on your local machine (version 1.5 or later).
- AWS CLI installed and configured with credentials (run
aws configure). - Basic familiarity with HTML/CSS for your blog content.
- A text editor (VS Code, Sublime, etc.).
Step-by-Step
- Create a project directory and navigate into it. This will hold all your Terraform configuration files.
- Define your provider. Create a file named
main.tfand add the AWS provider block, specifying your preferred region (e.g.,us-east-1). - Set up S3 for static content. Add an S3 bucket resource in
main.tfwith a globally unique name (e.g.,my-blog-bucket-2025). Configure it for static website hosting with an index document ofindex.htmland an error document of404.html. - Add a CloudFront distribution. Create a CloudFront distribution resource that points to your S3 bucket as the origin. Set the default root object to
index.html. This gives you HTTPS and global edge caching. - Create a Lambda function for dynamic features. Write a simple Lambda function (e.g., in Python or Node.js) that handles a contact form submission and stores messages in a DynamoDB table. Package your code into a ZIP file and reference it in the Terraform configuration.
- Set up IAM roles. Define an IAM role for Lambda with permissions to write to DynamoDB and log to CloudWatch. Attach the appropriate policies.
- Add an API Gateway endpoint. If your Lambda function needs to be publicly accessible (e.g., for form submissions), create an API Gateway REST API with a POST method that triggers your Lambda.
- Configure outputs. In
outputs.tf, print the CloudFront domain name and the S3 bucket website endpoint so you know where your blog is live. - Initialize and apply. Run
terraform initto download providers, thenterraform planto review changes, and finallyterraform applyto create the resources. Typeyeswhen prompted. - Deploy your blog content. Upload your HTML, CSS, and JS files to the S3 bucket using
aws s3 sync ./public s3://your-bucket-name. Wait a few minutes for CloudFront to propagate, then visit your CloudFront URL.
Common Mistakes
- Using a non-unique S3 bucket name — S3 bucket names are global, so you’ll get an error if the name is taken.
- Forgetting to set the origin access control — Your S3 bucket should be private, and CloudFront should access it via an Origin Access Control (OAC) to avoid public access.
- Not configuring the default root object — Without it, visiting the root URL returns an access denied error.
- Hardcoding AWS credentials in Terraform — Use environment variables or IAM roles instead.
- Ignoring the Lambda execution role — If the role lacks DynamoDB permissions, the function will fail silently.
Tips
- Use Terraform modules — Break your configuration into reusable modules for S3, CloudFront, and Lambda to keep it organized.
- Version your state file — Store Terraform state in an S3 bucket with DynamoDB locking to enable team collaboration and prevent corruption.
- Automate content deployment — Use a CI/CD pipeline (like GitHub Actions) to run
terraform applyandaws s3 syncon every push. - Enable CloudFront logging — This helps you debug traffic issues and understand your audience.
- Set up cost alerts — Even though the blog is cheap, it’s good practice to monitor usage.
Troubleshooting
- Access denied when visiting the CloudFront URL — Check that the S3 bucket policy allows CloudFront to read objects. Ensure the OAC is correctly attached.
- Changes not appearing after updating S3 — CloudFront caches content; invalidate the cache by running
aws cloudfront create-invalidation --distribution-id YOUR_ID --paths "/*". - Terraform apply fails with “BucketAlreadyExists” — Change the bucket name to something unique.
- Lambda function times out — Increase the timeout in your Terraform configuration (e.g., to 10 seconds). Also check your function’s memory allocation.
- API Gateway returns 500 — Look at CloudWatch logs for the Lambda function to see the error stack trace.
Frequently Asked Questions
- Can I use a custom domain with this setup? Yes, create a Route 53 hosted zone and add an A record that points to your CloudFront distribution. Use an ACM certificate for HTTPS.
- How much does this cost? Typically under $1 per month for low traffic. S3 and CloudFront have free tiers, and Lambda charges only when invoked.
- Can I use a database like MySQL instead of DynamoDB? Yes, but you’d need to run RDS, which costs more. DynamoDB is serverless and integrates well with Lambda.
- How do I update my blog content? Simply overwrite the files in S3, then invalidate the CloudFront cache. This can be automated with a script or CI/CD.
- Is this setup secure? Yes, as long as you keep the S3 bucket private and use Origin Access Control. Also, use HTTPS via CloudFront’s default certificate or your own ACM certificate.
- Can I use a static site generator like Hugo or Jekyll? Absolutely. Build your site locally, then upload the generated HTML files to S3.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.