Skip to content

AWS Architecture

Carbon Connect runs on AWS in the eu-north-1 (Stockholm) region using a hybrid serverless architecture with ECS Fargate for compute and managed services for data.


Architecture Diagram

flowchart TB
    subgraph Internet
        U[Users / Browsers]
        GH[GitHub Actions<br/>CI/CD via OIDC]
    end

    subgraph AWS["AWS eu-north-1"]
        subgraph Public["Public Subnets"]
            ALB[Application Load Balancer<br/>+ WAF]
            NAT[NAT Gateway]
        end

        subgraph Private["Private Subnets"]
            subgraph ECS["ECS Fargate Cluster"]
                API[API Service<br/>FastAPI]
                Worker[Celery Worker<br/>Fargate Spot]
                Beat[Celery Beat<br/>Scheduler]
            end
            MS[Meilisearch<br/>Search Engine]
        end

        subgraph Data["Data Subnets"]
            RDS[(RDS PostgreSQL 16<br/>+ pgvector)]
            EC[(ElastiCache<br/>Valkey Serverless)]
        end

        subgraph Storage["S3 Buckets"]
            S3D[Documents Bucket]
            S3M[Media Bucket]
        end

        subgraph Security["Security"]
            SM[Secrets Manager<br/>API Keys, DB Pass]
            IAM[IAM Roles<br/>OIDC Provider]
            KMS[KMS<br/>Encryption Keys]
        end

        subgraph Monitoring
            CW[CloudWatch<br/>Logs + Metrics]
            CI[Container Insights]
        end
    end

    U -->|HTTPS| ALB
    GH -->|OIDC| IAM
    ALB -->|Port 8000| API
    API --> RDS
    API --> EC
    API --> MS
    Worker --> RDS
    Worker --> EC
    Beat --> EC
    API --> S3D
    API --> S3M
    API --> SM
    ECS --> CW
    ECS --> CI
    Private -->|Outbound| NAT
    NAT -->|Internet| Internet

VPC Layout

The VPC uses a three-tier subnet architecture across three availability zones:

Subnet Tier CIDR Range Purpose Internet Access
Public 10.0.0.0/20 - 10.0.32.0/20 ALB, NAT Gateway Direct (IGW)
Private 10.0.48.0/20 - 10.0.80.0/20 ECS tasks, Meilisearch Outbound only (NAT)
Data 10.0.96.0/20 - 10.0.128.0/20 RDS, ElastiCache No internet access

VPC CIDR: 10.0.0.0/16 (65,536 addresses)

Availability Zones

  • eu-north-1a
  • eu-north-1b
  • eu-north-1c

NAT Gateway Strategy

  • Development/Staging: Single NAT Gateway (cost savings)
  • Production: NAT Gateway per AZ (high availability)

Service Details

RDS PostgreSQL

Setting Default Description
Instance class db.t4g.medium Graviton ARM (20% savings over Intel)
Engine version PostgreSQL 16 With pgvector extension
Storage 50 GB (auto-scale to 200 GB) General Purpose SSD (gp3)
Multi-AZ false (dev), true (prod) Standby replica for failover
Backup retention 7 days Automated daily backups
Deletion protection false (dev), true (prod) Prevents accidental deletion
Extensions vector, uuid-ossp pgvector for embedding search

ElastiCache (Valkey)

Setting Default Description
Engine Valkey 20% cheaper than Redis
Mode Serverless (dev/staging) Auto-scaling, pay-per-use
Node type cache.t4g.medium (prod) Graviton ARM
Nodes 1 (dev), 2+ (prod) Read replicas for production

ECS Fargate

API Service:

Setting Default Description
CPU 1024 (1 vCPU) Fargate compute units
Memory 2048 MB (2 GB) Container memory
Desired count 2 Running tasks
Auto-scale 1-10 Based on CPU/memory utilization
Capacity provider Fargate (on-demand) Guaranteed availability

Worker Service:

Setting Default Description
CPU 1024 (1 vCPU) Fargate compute units
Memory 2048 MB (2 GB) Container memory
Desired count 2 Running tasks
Auto-scale 1-10 Based on queue depth
Capacity provider Fargate Spot 70% cost savings

Application Load Balancer

Feature Configuration
Type Application (Layer 7)
Listeners HTTP (80) -> HTTPS redirect, HTTPS (443)
Target group API containers on port 8000
Health check GET /api/v1/health
WAF AWS WAF with rate limiting
WAF rate limit 2000 requests per 5 minutes per IP

S3 Buckets

Bucket Purpose Lifecycle
{project}-{env}-documents Application documents, reports Standard -> Intelligent-Tiering -> Glacier
{project}-{env}-media Company logos, images Standard (no lifecycle)

Both buckets have public access blocked and server-side encryption (AES-256).

Secrets Manager

The secrets module stores sensitive configuration:

Secret Description
Database password Auto-generated RDS master password
Application secrets JWT secret, session key
Claude API key Anthropic API key
Climatiq API key Carbon calculation API key
Meilisearch key Search engine master key

Security

Network Security

  • Public subnets: Only ALB and NAT Gateway
  • Private subnets: ECS tasks with outbound-only internet access
  • Data subnets: No internet access, only accessible from private subnets
  • Security groups enforce least-privilege network access

IAM

  • ECS task execution role: Pull images from ECR, read secrets
  • ECS task role: Access S3, Secrets Manager, CloudWatch
  • GitHub Actions: OIDC-based federation (no long-lived credentials)

Encryption

  • RDS: Encrypted at rest (AES-256 via KMS)
  • ElastiCache: Encryption in transit and at rest
  • S3: Server-side encryption (AES-256)
  • ALB: TLS 1.2+ for all HTTPS traffic

Cost Optimization

Strategy Service Savings
Graviton (ARM64) RDS, ElastiCache ~20%
Fargate Spot Celery workers ~70%
Valkey Serverless Cache (dev/staging) Pay-per-use
S3 Intelligent-Tiering Documents Automatic tier optimization
Single NAT Gateway Non-production ~66% NAT cost