Goal
Build a portfolio to document networking, cloud, and security projects: a place to share the work, explain decisions, and record lessons learned.
Stack
Astro generates the static site. Terraform manages the hosting infrastructure, and the AWS CLI uploads the build and invalidates the CloudFront cache.
Architecture
Visitors connect to CloudFront over HTTPS. HTTP requests redirect to HTTPS, and CloudFront uses Origin Access Control to sign requests and retrieve files from the private Amazon S3 bucket through its REST endpoint.
Custom domain — completed
The portfolio is live at jacobferguson.io. Cloudflare manages DNS, with the apex and www records pointing to d2q1cw2uv4nyuz.cloudfront.net. Cloudflare also holds the DNS-only ACM validation CNAME records, which remain in place for certificate renewal.
An issued AWS Certificate Manager certificate in us-east-1 covers jacobferguson.io and www.jacobferguson.io. Both names are configured as CloudFront aliases. CloudFront serves the certificate using SNI and the TLSv1.2_2021 security policy, requiring TLS 1.2 or later.
Security
All four S3 Block Public Access settings are enabled: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets. The bucket uses AES256 server-side encryption.
The bucket policy grants the CloudFront service permission to read objects only when the request's source ARN matches this distribution. The S3 bucket is not publicly accessible.
Routing
A CloudFront Function rewrites directory and extensionless URLs to the generated index.html files. For example, /about/ becomes /about/index.html, and /blog becomes /blog/index.html. Asset URLs with file extensions retain their filenames.
Deployment
- Run
npm run buildto generate the static site indist/. Astro's configured site URL ishttps://jacobferguson.io, used for canonical URLs, social sharing metadata, RSS, and the sitemap. - For infrastructure changes, run Terraform plan, review the proposed changes, and apply the saved plan. Content-only updates require only a build, S3 upload, and CloudFront invalidation.
- Use
aws s3 syncwith theportfolioprofile to uploaddist/to the private S3 bucket. - Invalidate the CloudFront cache so visitors receive the updated files.
Troubleshooting
- Project location: move the project out of
System32into a user-owned projects directory, then run commands from the repository root. - Terraform on PATH: after installing Terraform, open a fresh terminal so it picks up the updated PATH. Confirm the installation with
terraform version. - Temporary credentials: sign in with
aws login --profile portfolio-login, then configure theportfolioprofile'scredential_processto runaws configure export-credentials --profile portfolio-login --format process. Terraform uses that shared profile to obtain temporary AWS credentials.
Next steps — planned
- GitHub Actions deployment (planned): automate the build, upload, and cache invalidation. Deployment currently uses the local Terraform and AWS CLI workflow.