Skip to main content

Deployment Architecture

Deployment Architecture

Project: Drop{{PROJECT_NAME}} Version: 0.1.0{{VERSION}} Date: 2026-02-23{{DATE}} Author: Platform Architect (AI){{AUTHOR}} Status: Draft | In Review | Approved Reviewers: Alem Bašić (CEO){{REVIEWERS}}

Document History

Version Date Author Changes
0.1 2026-02-23{{DATE}} Platform Architect (AI){{AUTHOR}} Initial draft from source code analysis

1. Overview

Drop is a remittance and QR payment application deployed on AWS in the eu-west-1 (Ireland) region. The application uses a Next.js 16 standalone server containerised in a multi-stage Docker image and served via AWS App Runner. The database is RDS PostgreSQL (db.t4g.micro) with automated daily snapshots and 7-day retention. All infrastructure is production-grade; staging runs on Fly.io (Stockholm).

System: Drop — Remittance + QR Payment App{{PROJECT_NAME}} Cloud Provider: AWS{{CLOUD_PROVIDER}} (production), Fly.io (staging) Provider Rationale: AWS{{RATIONALE}} App Runner chosen for fully managed container hosting with zero infrastructure management; auto-scaling built-in; GDPR data residency satisfied by eu-west-1 (Ireland). Architecture Pattern: Monolith{{PATTERN}} — single Next.js app serving both frontend and API routes.


2. Infrastructure Topology

graph TB
    subgraph Internet
        USER[End UsersUsers]
        CDN[CDN / Mobile App]
        BSTACK[BetterStack Uptime Monitor]CloudFront]
    end

    subgraph AWSPublic eu-west-1Subnet
        subgraphALB[Application AppLoad RunnerBalancer]
        AR[AWSBASTION[Bastion App Runner\ndrop-web\neu-west-1]Host]
    end

    subgraph ECRPrivate REPO[ECRSubnet Repository\n324480209768.dkr.ecr.eu-west-1\n.amazonaws.com/drop-web]- App
        APP1[App Server 1]
        APP2[App Server 2]
    end

    subgraph RDSPrivate DB[Subnet - Data
        DB_PRIMARY[(RDSPrimary PostgreSQLDB)]
        16\ndrop-db\ndb.t4g.micro\ndrop-db.czu2qe4quy4v\n.eu-west-1.rds.amazonaws.com)DB_REPLICA[(Read Replica)]
        CACHE[Redis Cache]
    end

    subgraph Isolated Subnet
        SECRETS[Secrets SM[AWSManager]
        SecretsBACKUP[Backup Manager\nJWT_SECRET\nDATABASE_URL\nSLACK_WEBHOOK_URL]
        end

        subgraph CloudWatch
            LOGS[CloudWatch Logs\n/aws/apprunner/drop-web/...]
        end
    end

    subgraph Staging - Fly.io Stockholm
        FLY[Fly.io App\ndrop-staging\nSQLite ephemeral]
    end

    subgraph External Services
        SUMSUB[Sumsub KYC API\napi.sumsub.com]
        BANKID[BankID OIDC\nNorwegian eID]
        SLACK[Slack Webhook\nalai-talk.slack.com]Storage]
    end

    USER --> ARCDN
    BSTACKCDN --> ARALB
    ARALB --> DBAPP1
    ARALB --> SMAPP2
    ARAPP1 --> LOGSDB_PRIMARY
    ARAPP2 --> SUMSUBDB_PRIMARY
    ARAPP1 --> BANKIDCACHE
    ARDB_PRIMARY --> SLACKDB_REPLICA
    REPOAPP1 --> ARSECRETS

3. Networking Architecture

3.1 VPC / VNET Design

App Runner is a fully managed service — AWS manages the underlying VPC. Drop's RDS instance is in the default VPC in eu-west-1. No custom CIDR ranges are managed by Drop at this stage.

BankIDAPIs
Network CIDR Purpose
AWS Default VPC (eu-west-1)/ VNET AWS-managed{{CIDR_VPC}} AppMain Runnernetwork + RDS default networkingboundary
AppPublic RunnerSubnet OutboundA AWS-managed NAT{{CIDR_PUB_A}} EgressLoad tobalancers, RDS,NAT Sumsub,gateways
Public Subnet B{{CIDR_PUB_B}}Load balancers, NAT gateways (AZ-B)
Private Subnet A{{CIDR_PRIV_A}}Application servers
Private Subnet B{{CIDR_PRIV_B}}Application servers (AZ-B)
Isolated Subnet A{{CIDR_ISO_A}}Databases, secrets
Isolated Subnet B{{CIDR_ISO_B}}Databases, secrets (AZ-B)

Note: Custom VPC with private subnets is tracked as a security hardening item for v1.0 production launch.

3.2 Load Balancer Configuration

App Runner provides a built-in HTTPS load balancer — no ALB/NLB managed by Drop.

(Fly.iostaging),AppRunner
Parameter Value
Type AWS{{LB_TYPE}} App Runner (built-in HTTPS proxy)
Protocol HTTPS (TLS 1.2+) — App Runner enforced
SSL Termination At Appload Runner edgebalancer
Health Check Path /api/health{{HEALTH_CHECK_PATH}}
Health Check Interval 30s{{INTERVAL}}s
Unhealthy Threshold 3{{THRESHOLD}} consecutive failures
ForceIdle HTTPSTimeout true{{TIMEOUT}}s
Stickiness {{STICKINESS}} default

3.3 DNS Architecture

Runnermanaged
Record Type Value TTL
getdrop.no (future){{DOMAIN}} A / ALIAS AppLoad Runner URLBalancer 300{{TTL}}
Current productionapi.{{DOMAIN}} CNAME 9ef3szvvsb.eu-west-1.awsapprunner.comAPI Load Balancer App{{TTL}}
cdn.{{DOMAIN}}CNAMECDN Distribution{{TTL}}

DNS Provider: TBD — requires domain transfer from getdrop.no registration (drop.no owned by TV2).{{DNS_PROVIDER}} Failover Strategy: Manual{{FAILOVER_STRATEGY}} failover via DNS update to secondary App Runner service in eu-north-1.

3.4 CDN Configuration

fromNext.js.next/static/App
Parameter Value
Provider None{{CDN_PROVIDER}} currently — App Runner serves directly
Static assetsOrigin Served{{CDN_ORIGIN}}
Cache viaBehaviors Static Runnerassets: 1yr, API: no-cache, HTML: 5min
HTTPS OnlyYes
WAF Integration TBD — requires domain + CloudFront setup{{WAF_INTEGRATION}}

Note: CDN and WAF integration planned for v1.0 launch via CloudFront + AWS WAF.


4. Compute

4.1 Container Orchestration

Platform: AWS{{ORCHESTRATION}} App Runner (managed container service — no cluster management)

Component Configuration Notes
Service ARNCluster arn:aws:apprunner:eu-west-1:324480209768:service/drop-web/8e45b0d335304487a1880f4e32d6aeec{{CLUSTER_SPEC}} Production
Node Groups{{NODE_GROUPS}}
Min Nodes{{MIN_NODES}}
Max Nodes{{MAX_NODES}}
Node Size{{NODE_SIZE}}
Container Registry ECR 324480209768.dkr.ecr.eu-west-1.amazonaws.com/drop-web{{REGISTRY}}
Service URLhttps://9ef3szvvsb.eu-west-1.awsapprunner.comProduction
Base Imagenode:22-alpineMulti-stage build
Non-root usernextjs (UID 1001)Security hardened
Exposed Port3000
Entrypointnode server.jsNext.js standalone

4.2 Serverless Functions

No serverless

functionscurrently.AlllogicisinNext.jsAPIroutesservedbyAppRunner.

Function Trigger Memory Timeout Purpose
{{FUNCTION_1}} {{TRIGGER}}{{MEMORY}}MB{{TIMEOUT}}s{{PURPOSE}}

4.3 Instance Sizing & Auto-Scaling

for
Service PlatformInstance Type Min Max Scale Trigger
drop-web{{SERVICE}} App Runner{{INSTANCE}} 1{{MIN}} Auto{{MAX}} ManagedCPU by> App{{CPU}}% Runner
drop-stagingFly.io0AutoScales to zero when idle, auto-start on request{{DURATION}}min

AppScale-Out Runner Auto-Scaling:Policy: Managed{{SCALE_OUT}} by minimumScale-In inPolicy: production,{{SCALE_IN}} scale-to-zero inScale-In stagingCooldown: (Fly.io).{{COOLDOWN}}min


5. Storage

5.1 Database Hosting

Database Engine Version Hosting Instance Storage HA
drop-db (prod){{DB_NAME}} PostgreSQL{{ENGINE}} 16{{VERSION}} AWS RDS{{HOSTING}} db.t4g.micro{{INSTANCE}} TBD — requires RDS console check{{STORAGE}}GB Single-AZ (multi-AZ upgrade planned)
drop-staging (staging)SQLite (better-sqlite3 12.6.2)Fly.io ephemeral volumeNo HA{{HA}}

Connection Pooling: Application-level{{POOL_TOOL}} (no PgBouncer currently) Max Connections: Default RDS db.t4g.micro (~85 connections){{MAX_CONN}} Connection String: Stored in AWS Secrets Manager{{SECRET_LOCATION}} (DATABASE_URL); never hardcoded. DB Endpoint: drop-db.czu2qe4quy4v.eu-west-1.rds.amazonaws.com:5432 DB Name: dropapp DB User: dropuserhardcoded)

5.2 Object Storage

No S3

bucketscurrently.Droppass-throughpaymentappnouserfileuploads.

Bucket is/ aContainer Purpose Access Lifecycle Encryption
{{BUCKET_NAME}}{{PURPOSE}}{{ACCESS}}{{LIFECYCLE}}AES-256

5.3 File Storage

Storage Type Mount Point Purpose Size
Fly.io Volume (staging){{STORAGE_NAME}} Persistent volume{{TYPE}} /app/data{{MOUNT}} SQLite database + backups{{PURPOSE}} 1GB{{SIZE}}GB

6. Security

6.1 Network Security Groups / Firewall Rules

Security Group Direction Port Protocol Source / Destination Purpose
App Runnersg-alb Inbound 443 TCP 0.0.0.0/0 HTTPS from internet
App Runnersg-alb Outbound 5432{{APP_PORT}} TCP RDS endpointsg-app PostgreSQLForward to app
sg-appInbound{{APP_PORT}}TCPsg-albFrom load balancer
sg-appOutbound{{DB_PORT}}TCPsg-dbDatabase access
App Runnersg-db OutboundInbound 443{{DB_PORT}} TCP 0.0.0.0/0sg-app ExternalFrom APIsapplication (Sumsub, BankID, Slack)only

6.2 WAF Configuration

WAF Provider: None currently (planned for v1.0 via CloudFront + AWS WAF)

Planned rules:{{WAF_PROVIDER}}

Rule Group Purpose Action
AWSManagedRulesCommonRuleSet OWASP Top 10 Block
AWSManagedRulesSQLiRuleSet SQL injection Block
AWSManagedRulesKnownBadInputsRuleSetKnown bad inputsBlock
Rate limiting 100{{RATE_LIMIT}} req/5min per IP Count → Block

Current mitigation: Application-level rate limiting in middleware.ts using rate_limits DB table (10 req/min on auth endpoints, 120 req/min on public rate endpoints).

6.3 Secrets Management

Secret Store: AWS{{SECRET_STORE}} Secrets Manager (auto-detected via AWS_SECRET_ARN env var) with fallback to environment variables.

API
Secret Rotation Schedule Access
DATABASE_URLDatabase (PostgreSQL credentials)credentials 90 days App Runner role only
JWT_SECRET 90keys daysApp Runner role only
SLACK_WEBHOOK_URL(third-party) On compromise App Runner role only
BANKID_CLIENT_SECRETPer BankID policyApp Runner role only
SUMSUB_API_KEY180 daysApp Runner role only
TLS certificates Auto-renewed60 bydays Appbefore Runnerexpiry AWSDeploy managedrole only
JWT signing key365 daysAuth service only

Caching: Secrets cached in-memory for 5 minutes (configurable TTL via initSecrets()).

6.4 IAM Roles & Policies

deployments
Role Trusted By Key Permissions Purpose
App Runner task role{{APP_ROLE}} AWSEC2 App/ RunnerECS Task secretsmanager:GetSecretValue,SecretsManager:GetSecret, ecr:GetAuthorizationTokenS3:GetObject Application runtime
GitHub Actions deploy roleGitHub Actions (OIDC)apprunner:StartDeployment, ecr:PushImage, ecr:BatchCheckLayerAvailability{{DEPLOY_ROLE}} CI/CD ECR:PushImage, ECS:UpdateServiceDeployments
RDS monitoring role{{BACKUP_ROLE}} arn:aws:iam::324480209768:role/rds-monitoring-roleLambda / Cron rds:DescribeDBInstances,RDS:CreateSnapshot, CloudWatch metricsS3:PutObject RDS enhanced monitoringBackups

7. Cost Estimation

500MBfree,then
Component Service Spec Est. Monthly Cost
Compute AWS App Runner{{SERVICE}} 0.25 vCPU / 0.5GB (auto-scale){{SPEC}} ~$25–50{{COST}}
Database AWS RDS PostgreSQL{{SERVICE}} db.t4g.micro, Single-AZ{{SPEC}} ~$15–25{{COST}}
ContainerLoad RegistryBalancer AWS ECR{{SERVICE}} First{{SPEC}} ${{COST}}
CDN{{SERVICE}}{{TRAFFIC}}GB transfer$0.10/{{COST}}
Storage{{SERVICE}}{{CAPACITY}}GB ~$1–5{{COST}}
Monitoring CloudWatch Logs{{SERVICE}} App{{METRICS}} Runner logs~$2–5
BetterStackUptime monitoringFree tier (3 monitors)metrics $0{{COST}}
Total ~$43–85/month{{TOTAL}}

Cost Optimization Notes:

  • App Runner scales to minimum instances in off-peak; staging (Fly.io) scales to zero.
  • db.t4g.micro is ARM-based Graviton — 20% cheaper than equivalent x86 instance.
  • Reserved instance / savings plan upgrade planned at v1.0 launch.

8. High Availability Design

v1.0
Component HA Strategy Failover Time Notes
Application (App Runner) AppMulti-AZ, RunnerN+1 managed multi-instanceinstances Immediate (ELB health check driven)check) App Runner restarts unhealthy instances automatically
Database (RDS)Single-AZ with automated backups30 min (snapshot restore) Multi-AZ plannedwith forauto-failover 60-120 secondsDNS propagation
Staging (Fly.io)Cache SingleCluster machine,mode scale-to-zero/ Replication Auto-start30 on requestseconds NoRedis HA for stagingSentinel
External servicesCDN N/AGlobal edge no Drop-managed HAnetwork Depends on provider SLATransparent Sumsub,Provider BankID managed externallyHA

RTO Target: 10{{RTO}} minutes (App Runner restart) / 30 minutes (RDS restore) RPO Target: 5{{RPO}} minutes (RDS PITR) / 24 hours (daily snapshot)


9. Multi-Region Considerations

Current: Single-region{{REGION_STRATEGY}} (eu-west-1 Ireland) Primary Region: eu-west-1 (Ireland){{PRIMARY_REGION}} Secondary Region: eu-north-1{{SECONDARY_REGION}} (Stockholm) — manual failover target only

Rationale: Single-region{{MULTI_REGION_RATIONALE}}

deployment appropriate for MVP/pre-production phase. Norwegian users served well from eu-west-1. GDPR data residency satisfied within EU. Cost-benefit favours backup-based recovery over active-active replication at current scale.

Data Replication: None{{REPLICATION_STRATEGY}} (manual snapshot copy to eu-north-1 on region failover) Failover Procedure: See disaster-recovery-plan.md



Approval

Role Name Date Signature
Author Platform Architect (AI) 2026-02-23
Reviewer
Approver Alem Bašić