AWS Architecture — Migration Guide

This page covers how to migrate DevMetrics from the default free-tier stack (Railway + Vercel + Atlas + Upstash) to a full AWS deployment. It includes an architecture overview, service mapping, infrastructure setup, and the code changes required in each layer.

The current stack works fine for portfolio purposes. Choose AWS if you want to demonstrate cloud infrastructure knowledge, need more control over networking, or are targeting roles where AWS experience is evaluated.


Architecture Overview

                        ┌─────────────────────────────────────┐
                        │           GitHub                    │
                        │  OAuth App + Webhooks               │
                        └──────────┬──────────────────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │        CloudFront CDN        │
                    │   (React SPA, global edge)   │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │    S3 Bucket (static files)  │
                    │    dist/ from Vite build     │
                    └─────────────────────────────┘

                    ┌─────────────────────────────┐
                    │     Application Load         │
                    │     Balancer (ALB)           │
                    └──────────────┬──────────────┘
                                   │
            ┌──────────────────────┴──────────────────────┐
            │                                             │
┌───────────▼───────────┐                   ┌────────────▼──────────┐
│   ECS Fargate Service  │                   │  ECS Fargate Service   │
│   devmetrics-api       │                   │  devmetrics-worker     │
│   (Express, port 3001) │                   │  (BullMQ worker)       │
└───────────┬────────────┘                   └────────────┬──────────┘
            │                                             │
            └──────────────┬──────────────────────────────┘
                           │ (both read/write)
            ┌──────────────┴──────────────────────────────┐
            │                                             │
┌───────────▼───────────┐                   ┌────────────▼──────────┐
│   MongoDB Atlas M0     │                   │  ElastiCache Redis     │
│   (unchanged)          │                   │  (replaces Upstash)    │
└────────────────────────┘                   └───────────────────────┘

            ┌─────────────────────────────────────────────┐
            │          AWS Secrets Manager                │
            │   (stores all env vars / secrets)           │
            └─────────────────────────────────────────────┘

            ┌─────────────────────────────────────────────┐
            │         GitHub Actions (CI/CD)              │
            │   builds image → pushes to ECR → deploys    │
            │   to ECS + invalidates CloudFront cache      │
            └─────────────────────────────────────────────┘

Service Mapping

Current AWS Equivalent Notes
Vercel S3 + CloudFront Static files on S3, served via CloudFront CDN
Railway (API) ECS Fargate (devmetrics-api) Containerized Express, behind ALB
Railway (Worker) ECS Fargate (devmetrics-worker) Same image, different start command
Upstash Redis ElastiCache (Serverless Redis) In-VPC, no public endpoint
MongoDB Atlas MongoDB Atlas (unchanged) Atlas runs on AWS anyway; DocumentDB is an alternative
GitHub Actions GitHub Actions (updated steps) Same CI, new deploy targets
.env file AWS Secrets Manager Secrets injected into ECS task definitions

Prerequisites


Step 1 — Dockerize the Server

Both ECS services run from the same Docker image with different start commands. Create server/Dockerfile:

FROM node:20-alpine

WORKDIR /app

# Install dependencies first (layer caching)
COPY package*.json ./
RUN npm ci --only=production

COPY src/ ./src/

# Default start command — overridden per ECS service
CMD ["node", "src/index.js"]

Create server/.dockerignore: