A production-like, fully automated mini e-commerce platform that demonstrates CDN → Varnish → App → DB behavior, including CI/CD, observability, and safe operations.
Phases 0-9 are implemented and validated locally and in Kubernetes (OpenAPI contract, cache headers, Varnish HIT/MISS/PASS, purge via PURGE with X-Purge-Token, CI/CD baseline, Pulumi + Cloudflare edge wiring, and frontend SPA).
- Go 1.26 (for API server and code generation)
- GoLangCI Lint (for static analysis)
# binary will be $(go env GOPATH)/bin/golangci-lint
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.9.0
- Docker
- Node.js 24+ and pnpm (for web frontend)
make api-init
make openapi
make api-lint
make api-test
make api-run
curl -i http://localhost:3000/
curl -i http://localhost:3000/health
curl -i http://localhost:3000/category
curl -i http://localhost:3000/product/prod-001
curl -i http://localhost:3000/cart
curl -i http://localhost:3000/account
# Admin update (triggers purge tags)
curl -i -X POST http://localhost:3000/admin/product/prod-001 \
-H 'Content-Type: application/json' \
-d '{"name":"Updated Name","inStock":false}'make web-install
make web-generate-client # Generate TypeScript client from OpenAPI spec
make web-run # Dev server at http://localhost:5173
make web-build # Production build
make web-preview # Preview production buildNote: Docker Compose Varnish setup may encounter DNS resolution issues in certain CI environments. See docs/docker-compose-issues.md for details. For full Varnish functionality, use the Kubernetes deployment.
make docker-up
make docker-logs
# Access web frontend (Docker host port 8080 -> web container port 80)
open http://localhost:8080
# Access via Varnish (Docker host port 6081 -> Varnish container port 80)
curl -i http://localhost:6081/
curl -i http://localhost:6081/health
curl -i http://localhost:6081/category
curl -i http://localhost:6081/product/prod-001
# Verify cache behavior (X-Cache: MISS on first request, HIT on second)
curl -i http://localhost:6081/product/prod-001
curl -i http://localhost:6081/product/prod-001
# Verify bypass for non-cacheable endpoints (X-Cache: PASS)
curl -i http://localhost:6081/cart
curl -i http://localhost:6081/account
make docker-downThe OpenAPI spec is the source of truth:
make openapi-validate
make openapiGenerated types live in apps/api/internal/api/api.gen.go and handlers in apps/api/cmd/server.
GitHub Actions runs OpenAPI validation, codegen drift checks, lint/test, and Docker image builds for app/web workflows.
Local CI runs can be executed with:
make app-ci
make web-ciRun the all the CI workflows with act:
make gh-act-all-ci- App CI - Automated linting and testing on PRs and main branch
- Web CI - Automated client generation, drift checks, linting, and build on PRs and main branch
- K8s CI - Validate Kubernetes manifests on PRs and main branch
- Infra Preview - Preview infrastructure changes on PRs
- Infra Up - Deploy infrastructure (manual trigger)
- Infra Destroy - Destroy all infrastructure resources (manual trigger)
- Configure AWS OIDC - Set up GitHub OIDC provider in AWS IAM
- Create IAM Role - Create a role with trust policy for your repository
make infra-github-actions-oidc-role- creates the IAM role with trust policy for GitHub OIDC authentication
- Add GitHub Repository Secrets:
AWS_ROLE_ARN- IAM role ARN for OIDC authenticationgh secret set AWS_ROLE_ARN -r sbasir/edge-cache-lab --body "arn:aws:iam::<account-id>:role/<role-name>"
PULUMI_ACCESS_TOKEN- Pulumi Cloud access token- Get from Pulumi Cloud dashboard → Account Settings → Access Tokens
gh secret set PULUMI_ACCESS_TOKEN -r sbasir/edge-cache-lab --body "<your-pulumi-access-token>"
CF_API_TOKEN- Cloudflare API token- Create a token for your zone in the Cloudflare dashboard → My Profile → API Tokens with permissions:
- Zone:Zone:Read
- Zone:DNS:Edit
- Zone:Workers Routes:Edit
- User:Membership:Read
- User:User Details:Read
- Account:Workers Scripts:Edit
gh secret set CF_API_TOKEN -r sbasir/edge-cache-lab --body "<your-cloudflare-api-token>"
- Create a token for your zone in the Cloudflare dashboard → My Profile → API Tokens with permissions:
CF_ZONE_ID- Cloudflare zone ID- Get from Cloudflare dashboard → Overview → API → Zone ID
gh secret set CF_ZONE_ID -r sbasir/edge-cache-lab --body "<your-cloudflare-zone-id>"
- Add Github Repository Variables:
CF_API_RECORD_NAME- DNS record namegh variable set CF_API_RECORD_NAME -r sbasir/edge-cache-lab -b api.edge.example.com
AWS_REGION- AWS region for deployment (e.g., us-east-1)gh variable set AWS_REGION -r sbasir/edge-cache-lab -b us-east-1
API_BASE_URL- Base URL for the API (used in web frontend build)gh variable set API_BASE_URL -r sbasir/edge-cache-lab -b https://api.edge.example.com
- Deploy via GitHub Actions:
- Go to Actions → "Pulumi Up" workflow
- Click "Run workflow"
- Confirm deployment
Implementation with reverse proxy caching. See docs/varnish.md for details.
Update a product and purge cached content with a token:
# With Docker Compose (through Varnish)
curl -i -X POST http://localhost:6081/admin/product/prod-001 \
-H 'Content-Type: application/json' \
-H 'X-Purge-Token: test-purge-token' \
-d '{"name":"Updated Product","inStock":false}'
# Response includes X-Purge-Tags (e.g., product:prod-001) for purge tooling.
# Purge cached product by URL (Varnish only supports PURGE here)
curl -i -X PURGE http://localhost:6081/product/prod-001 \
-H 'X-Purge-Token: test-purge-token'
# Verify purge worked - next GET should be MISS
curl -i http://localhost:6081/product/prod-001 # X-Cache: MISSPurge token is validated by both the API and Varnish. The default token is test-purge-token.
For non-default tokens:
- Set the
PURGE_TOKENenvironment variable in the API deployment to your desired token value. - Configure Varnish (e.g., in the VCL) to expect the same token value.
- Ensure the token value matches in both the API environment variable and the Varnish configuration; mismatches will cause purge requests to be rejected.
- When changing the token, update all of these places and redeploy/reload both the API and Varnish.
Run comprehensive endpoint tests:
# Test against Docker Compose (default port 6081)
make docker-up
make validate-endpoints
# Test against Kubernetes (after port-forward)
make k8s-local-up && make k8s-wait
make k8s-port-forward-varnish # in one terminal
make validate-endpoints # in another
make k8s-local-downDeploy to local Kubernetes:
# Deploy API, Varnish, and Web
make k8s-local-up
# Wait for rollout
make k8s-wait
# Check status
make k8s-status
# Port-forward Web (access at http://localhost:8080)
make k8s-port-forward-web
# Port-forward Varnish (access at http://localhost:6081)
make k8s-port-forward-varnish
# In another terminal, test cache behavior
curl -i http://localhost:6081/product/prod-001 # X-Cache: MISS
curl -i http://localhost:6081/product/prod-001 # X-Cache: HIT
curl -i http://localhost:6081/cart # X-Cache: PASS
# Cleanup
make k8s-local-down