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
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:
- checks out the repo
- installs Node dependencies
- builds the Astro site from
site/ - archives the generated output
- copies the artifact to both origin VMs
- 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:
| Feature | Value to this project |
|---|---|
| DNS | Public entry point for the domain |
| CDN | Better static asset delivery |
| TLS | HTTPS at the edge |
| Load balancing | Health-checked failover across origins |
| Basic protection | Reduces 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.
| Resource | Estimated Monthly Cost |
|---|---|
| GCP e2-micro VM 1 | Free 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