Back to blog

Setting Up This Portfolio Site

How I built this portfolio using Astro, AWS S3, CloudFront, and GitHub Actions for automated deployments.

I built this portfolio to show what I can do beyond what fits on a resume. Resumes are limited to bullet points and job titles, but I wanted a place where I can explain my work with more context. I also wanted to be findable online for anyone in Central Florida looking for cloud infrastructure or DevOps help.

Why Astro and AWS

I chose Astro because I wanted to learn a new framework. I had heard it was popular for static sites, so I used this project as a chance to try it out. For hosting, I went with AWS because I already had experience setting up static sites on S3, and the free tier makes it cost-effective.

I considered using Azure, but I find it more complex and better suited for enterprise projects with many moving parts. For a simple static site like this, AWS felt like the right fit.

The Stack

The site runs on three main pieces: S3, CloudFront, and GitHub Actions.

S3 stores all the files. The HTML, CSS, JavaScript, images, everything you see on the site lives in an S3 bucket. I could have used EC2, Amplify, or ECS, but S3 is the simplest and cheapest option for static hosting. It also gives me full control over the deployment process.

CloudFront sits in front of S3 as a CDN. It caches the site at edge locations around the world, which speeds up delivery. It also handles HTTPS. The site would work without CloudFront, but it would be slower and insecure.

GitHub Actions handles deployments. Without it, I would either need to set up AWS CodePipeline (which involves CodeCommit, CodeBuild, CodeDeploy, and more complexity), or I would have to upload files manually. Manual uploads are error-prone and tedious.

How Deployment Works

The code lives in a GitHub repository. When I push changes to the main branch, GitHub Actions automatically runs a workflow defined in deploy.yml. The workflow does the following:

  1. Sets up Node.js with the correct version
  2. Installs dependencies with npm ci
  3. Builds the site with npm run build
  4. Configures AWS credentials using secrets stored in GitHub
  5. Syncs the build output to S3 with aws s3 sync ./dist s3://bucket --delete
  6. Invalidates the CloudFront cache so changes appear immediately

The --delete flag is important. It removes files from S3 that no longer exist locally, keeping S3 in sync with the source. Without it, deleted files would stay in S3 forever.

From my end, the only manual step is committing and pushing code. Everything else happens automatically.

What Tripped Me Up

The biggest challenge was writing the YAML file for GitHub Actions. YAML is picky about indentation, and the workflow has multiple steps that need to run in the right order. I had indentation issues early on, but the GitHub Actions extension in VS Code helped me catch and fix them quickly.

I also did not initially understand what the --delete flag did for s3 sync. Now I know it mirrors deletions from the source to the destination, which is exactly what I needed.

CloudFront cache invalidation is another thing worth mentioning. Without it, updates can take hours to propagate. The invalidation step forces CloudFront to serve the latest version immediately.

Setting Up the Custom Domain

I purchased my domain from Porkbun and needed to point it to CloudFront. The main confusion was understanding when to use ALIAS vs CNAME records. I learned that CNAME records cannot be used on root domains (like rodgerpolan.co), only on subdomains (like www.rodgerpolan.co). For the root domain, you need an ALIAS record.

Porkbun had locked the existing ALIAS record because it pointed to their parking page service. I had to delete it completely and create a new ALIAS record pointing to my CloudFront distribution.

After setting up DNS, I visited the site and got a 403 error from CloudFront. The browser showed the site was not secure, which told me it was an SSL certificate issue. I looked up how to provision certificates in AWS Certificate Manager and found I needed to add the domain as an alternate domain name in CloudFront and attach an SSL certificate.

ACM required DNS validation using CNAME records. I needed two separate validation records: one for the root domain and one for the www subdomain. I added both CNAME records in Porkbun, waited for validation to complete, then attached the certificate to CloudFront. The whole process took 20-30 minutes.

The Result

The setup works. I commit code, push to GitHub, and the site updates automatically. The entire process takes a couple of minutes. I have not measured the exact time difference compared to manual deployment, but not having to think about uploading files or clearing caches is worth it.