The Hidden Cost of Always-On AWS EC2 & Application Load Balancers
For years, this website (aldianfazrihady.com) ran on a classic high-availability AWS architecture: CloudFront CDN in front of an internet-facing Application Load Balancer (AFCOM-0), an Auto Scaling Group of EC2 Spot Instances running Nginx + uWSGI (for HTTP) + Daphne (for WebSockets) + Celery, RDS PostgreSQL, and ElastiCache Redis.
Even after aggressive optimizations—such as pruning unused ALB availability zones to minimize AWS's $0.005/hr public IPv4 charges and automating AMI snapshot cleanup—an always-on AWS ALB + EC2 + Elastic IP stack still carries a baseline floor of ~$45.70/month ($25 ALB + $17 EC2 + $3.70 IPv4) regardless of traffic volume.
By migrating the entire stack to Google Cloud Run serverless containers with Daphne ASGI, WhiteNoise, Supabase PostgreSQL, and Redis Cloud, we reduced baseline infrastructure cost to $0.00/month while preserving real-time WebSocket chat, push notifications, and sub-second responses.
1. Consolidating Nginx, uWSGI, and Daphne into a Single ASGI Port
On traditional VMs, Django deployments often split HTTP traffic (uWSGI/Gunicorn) and WebSocket traffic (Daphne/Uvicorn) across separate internal ports behind Nginx. On Google Cloud Run, a container exposes a single ingress PORT (default 8080) that natively supports both HTTP/2 and WebSocket (Upgrade: websocket) connections with up to a 60-minute request timeout.
# Dockerfile entrypoint running Daphne ASGI on $PORT
CMD daphne -b 0.0.0.0 -p ${PORT:-8080} afcom.routing:application
To serve compiled Webpack bundles and Service Worker scripts without a separate Nginx container, we integrated WhiteNoise (whitenoise.middleware.WhiteNoiseMiddleware) with a custom IMMUTABLE_FILE_TEST and a Service-Worker-Allowed: / header hook so the PWA service worker can control the root scope even when served from /static/afcom/bundles/.
2. Eliminating Celery Workers in Serverless Containers
Scale-to-zero containers cannot rely on long-running background Celery daemon processes because CPU is throttled when no HTTP or WebSocket request is active. We refactored two critical paths:
- Real-Time Admin-to-Visitor Chat Replies: Instead of dispatching a Celery task to push messages over the channel layer, the REST endpoint calls
async_to_sync(channel_layer.group_send)directly against Redis Cloud, delivering replies to the visitor's WebSocket in <15ms. - Email Notifications: Dispatched via non-blocking thread execution during the active request lifecycle using Gmail SMTP TLS.
3. Automated CI/CD with Google Cloud Build
Every push triggers a reproducible multi-step build via cloudbuild.yaml that builds the container image, tags it with $SHORT_SHA, pushes it to Artifact Registry / GCR, and deploys a new Cloud Run revision with GIT_HASH=$SHORT_SHA injected as an environment variable.