On this page

Overview

This project started with a simple question: if the application is intentionally lightweight, what can the deployment still say about how I work?

The answer was to keep the frontend static and put the engineering effort into how it is delivered. The site runs behind Cloudflare, serves from two GCP origin VMs in separate zones, and deploys through GitHub Actions. The stack is small, but the responsibilities are the same ones that show up on larger systems: networking, failover, delivery, and keeping the setup understandable.

Architecture

Traffic enters through Cloudflare, which handles DNS, CDN, TLS, and load balancing. Requests are routed to one of two GCP Compute Engine e2-micro VMs in separate zones. Each VM runs Nginx and serves the static Astro build from /var/www/html.

Internet
   |
Cloudflare DNS + CDN
   |
Cloudflare Load Balancer
   |---------------------------|
   |                           |
GCP VM 1                    GCP VM 2
us-central1-a               us-central1-b
Nginx                       Nginx
Cloudflare distributes traffic across two origin VMs in separate GCP zones.
Multi-node architecture: Cloudflare load balancer routing to two GCP VMs running Nginx

Cloudflare distributes traffic across two origin VMs in separate GCP zones.

Why This Shape

Most portfolio sites stop at a managed frontend host. That is a reasonable choice, but it hides the parts of engineering work I wanted to emphasize: deployment, origin management, and failure handling.

Two small VMs in separate zones make the tradeoff visible. The site can tolerate losing one instance or one zone, and the application itself does not need to change to get that behavior.

Infrastructure Setup

Provisioning is defined in Terraform and creates:

  • a VPC network and firewall rules
  • two Debian 12 VM instances
  • startup-script-based web server setup
  • public IPs for both origins

Each origin runs a minimal Nginx configuration with a /healthz endpoint for load-balancer checks and serves the static site directly from disk.

server {
    listen 80;

    root /var/www/html;
    index index.html;

    location /healthz {
        return 200 'OK';
        add_header Content-Type text/plain;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Delivery Workflow

Deployment is handled by GitHub Actions on every push to main.

The workflow:

  1. checks out the repo
  2. installs Node dependencies
  3. builds the Astro site from site/
  4. archives the generated output
  5. copies the artifact to both origin VMs
  6. reloads Nginx after deployment

This keeps the application layer simple. There is no runtime process manager, database, or app server to operate for this project. The focus stays on reproducible delivery and infrastructure choices.

Cloudflare’s Role

Cloudflare handles several jobs that would otherwise require more infrastructure:

FeatureValue to this project
DNSPublic entry point for the domain
CDNBetter static asset delivery
TLSHTTPS at the edge
Load balancingHealth-checked failover across origins
Basic protectionReduces direct exposure of the origin layer

Note

The point of using Cloudflare here was not feature volume. It was to keep the origin layer small while still showing how traffic, failover, and public delivery are handled.

Cost and Tradeoffs

The whole setup stays in a low monthly range because the application is static and the compute layer is deliberately small.

ResourceEstimated Monthly Cost
GCP e2-micro VM 1Free tier or low cost
GCP e2-micro VM 2~$6-8
Persistent disks~$1
Cloudflare Load Balancer~$5
Network egress~$0-2
Total~$12-16/month

The main tradeoffs were straightforward:

  • Static Astro over a dynamic app to keep operations simple and failure modes easy to reason about
  • Cloudflare over a cloud-native load balancer to combine CDN, TLS, and failover in one edge layer
  • Two small VMs over one to show redundancy without turning the project into an expensive architecture exercise

Challenges

The hardest parts were around integration, not application code.

  • aligning Cloudflare behavior with origin firewall and health-check expectations
  • keeping the deployment flow simple without hiding what is actually happening
  • choosing an architecture that was meaningful enough to discuss without adding complexity that did not serve the project

What It Shows

This project shows the kind of work I want to keep doing: connecting application delivery to infrastructure decisions.

  • Deployment thinking: build once, ship predictably, keep the runtime simple
  • Infrastructure fundamentals: networking, origins, health checks, and web serving
  • Operational judgment: choosing the smallest setup that still demonstrates the right engineering concerns
  • Cost awareness: using just enough infrastructure to make the design decisions real

Next Improvements

  • make deployments more atomic to avoid stale assets on origin
  • tighten origin security defaults and deployment access
  • add more explicit monitoring and verification around releases
  • document the final production setup more precisely alongside the Terraform and workflow