Introduction
As a DevOps Engineer, I believe in practicing what I preach. When I decided to start this blog, I wanted to use it as a real-world example of DevOps principles in action. This post documents how I built the very site you’re reading, using Hugo for static site generation and AWS for hosting.
Why This Tech Stack?
Hugo: The Developer’s Choice
- Speed: Builds sites in milliseconds, not minutes
- Simplicity: Write in Markdown, deploy as HTML
- SEO-friendly: Optimized out of the box
- Themes: Professional themes available
- No database: Static = secure and fast
AWS: The DevOps Playground
- Cost-effective: ~$3-5/month for a personal blog
- Global performance: CloudFront CDN
- Scalability: Handles traffic spikes automatically
- Learning opportunity: Hands-on AWS experience
- Infrastructure as Code: Everything versioned and reproducible
Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ GitHub Repo │───▶│ GitHub Actions │───▶│ S3 Bucket │
│ (Hugo Site) │ │ (CI/CD) │ │ (Static Host) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ serloaiza.com │◀───│ Route 53 │◀───│ CloudFront │
│ (Your Site) │ │ (DNS) │ │ (CDN) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Step 1: Hugo Setup
Installation
# macOS
brew install hugo
# Verify installation
hugo version
Create the Site
hugo new site serloaiza-blog
cd serloaiza-blog
Add a Theme
I chose PaperMod for its clean design and DevOps blog-friendly features:
git init
git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
Configuration
Here’s my hugo.toml with multilingual support:
baseURL = 'https://serloaiza.com/'
defaultContentLanguage = 'en'
theme = 'PaperMod'
[languages]
[languages.en]
languageCode = 'en-US'
languageName = 'English'
title = 'SerLoaiza - DevOps Engineer Blog'
weight = 1
[languages.en.params]
author = "Sergio Loaiza"
description = "Personal blog about DevOps, AWS, Terraform, Kubernetes and more"
[languages.en.params.homeInfoParams]
Title = "Hi! I'm Sergio Loaiza 👋"
Content = "DevOps Engineer passionate about automation and best practices."
[languages.es]
languageCode = 'es-ES'
languageName = 'Español'
title = 'SerLoaiza - Blog de Ingeniero DevOps'
weight = 2
[params]
ShowReadingTime = true
ShowShareButtons = true
ShowPostNavLinks = true
ShowBreadCrumbs = true
ShowCodeCopyButtons = true
Step 2: Content Creation
Creating Posts
hugo new posts/my-first-post.md
Content Structure
content/
├── posts/
│ ├── _index.md
│ ├── what-devops-really-means.md
│ └── building-devops-blog-hugo-aws.md
├── about.md
└── es/ # Spanish content
├── posts/
└── about.md
Local Development
hugo server -D
Visit http://localhost:1313 to see your site.
Step 3: AWS Infrastructure with Terraform
Now for the fun part—let’s build the AWS infrastructure using Terraform.
Project Structure
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars
S3 Bucket for Hosting
resource "aws_s3_bucket" "blog" {
bucket = var.domain_name
}
resource "aws_s3_bucket_website_configuration" "blog" {
bucket = aws_s3_bucket.blog.id
index_document {
suffix = "index.html"
}
error_document {
key = "404.html"
}
}
resource "aws_s3_bucket_public_access_block" "blog" {
bucket = aws_s3_bucket.blog.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
CloudFront Distribution
resource "aws_cloudfront_distribution" "blog" {
origin {
domain_name = aws_s3_bucket_website_configuration.blog.website_endpoint
origin_id = "S3-${var.domain_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
enabled = true
default_root_object = "index.html"
aliases = [var.domain_name, "www.${var.domain_name}"]
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.domain_name}"
compress = true
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.blog.certificate_arn
ssl_support_method = "sni-only"
}
}
SSL Certificate
resource "aws_acm_certificate" "blog" {
provider = aws.us_east_1
domain_name = var.domain_name
subject_alternative_names = ["www.${var.domain_name}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
Step 4: CI/CD with GitHub Actions
Workflow Configuration
.github/workflows/deploy.yml:
name: Deploy Hugo Site to AWS
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: |
aws s3 sync ./public/ s3://${{ secrets.S3_BUCKET }} --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
Step 5: Domain Configuration
Route 53 Setup
resource "aws_route53_zone" "blog" {
name = var.domain_name
}
resource "aws_route53_record" "blog" {
zone_id = aws_route53_zone.blog.zone_id
name = var.domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.blog.domain_name
zone_id = aws_cloudfront_distribution.blog.hosted_zone_id
evaluate_target_health = false
}
}
Cost Breakdown
Monthly AWS Costs:
- S3 Storage: ~$0.50 (for a typical blog)
- CloudFront: ~$1-3 (depends on traffic)
- Route 53: $0.50 (hosted zone)
- Data Transfer: ~$0.50-2
Total: ~$2.50-6/month 💰
Performance Benefits
Before (Traditional Hosting):
- Load time: 2-4 seconds
- Global availability: Limited
- SSL: Extra cost
- CDN: Not included
After (AWS + Hugo):
- Load time: 200-500ms ⚡
- Global availability: 216+ edge locations
- SSL: Free with ACM
- CDN: Included with CloudFront
Lessons Learned
What Worked Well:
- Hugo’s speed: Site builds in under 1 second
- AWS reliability: 99.99% uptime
- Cost efficiency: Cheaper than traditional hosting
- Developer experience: Git-based workflow
Challenges:
- Initial setup complexity: Terraform learning curve
- DNS propagation: Takes time for changes
- Cache invalidation: Need to clear CloudFront cache
Next Steps
- Add monitoring: CloudWatch dashboards
- Implement analytics: Google Analytics or AWS analytics
- SEO optimization: Structured data and meta tags
- Performance monitoring: Core Web Vitals tracking
Conclusion
Building this blog was a perfect example of DevOps principles in action:
- Infrastructure as Code: Everything is versioned and reproducible
- Automation: Push to Git = automatic deployment
- Monitoring: CloudWatch alerts for issues
- Cost optimization: Pay only for what you use
- Scalability: Handles traffic spikes automatically
The best part? This entire setup took about 2 hours to implement and costs less than a fancy coffee per month.
Want to build something similar? The code for this blog is available on GitHub. Feel free to fork it and make it your own!
Questions about the setup? Reach out on LinkedIn or GitHub. I’m always happy to help fellow DevOps engineers!