Hosting a Static Website with AWS: My Learning Journey

Last Updated on March 24, 2025
🚧 In Progress
#AWS #S3 #CloudFront #DevOps #WebHosting

I've been working on moving my portfolio website from traditional hosting to AWS. This post documents my journey, the challenges I faced, and how I overcame them. I'm sharing this to help others who might face similar issues and to document my own learning process.

What I'm Trying to Accomplish

My goal is to create a scalable, secure hosting solution for my static website using AWS S3 for storage, CloudFront for content delivery, and a custom domain from GoDaddy. This approach should provide better performance while keeping costs down.

Challenge #1: S3 Bucket Permissions

Initially, I couldn't get my S3 bucket to serve files correctly. After uploading my files, I kept getting "Access Denied" errors. I spent hours confused about why my bucket policy wasn't working.

Solution:

I discovered I needed both the correct bucket policy and the right ACL settings. Here's the bucket policy that finally worked:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket-name/*"
    }
  ]
}

Additionally, I needed to disable "Block all public access" settings on the bucket.

What I Learned:

AWS has multiple layers of security, and you need to address all of them. The console doesn't always make it clear which setting is causing a problem. Reading the AWS documentation thoroughly before starting would have saved me time.

Setting Up CloudFront Distribution

Next, I needed to set up a CloudFront distribution to deliver my content with low latency. The AWS console has so many options that it was initially overwhelming.

Challenge #2: SSL Certificate Issues

I wanted to use HTTPS, but kept getting certificate validation errors. The certificate was created but showed as "Pending validation" for hours.

Solution:

I needed to add specific DNS records to GoDaddy to validate my domain ownership. After creating the CNAME records AWS provided and waiting about 30 minutes, the certificate was validated. I also found that you must request certificates in the US East (N. Virginia) region for them to work with CloudFront, regardless of where your S3 bucket is located.

What I Learned:

AWS Certificate Manager is region-specific, and CloudFront has specific requirements. Reading the fine print in documentation is crucial. Also, DNS propagation takes time, so patience is key.

DNS Configuration Troubles

The final step was connecting my GoDaddy domain to the CloudFront distribution.

Challenge #3: Pointing Domain to CloudFront

After updating my DNS records in GoDaddy, my website still wasn't loading. I kept getting directed to GoDaddy's parking page.

Solution:

I needed to create both an A record and a CNAME record. The A record needed to be an "Alias" pointing to my CloudFront distribution, and the CNAME was needed for the "www" subdomain. Also, DNS changes took about 48 hours to fully propagate, much longer than I expected.

What I Learned:

DNS propagation can be frustratingly slow. Using tools like dig or online DNS checkers helps confirm if your changes are propagating correctly. Plan ahead and don't make these changes right before an important deadline.

Building My Portfolio Website: A Journey Through Code and Cloud

Creating a portfolio website seems straightforward until you dive into the details. What began as a simple showcase of my work evolved into a comprehensive learning experience spanning front-end development, cloud hosting, automation, and serverless architecture. Here's how I navigated the challenges and what I learned along the way.

Phase 1: Laying the Foundation

Starting with a clean slate, I designed the website structure using HTML, CSS, and JavaScript. Creating a responsive design was crucial - I wanted the site to look great on any device. I leveraged Claude to help with some of the initial code scaffolding, which accelerated development while giving me time to focus on the overall design and user experience.

Challenge #4: Mobile Responsiveness

The navbar worked perfectly on desktop but became unresponsive on mobile devices. After inspecting the code, I discovered the issue was related to event listeners not properly handling touch events and viewport sizes.

Solution:

I implemented a responsive navbar that collapses into a hamburger menu on smaller screens, ensuring smooth navigation regardless of device. This required restructuring my CSS with media queries and updating JavaScript event handlers to detect both click and touch interactions.

Phase 2: Cloud Hosting & Continuous Deployment

After completing local development, I moved to hosting the site on AWS. I chose Amazon S3 for static site hosting combined with CloudFront for content delivery and HTTPS support.

Challenge #5: Domain Configuration

Connecting my GoDaddy domain to AWS CloudFront initially resulted in SSL certificate errors and DNS resolution issues. The site would intermittently show as unavailable or insecure.

Solution:

I configured AWS Certificate Manager (ACM) to create and validate an SSL certificate for my domain. Then I created CloudFormation templates to properly set up the infrastructure connecting S3, CloudFront, and ACM. This ensured consistent deployment and proper DNS resolution.

Challenge #6: Deployment Automation

Manually uploading files to S3 after each code change quickly became tedious and error-prone.

Solution:

I set up a GitHub Actions workflow to automatically deploy code to my S3 bucket whenever I pushed changes to the master branch. However, this introduced a new challenge...

name: Deploy to S3

on:
    push:
    branches:
        - master  # Set the branch to deploy from

jobs:
    deploy:
    runs-on: ubuntu-latest
    steps:
        - uses: actions/checkout@v2
        
        - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        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 ./ s3://my-bucket-name/ --delete --exclude ".git/*" --exclude ".github/*"
            
        - name: Invalidate CloudFront
        run: |
            aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"}

Challenge #7: CloudFront Access Issues

After implementing the GitHub Actions workflow, I encountered 403 Access Denied errors when trying to access my website through my domain or CloudFront.

Solution:

The issue was in my S3 bucket policy. When GitHub Actions uploaded files, it wasn't preserving the necessary settings. I modified my bucket policy to properly allow CloudFront access via Origin Access Control (OAC) and updated my GitHub workflow to maintain the correct content types for different file extensions. This ensured CloudFront could properly serve my static content while keeping the S3 bucket itself secure and private.

Phase 3: Interactive Features & Backend Integration

With the site live, I focused on adding interactive elements to engage visitors and showcase my skills.

Challenge #8: Contact Form Implementation

I wanted to include a contact form without requiring server-side code on my static site. Additionally, I needed to protect my email from spam while ensuring messages would reach me reliably.

Solution:

I implemented a serverless architecture using AWS Lambda and API Gateway. When a visitor submits the form, JavaScript sends the data to an API Gateway endpoint, which triggers a Lambda function. The Lambda function processes the submission and forwards it to my email using Amazon SES (Simple Email Service).

Challenge #9: CORS Configuration

After implementing the contact form, I encountered CORS (Cross-Origin Resource Sharing) errors when testing. The browser would block the form submission with a "No 'Access-Control-Allow-Origin' header" error.

Solution:

I updated the API Gateway configuration to properly handle OPTIONS preflight requests and return the correct CORS headers. I also modified my Lambda function to include appropriate CORS headers in both success and error responses. This ensured browsers would allow the cross-origin requests from my website to my API.

Learning Outcomes & Skills Acquired

This project has significantly expanded my cloud architecture knowledge and DevOps skills. I've gained hands-on experience with:

Future Enhancements

Moving forward, I plan to enhance the site with a more robust project filtering system, a dedicated blog section with pagination, and analytics integration to better understand visitor engagement. I'm also exploring adding a dark mode option and multilingual support to improve accessibility.

This portfolio isn't just a showcase of my work—it's a living demonstration of my problem-solving approach and continuous learning mindset. Each challenge I encountered became an opportunity to deepen my understanding and expand my skillset.

What I'm Working On Next

My site is now successfully running on AWS, but I'm working on automating deployments using GitHub Actions. This will allow me to push changes to my repository and have them automatically deployed to S3. I'm also exploring how to set up monitoring and alerts.

Check back next week for an update on how I'm handling the CI/CD pipeline!