Cost-Optimized Design

costing.climacs.net — V2

EC2 + Caddy Cost-Optimized Pipeline — ~$7.5/mo
costing.climacs.net ↗

~$7.5/mo
Monthly Cost
t4g.nano
ARM Graviton
Caddy
Auto TLS
73% saved
vs V1 Enterprise

🏗️ V2 Architecture Flow

User HTTPS
AWS Cloud us-east-1
VPC — Default
EC2 t4g.nano (ARM64)
Caddy Auto TLS / Reverse Proxy
Docker FastAPI :8000
Elastic IP Static Public IPv4
Serverless Data Pipeline Shared with V1
Route 53 A → EIP
S3 CUR Billing Data
EventBridge rate(8 hours)
Lambda Aggregator
S3 Aggregates JSON Output
Athena SQL Queries
Glue Crawler
HTTPS :443 :8000 Write JSON Trigger Read Aggregates
Request flow
Data flow
Pipeline trigger
AWS Cloud
VPC
EC2 Instance

⚖️ V1 Enterprise vs V2 Cost-Optimized

Component V1 Enterprise V2 EC2 Monthly Savings
Compute ECS Fargate (0.25 vCPU)
$9.00/mo
EC2 t4g.nano
$3.07/mo
-$5.93
Load Balancer ALB
$16.20/mo
Caddy (on host)
$0.00
-$16.20
TLS Certificate ACM (free) Let's Encrypt (free) $0.00
Static IP ALB DNS (included) Elastic IP
$3.65/mo
+$3.65
Route53 Alias → ALB A record → EIP $0.00
Lambda + Athena rate(15 min) rate(8 hours) ~93 fewer runs/day
Deployment ECS service update SSM RunCommand
Total Monthly ~$28/mo ~$7.5/mo -$20.5/mo (73%)

📦 Component Deep Dive

EC2 t4g.nano Compute

AWS Graviton2 ARM64 processor. 2 vCPUs, 0.5 GiB memory. Runs Amazon Linux 2023. Docker installed via cloud-init user_data.sh. Pulls the FastAPI backend container from ECR on first boot. Instance profile grants S3 read-only access.

ARM64 / Graviton2 0.5 GiB RAM Amazon Linux 2023 Docker Engine $0.0042/hr
Caddy Server Networking

Automatic HTTPS reverse proxy. Caddy handles TLS certificate provisioning and renewal from Let's Encrypt. Listens on ports 80 (HTTP redirect) and 443 (HTTPS), proxying all traffic to the backend container on localhost:8000. Zero-config TLS.

Auto Let's Encrypt HTTP → HTTPS redirect reverse_proxy :8000 Caddyfile config
Elastic IP Networking

Static IPv4 address assigned to the EC2 instance. Route53 A-record points directly to this IP — no ALB indirection. Caddy uses this IP for Let's Encrypt ACME validation. Note: AWS charges $3.65/mo for EIPs since Feb 2024.

Static IPv4 Direct A-record $3.65/mo
EventBridge Management

Scheduled rule triggers Lambda aggregator 3×/day (every 8 hours). Reduced from 15-minute intervals in V1 — CUR data only updates 1–3×/day, so higher frequency was pure waste. Saves ~93 Lambda + Athena invocations daily.

rate(8 hours) 3× daily 93 fewer runs/day
Lambda Aggregator Compute

Runs 6 Athena SQL queries against CUR Parquet files, aggregating costs by service, day, and tag. Writes JSON results to the S3 Aggregates bucket. Execution time ~3s. Shared between V1 and V2 — no changes needed.

Python 3.12 128 MB RAM 6 Athena queries Aggregates JSON
TF Terraform IaC Infrastructure

All V2 infrastructure defined in terraform-v2-ec2/. Separate state file (costing/v2-ec2.tfstate) from V1. EC2, security group, IAM role, EIP, Route53, Lambda, S3/Athena — all codified. GitLab CI runs on v2-ec2 branch.

ec2.tf user_data.sh route53.tf lambda.tf main.tf variables.tf

💰 V2 Cost Breakdown

🕐 1-Week Run

EC2 t4g.nano (168 hrs × $0.0042) $0.71
Elastic IP (168 hrs) $0.84
Route53 + Lambda + Athena $0.15
S3 storage $0.01
Total (1 week) ~$1.71

📅 Monthly Projection

EC2 t4g.nano (730 hrs) $3.07
Elastic IP $3.65
Route53 hosted zone $0.50
Lambda + Athena + S3 $0.30
Total (monthly) ~$7.52/mo

💡 Further Optimizations

Reserved Instance (1yr) -30%
Spot Instance -60%
IPv6 only (no EIP) -$3.65
1-year Savings Plan -40%
Theoretical floor ~$2/mo

🔐 Security Model

🔒
TLS via Let's Encrypt
Caddy automatically provisions and renews TLS certificates from Let's Encrypt. ACME HTTP-01 challenge validated on port 80. Certificates stored on disk with auto-renewal before expiry.
🔑
HTTP Basic Auth
Same auth model as V1 — all /api/* endpoints require credentials. Credentials injected via environment file on EC2. Frontend login modal remains unchanged.
🛡️
Security Group
EC2 SG only allows inbound TCP 80 (HTTP redirect) and 443 (HTTPS). All egress allowed for Docker pulls, AWS API calls, and Let's Encrypt ACME validation.
👤
IAM Instance Profile
EC2 role has least-privilege S3 read-only access to the aggregates bucket + ECR pull permissions. No long-term AWS credentials stored on the instance.

🔄 GitLab CI/CD Pipeline

🌿
Branch: v2-ec2
Dedicated branch with its own .gitlab-ci.yml. Pushes trigger validate → plan → apply → build → deploy stages targeting terraform-v2-ec2/.
🐳
ARM64 Docker Build
Uses docker buildx to cross-compile for linux/arm64, ensuring compatibility with Graviton2 t4g.nano. Images pushed to ECR with commit SHA + latest tags.
📡
Deploy via SSM
Instead of ECS service updates, the pipeline uses AWS Systems Manager RunCommand to trigger a Docker pull + restart on the EC2 instance. No SSH keys needed.
🏷️
Separate Terraform State
V2 uses costing/v2-ec2.tfstate in the same S3 backend. Completely isolated from V1's state — both can coexist safely during migration.

🔀 Migration Path: V1 → V2

To switch from V1 Enterprise to V2 EC2, deploy V2 first, verify Caddy TLS acquisition, then swing Route53 from ALB alias to EIP A-record. Finally, destroy V1 Fargate resources. Both versions share the same S3 data pipeline — zero data migration needed.

# 1. Deploy V2
cd terraform-v2-ec2 && terraform apply

# 2. Verify HTTPS
curl -I https://costing.climacs.net

# 3. Destroy V1 (after validation)
cd ../terraform && terraform destroy