DEV Community

Cover image for Deploy Hugo Site to AWS S3 with AWS CLI
Rost
Rost

Posted on • Originally published at glukhov.org

Deploy Hugo Site to AWS S3 with AWS CLI

Deploying a Hugo static site to AWS S3 using the AWS CLI provides a robust, scalable solution for hosting your website. This guide covers the complete deployment process, from initial setup to advanced automation and cache management strategies.

Deployment with AWS CLI is becoming g-to approach since hugo distribution removed deploy command from standard package.
See about extended and withdeploy in How to Install Ubuntu 24.04 & useful tools if doing this locally and still wanting to call hugo deploy.

Prerequisites

Before deploying your Hugo site to AWS S3, ensure you have:

  • A Hugo site ready for deployment (if you need help creating one, check the Hugo quickstart guide)
  • AWS account with appropriate permissions
  • AWS CLI installed and configured
  • Basic knowledge of command-line operations

Building Your Hugo Site

The first step in deploying your Hugo site is generating the static files. Use the Hugo Cheat Sheet for reference on available commands and options.

Build your site with optimization flags:

hugo --gc --minify
Enter fullscreen mode Exit fullscreen mode

The --gc flag removes unused cache files, while --minify compresses your HTML, CSS, and JavaScript for optimal performance. This generates your static site in the public/ directory, which is what we'll deploy to S3.

Configuring AWS CLI

If you haven't already configured the AWS CLI, run:

aws configure
Enter fullscreen mode Exit fullscreen mode

You'll be prompted to enter:

  • AWS Access Key ID: Your AWS access key
  • AWS Secret Access Key: Your secret access key
  • Default region: Your preferred AWS region (e.g., us-east-1, ap-southeast-2)
  • Default output format: json (recommended)

For programmatic access, ensure your IAM user has permissions for:

  • s3:PutObject
  • s3:GetObject
  • s3:DeleteObject
  • s3:ListBucket
  • cloudfront:CreateInvalidation (if using CloudFront)

Creating and Configuring the S3 Bucket

Create the Bucket

Create your S3 bucket using the AWS CLI:

aws s3 mb s3://your-bucket-name --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Important: Choose a globally unique bucket name. If you plan to use the bucket for website hosting, the bucket name must match your domain name if you're using a custom domain.

Configure Bucket for Static Website Hosting

Enable static website hosting:

aws s3 website s3://your-bucket-name \
  --index-document index.html \
  --error-document 404.html
Enter fullscreen mode Exit fullscreen mode

Set Bucket Policy

For public read access (if not using CloudFront), create a bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Apply the policy:

aws s3api put-bucket-policy \
  --bucket your-bucket-name \
  --policy file://bucket-policy.json
Enter fullscreen mode Exit fullscreen mode

Security Note: If you're using CloudFront (recommended), you can restrict S3 bucket access to CloudFront only, eliminating the need for public read access.

Deploying to S3 Using AWS CLI

The core deployment command uses aws s3 sync to upload your site:

aws s3 sync public/ s3://your-bucket-name/ \
  --delete \
  --cache-control max-age=60
Enter fullscreen mode Exit fullscreen mode

Key Parameters Explained

  • public/: Your local Hugo output directory
  • s3://your-bucket-name/: Your S3 bucket destination
  • --delete: Removes files in S3 that don't exist locally, ensuring your bucket mirrors your build
  • --cache-control max-age=60: Sets cache headers (60 seconds in this example)

Advanced Sync Options

For more control over your deployment:

aws s3 sync public/ s3://your-bucket-name/ \
  --delete \
  --cache-control "public, max-age=31536000, immutable" \
  --exclude "*.html" \
  --cache-control "public, max-age=60" \
  --include "*.html" \
  --exclude "*.js" \
  --cache-control "public, max-age=31536000, immutable" \
  --include "*.js"
Enter fullscreen mode Exit fullscreen mode

This example sets different cache durations for HTML files (60 seconds) versus static assets like JavaScript (1 year), which is a common optimization strategy.

Setting Up CloudFront Distribution

While S3 can host static websites directly, using Amazon CloudFront as a CDN provides better performance, security, and global distribution.

Create CloudFront Distribution

aws cloudfront create-distribution \
  --distribution-config file://cloudfront-config.json
Enter fullscreen mode Exit fullscreen mode

A basic CloudFront configuration includes:

  • S3 bucket as origin
  • Default cache behaviors
  • SSL/TLS certificate (from AWS Certificate Manager)
  • Custom domain configuration (optional)

Cache Management Strategies

When deploying through CloudFront, consider these caching strategies:

  1. Set Maximum TTL: Configure CloudFront's Maximum TTL to control how long content is cached at edge locations
  2. Content Versioning: Use version identifiers in filenames (e.g., style-v2.css) to force cache updates
  3. Cache-Control Headers: Set appropriate headers during S3 sync (as shown above)
  4. Selective Invalidation: Invalidate only changed paths rather than the entire cache

Cache Invalidation

After deploying updates, invalidate the CloudFront cache to serve fresh content:

aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/*"
Enter fullscreen mode Exit fullscreen mode

For more targeted invalidations:

aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/index.html" "/blog/*"
Enter fullscreen mode Exit fullscreen mode

Cost Consideration: The first 1,000 invalidation paths per month are free. After that, each path costs $0.005. Use selective invalidation to minimize costs.

Automation with CI/CD

Manual deployments work for small projects, but automation is essential for production workflows. You can integrate this deployment process with Gitea Actions to deploy Hugo website to AWS S3 or similar CI/CD pipelines.

Basic Deployment Script

Create a simple deployment script:

#!/bin/bash
set -e

# Build Hugo site
echo "Building Hugo site..."
hugo --gc --minify

# Deploy to S3
echo "Deploying to S3..."
aws s3 sync public/ s3://your-bucket-name/ \
  --delete \
  --cache-control max-age=60

# Invalidate CloudFront cache
echo "Invalidating CloudFront cache..."
aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/*"

echo "Deployment complete!"
Enter fullscreen mode Exit fullscreen mode

Make it executable and run:

chmod +x deploy.sh
./deploy.sh
Enter fullscreen mode Exit fullscreen mode

GitHub Actions Example

For GitHub repositories, create .github/workflows/deploy.yml:

name: Deploy to AWS S3

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

      - name: Build
        run: hugo --gc --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://your-bucket-name/ \
            --delete \
            --cache-control max-age=60

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"
Enter fullscreen mode Exit fullscreen mode

Monitoring and Optimization

Enable Logging

Configure S3 and CloudFront logging to track access patterns:

# Enable S3 access logging
aws s3api put-bucket-logging \
  --bucket your-bucket-name \
  --bucket-logging-status file://logging.json
Enter fullscreen mode Exit fullscreen mode

Set Up CloudWatch Alarms

Monitor your deployment with CloudWatch:

aws cloudwatch put-metric-alarm \
  --alarm-name high-error-rate \
  --alarm-description "Alert on high error rate" \
  --metric-name 4xxError \
  --namespace AWS/CloudFront \
  --statistic Sum \
  --period 300 \
  --threshold 10 \
  --comparison-operator GreaterThanThreshold
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Common Issues

Files Not Updating

If changes aren't appearing:

  1. Check CloudFront cache invalidation status
  2. Verify --delete flag is used in sync command
  3. Clear browser cache or test in incognito mode
  4. Check S3 bucket permissions

Slow Deployments

Optimize sync performance:

  • Use --exclude and --include to skip unnecessary files
  • Consider using --size-only for faster comparisons
  • Use parallel uploads with --cli-read-timeout and --cli-write-timeout

Permission Errors

Ensure your IAM user has:

  • S3 bucket access permissions
  • CloudFront invalidation permissions (if applicable)
  • Proper bucket policies configured

Best Practices Summary

  1. Always use --delete to keep S3 in sync with your local build
  2. Set appropriate cache headers based on file types
  3. Use CloudFront for production deployments
  4. Automate deployments with CI/CD pipelines
  5. Monitor costs - be mindful of CloudFront invalidation charges
  6. Version your deployments - tag releases for easy rollback
  7. Test locally first - verify your Hugo build before deploying

Related Resources

For more detailed information on Hugo deployment, check out the comprehensive guide on deploying Hugo-generated website to AWS S3, which covers additional deployment options and configurations.

When writing your content, remember to use proper Markdown code blocks for code examples, and refer to the Comprehensive Markdown Cheatsheet for formatting guidelines.

Useful Links

External References

Top comments (0)