HR-ATS-Portal/docs/aws-production-cost-estimat...

42 KiB
Raw Blame History

AWS Production Cost Estimate — Utopia Brands HR/ATS Portal

Status: Draft for budget approval. Prepared 2026-08-04. Platform: Amazon Web Services only. Every service, alternative and price in this document is AWS. Prepared against: docs/architecture/ (the signed-off-pending architecture package) and the current state of the repository.


0. The number, up front

Scenario Monthly (on-demand) Monthly (with 1-yr commitments) Annual (committed)
A — Lean (single-AZ, Spot workers, accepts downtime) $454 $404 $4,850
B — Recommended (Multi-AZ, HA, the sizing the architecture specifies) $957 $814 $9,770
C — Scale (3× volume: 100k+ applications/yr, ~75 concurrent users) $2,800 $2,380 $28,560

Add staging (~$220/mo, or ~$140/mo with an off-hours shutdown schedule) and AWS Support (Developer $29/mo, Business ~$130/mo at this spend).

Recommended budget line: $1,206/month all-in ($957 prod + $220 staging + $29 support) = ~$14,470 in year 2 on-demand, ~$12,760 with 1-year commitments.

Year 1 is lower because production does not exist for the first ~7 months. See §10.

Unit economics. At Option B and the architecture's midpoint volume of 40,000 applications/year, infrastructure costs $0.29 per application processed, or $15.25 per named seat per month across 66 seats.


1. What this document prices, and on what basis

Every sizing input below is taken from the architecture package or read directly out of the repository. Nothing is invented. Where the source itself says ASSUMPTION, that label is carried forward — those are the numbers most likely to move the total.

Input Value Source
Named seats 66 02-system-architecture.md:833 (BRD §4)
Peak concurrent users 2025 02-system-architecture.md:834ASSUMPTION
Applications per year 20,00060,000 02-system-architecture.md:835ASSUMPTION
Documents per day at peak 200600 02-system-architecture.md:836ASSUMPTION
Blob volume, year one well under 1 TB 02-system-architecture.md:837ASSUMPTION
Candidate rows, several years 10⁴10⁵ 02-system-architecture.md:838ASSUMPTION
Queue throughput hundreds of jobs/hour 02-system-architecture.md:839
Web process 2 vCPU / 4 GB, autoscale 14 02-system-architecture.md:439
Worker process 2 vCPU / 4 GB, concurrency 4, autoscale 13 02-system-architecture.md:440
Database PostgreSQL 16, 2 vCPU / 8 GB, PITR, 14-day backups 02-system-architecture.md:445
Cache Redis — cache, rate limit, sessions. Never a broker 02-system-architecture.md:447
Queue PostgreSQL-backed (procrastinate). No Redis broker, no Kafka ADR 0004
Environments local (docker compose), staging, production. No per-developer cloud env 02-system-architecture.md:490-496
Max upload size 25 MB per file 06-api-boundaries.md:1126ASSUMPTION
Audit retention 7 years, WORM/immutable archive 02-system-architecture.md:1102ASSUMPTION

The volume figures are modest. This is a 66-seat internal system, not a public SaaS. The cost model reflects that: the dominant lines are the database and the always-on network plumbing, not compute or storage.


2. Azure → AWS service mapping

ADR 0012 recommends Azure on the strength of assumption A1 (Utopia Brands runs Microsoft 365, so Entra ID and Graph co-locate). The same document states plainly that "the architecture is unchanged and the equivalent AWS or GCP services substitute directly" (02-system-architecture.md:950-956). This is that substitution.

Architecture calls for Azure (ADR 0012) AWS equivalent used here Note
Container platform, one image / two revisions Container Apps ECS on Fargate — one task definition family, two services (web, worker) Closest 1:1 fit. See §11 for why not App Runner / EKS / EC2
Managed PostgreSQL 16 Flexible Server RDS for PostgreSQL 16 (Graviton) All required extensions available: pg_trgm, unaccent, btree_gist, pgcrypto, pgvector
Object storage for CV blobs Blob Storage S3 The architecture already names S3 as "the default" if the cloud decision moves to AWS (04-integrations-and-processing.md:1062)
Immutable audit archive Immutable blob container S3 Object Lock (Compliance mode) _decisions.md names Object Lock by name for this purpose
Cache / rate limit / sessions Managed Redis ElastiCache for Valkey (Redis-compatible) Valkey is ~20% cheaper than the Redis OSS engine for the same node
Secret store, no keys in code Key Vault Secrets Manager (rotating) + SSM Parameter Store (non-secret config, free) Accessed via ECS task role — no static credentials
Managed identity Managed Identity IAM roles for tasks (IRSA-equivalent) Same "no keys anywhere" property
SSO Entra ID Entra ID, unchanged — federated to AWS via IAM Identity Center (OIDC) for console access The application keeps using Entra as its IdP. AWS does not replace it
Careers mailbox Microsoft Graph Microsoft Graph, unchanged Graph is an M365 service, not a cloud-platform service. Only the egress path changes (NAT Gateway)
CDN + TLS + WAF Front Door CloudFront + ACM + AWS WAF ACM certificates are free
Load balancer Container Apps ingress Application Load Balancer Required in front of ECS
Container registry ACR ECR
Log workspace Log Analytics CloudWatch Logs
Malware scanning ClamAV in worker image, or Defender for Storage GuardDuty Malware Protection for S3 See §7 — cheaper and better than ClamAV at this volume
OCR (unspecified) Amazon Textract as fallback behind free local parsers
AI provider contracted API provider under DPA Amazon Bedrock Satisfies the 05-security T-13 requirement directly: zero retention, no training on customer data, in-region processing, and it is inside the same account boundary
Error tracking Sentry (self-hosted or EU) Sentry remains a third-party SaaS. AWS-native alternative: CloudWatch Application Signals + X-Ray Priced as CloudWatch below; Sentry SaaS is out of AWS scope

Two things AWS improves over the Azure plan, at no extra cost:

  1. Bedrock resolves open item BL-3. 05-security-rbac-ai-governance.md:720 flags AI provider data handling as an unresolved legal blocker requiring a DPA with zero-retention and no-training terms. Bedrock provides exactly that contractually, inside the customer's own AWS account, under the existing AWS agreement — no new vendor, no new DPA negotiation, no new data processor.
  2. Object Lock is native. _decisions.md layer 4 requires a write-once audit archive. S3 Object Lock in Compliance mode is the reference implementation of that requirement.

3. Target architecture on AWS

graph TB
  subgraph EDGE["Edge — public"]
    R53["Route 53<br/>DNS + health checks"]
    CF["CloudFront<br/>static bundle + /api behaviour<br/>1 TB/mo egress free"]
    WAF["AWS WAF<br/>managed rules + Bot Control<br/>on the careers form"]
    ACM["ACM<br/>TLS certs — free"]
  end

  subgraph VPC["VPC — 2 Availability Zones"]
    subgraph PUB["Public subnets"]
      ALB["Application Load Balancer"]
      NAT["NAT Gateway x2<br/>outbound to Graph + job boards"]
    end
    subgraph PRIV["Private subnets — no inbound from internet"]
      WEB["ECS Fargate service: web<br/>2 vCPU / 4 GB<br/>desired 2, autoscale 2-4"]
      WRK["ECS Fargate service: worker<br/>2 vCPU / 4 GB<br/>desired 1, autoscale 1-3"]
      MIG["ECS RunTask: migrate<br/>one-off, per deploy"]
    end
    subgraph DATA["Data — private, encrypted at rest"]
      RDS[("RDS PostgreSQL 16<br/>db.m7g.large Multi-AZ<br/>PITR 14 days")]
      EC[("ElastiCache for Valkey<br/>cache.t4g.small x2<br/>cache / rate limit / sessions")]
    end
    VPE["S3 Gateway Endpoint<br/>FREE — keeps CV traffic off NAT"]
  end

  subgraph STORE["Storage & AI — regional services"]
    S3A[("S3: candidate-documents<br/>SSE-KMS, versioning off,<br/>7-day soft delete")]
    S3B[("S3: audit-archive<br/>Object Lock COMPLIANCE<br/>Glacier Instant Retrieval")]
    S3C[("S3: static frontend<br/>OAC-restricted to CloudFront")]
    BR["Amazon Bedrock<br/>ATS scoring + chatbot"]
    TX["Amazon Textract<br/>OCR fallback only"]
    GD["GuardDuty<br/>Malware Protection for S3"]
  end

  subgraph OPS["Security & ops"]
    SM["Secrets Manager<br/>Graph creds, DB password"]
    KMS["KMS<br/>4 customer-managed keys"]
    CW["CloudWatch<br/>logs, metrics, alarms"]
    CT["CloudTrail + AWS Config"]
    ECR["ECR<br/>one image, keep last 10"]
  end

  EXT["Microsoft Graph<br/>careers mailbox + Entra ID SSO"]

  R53 --> CF
  WAF --> CF
  ACM -.-> CF
  CF --> S3C
  CF --> ALB
  ALB --> WEB
  WEB --> RDS
  WEB --> EC
  WEB --> VPE
  WRK --> RDS
  WRK --> VPE
  WRK --> NAT
  WEB --> NAT
  NAT --> EXT
  VPE --> S3A
  RDS -.->|"LISTEN / NOTIFY"| WRK
  RDS -->|"nightly closed-partition export"| S3B
  S3A --> GD
  WRK --> BR
  WRK --> TX
  WEB --> BR
  WEB --> SM
  WRK --> SM
  KMS -.-> S3A
  KMS -.-> RDS
  WEB --> CW
  WRK --> CW
  MIG --> RDS
  ECR -.-> WEB
  ECR -.-> WRK

  classDef free fill:#eafff4,stroke:#004d43,stroke-width:2px
  classDef costly fill:#fff4e6,stroke:#a35200,stroke-width:2px
  class VPE,ACM free
  class RDS,NAT,BR costly

Green = free and load-bearing. Amber = the three lines that dominate the bill.


4. Pricing basis and honesty statement

Region priced us-east-1 (N. Virginia) — AWS's cheapest major region, used as the baseline
Prices AWS public list prices, on-demand, as of August 2026
Excludes Taxes/VAT, Enterprise Discount Program terms, AWS Marketplace software, third-party SaaS (Sentry, GitHub), staff time, and one-time engineering effort
Hours/month 730
Currency USD

⚠️ These figures were compiled from published AWS list pricing, not from a live query against the AWS Pricing API. Before this document is used to commit spend, re-run every line through the AWS Pricing Calculator for the region actually chosen. Expect individual line items to move by a few percent; expect the total to land within ±10% of Option B.

4.1 Region multiplier — the single largest lever on this number

Region is not yet decided. 02-system-architecture.md:502 records that postings span six jurisdictions and that residency is unresolved (BRD OQ-4). Region choice moves the total by up to 30%.

Region Multiplier vs us-east-1 Option B monthly When you would choose it
us-east-1 N. Virginia 1.00× $957 Cheapest; no EU/UK residency guarantee
us-west-2 Oregon 1.00× $957 Same price, better DR pairing with us-east-1
eu-west-1 Ireland ~1.06× ~$1,015 GDPR residency, English-language jurisdiction
eu-central-1 Frankfurt ~1.12× ~$1,072 Strictest GDPR posture
eu-west-2 London ~1.10× ~$1,053 UK data residency
ap-south-1 Mumbai ~0.97× ~$928 Cheapest of the non-US options
me-central-1 UAE ~1.25× ~$1,196 Only if Gulf residency is mandated

Recommendation: if residency is genuinely unconstrained, use us-east-1. If any of the six jurisdictions is in the EU/UK — which is likely — use eu-west-1 and budget ~$1,015/mo for Option B. Do not split across regions; the architecture forbids it (02-system-architecture.md:66 — "no region column, no tenant module").


This is the configuration the architecture actually specifies, deployed with the availability posture a system holding candidate PII under legal retention obligations should have.

5.1 Compute

Line Configuration Calculation $/mo
ECS Fargate — web 2 tasks × 2 vCPU / 4 GB, 24×7 2 × (2 × $0.04048 + 4 × $0.004445) × 730 144.16
ECS Fargate — worker 1 baseline, bursts to 3; avg 1.2 tasks × 2 vCPU / 4 GB 1.2 × $0.09874 × 730 86.50
ECS Fargate — migrate One-off task per deploy, ~3 min, ~30 deploys/mo 30 × 0.05 h × $0.09874 0.15
Application Load Balancer 1 ALB + ~2 LCU average $0.0225 × 730 + 2 × $0.008 × 730 28.11
Compute subtotal $258.92

Two web tasks is not padding — it is the minimum for a zero-downtime rolling deploy and for surviving the loss of one Availability Zone. The architecture's "14 replicas" describes the autoscaling range; the floor for production HA is 2.

5.2 Data

Line Configuration Calculation $/mo
RDS PostgreSQL 16 db.m7g.large (2 vCPU / 8 GB, Graviton3), Multi-AZ 2 × $0.1733 × 730 253.02
RDS storage 100 GB gp3, Multi-AZ (billed on both instances) 100 × $0.23 23.00
RDS backup storage PITR 14 days; ~150 GB beyond the free allowance 150 × $0.095 14.25
ElastiCache for Valkey cache.t4g.small × 2 (primary + replica, Multi-AZ) 2 × $0.0324 × 730 47.30
S3 — candidate documents 500 GB S3 Standard (end-of-year-1 projection) 500 × $0.023 11.50
S3 — requests ~30k PUT + ~120k GET/mo 30 × $0.005 + 120 × $0.0004 0.20
S3 — audit archive 60 GB, Glacier Instant Retrieval, Object Lock Compliance 60 × $0.004 0.24
S3 — static frontend ~200 MB negligible 0.01
Data subtotal $349.52

Why db.m7g.large and not a burstable db.t4g.large (which would save $158/mo Multi-AZ): the worker process runs sustained CPU-bound parsing and batch rescoring against this database. A burstable instance that exhausts its CPU credits during a bulk-import or rescore batch degrades the interactive path at the same time — exactly the coupling 02-system-architecture.md:6.1 splits the processes to avoid. db.t4g.large is priced in Option A and is a legitimate choice while volumes stay at the low end of the assumed range; it is not the right default.

Storage grows. At 40,000 applications/year × ~0.5 MB average CV, blob storage grows ~20 GB/month. By year 3 that is ~1.2 TB (~$28/mo). This line is not a budget risk — S3 is the cheapest thing here.

5.3 Edge and network

Line Configuration Calculation $/mo
CloudFront ~200 GB egress/mo — inside the perpetual 1 TB/mo free tier 0 0.00
AWS WAF — web ACL 1 ACL + 4 managed rule groups $5.00 + 4 × $1.00 9.00
AWS WAF — requests ~2M requests/mo 2 × $0.60 1.20
AWS WAF — Bot Control Targeted at the public careers form only $10.00 + 2 × $1.00 12.00
Route 53 1 hosted zone + ~2M queries $0.50 + 2 × $0.40 1.30
NAT Gateway 2 AZ (HA) — required for Graph, job boards, ECR 2 × $0.045 × 730 65.70
NAT data processing ~120 GB (S3 traffic excluded via Gateway Endpoint) 120 × $0.045 5.40
S3 Gateway VPC Endpoint FREE — and it is what keeps the NAT bill small 0 0.00
Data transfer out (non-CloudFront) ~20 GB 20 × $0.09 1.80
ACM certificates Public certs for CloudFront/ALB free 0.00
Edge & network subtotal $96.40

The NAT Gateway is the most-underestimated line in any AWS estimate, and at $71/mo it is the third-largest item here — more than the entire cache tier. Two design decisions keep it from being much worse:

  • The free S3 Gateway Endpoint is mandatory, not optional. Every CV upload, download and virus-scan read flows to S3. Routed through NAT instead, that traffic alone would add ~$25/mo at year-1 volume and scale linearly with document count. Configure it on day one.
  • CloudFront's 1 TB/month free egress tier covers this workload entirely. A 66-seat internal tool plus a careers site will not approach 1 TB. Serving the frontend from S3 through CloudFront is therefore genuinely free, and cheaper than serving it from the ALB.

Lean alternative: a single NAT Gateway saves $32.85/mo but means a worker in the failed AZ loses all outbound connectivity — Graph polling stops until ECS reschedules the task into the healthy AZ. Given that 04-integrations-and-processing.md promises zero documents lost with reconciliation, and that intake is idempotent and replayable, one NAT is defensible. It is priced in Option A.

5.4 Security and operations

Line Configuration Calculation $/mo
Secrets Manager ~10 secrets (DB, Graph client secret, webhook clientState, job-board keys) + API calls 10 × $0.40 + calls 4.50
SSM Parameter Store Non-secret config (Standard tier) free 0.00
KMS 4 customer-managed keys (S3 docs, S3 audit, RDS, Secrets) + requests 4 × $1.00 + $1.00 5.00
ECR ~20 GB across the last 10 image tags 20 × $0.10 2.00
CloudWatch Logs ~15 GB/mo ingest + ~60 GB retained 15 × $0.50 + 60 × $0.03 9.30
CloudWatch metrics + alarms 50 custom metrics + 30 alarms 50 × $0.30 + 30 × $0.10 18.00
CloudTrail Management events free; S3 data events on the document buckets 2.00
AWS Config Compliance recording, ~10 rules 10.00
GuardDuty Account-level threat detection + Malware Protection for S3 see §7 25.00
AWS Backup RDS snapshot copies to a second region for DR 8.00
Security & ops subtotal $83.80

5.5 AI and document services (usage-based)

These are the only lines that scale with business volume rather than with time. They are also the only lines with genuinely unbounded downside if left unmonitored — see §8.

Line Basis Calculation $/mo
Bedrock — ATS scoring 3,300 applications/mo (40k/yr midpoint), Claude Sonnet 4.5, prompt caching on the job-description prefix 3,300 × $0.0214 70.62
Bedrock — chatbot ~4,400 queries/mo (20 active users × 10/day × 22 days), Sonnet 4.5, cached system prompt + tool schemas 4,400 × $0.0147 64.68
Bedrock — rescore batches Model/config version changes trigger full re-evaluation (05-security:519); amortised 25.00
Amazon Textract OCR fallback only — ~5,000 pages/mo after free local parsers 5 × $1.50 7.50
Amazon SES ~5,000 transactional emails/mo (optional; Graph handles most outbound) 5 × $0.10 0.50
AI subtotal $168.30

Token model used for scoring, per call: ~1,200 cached prefix tokens (system prompt + job version) at $0.30/M, ~3,000 fresh input tokens (redacted CV body) at $3.00/M, ~800 output tokens (score + rationale + skill matches) at $15.00/M.

5.6 Option B total

Group $/mo
Compute 258.92
Data 349.52
Edge & network 96.40
Security & ops 83.80
AI & document services 168.30
Production total, on-demand $956.94
Production total, with 1-yr commitments (§9) $814

6. Option A (lean) and Option C (scale)

6.1 Option A — Lean: $454/mo

Everything single-AZ. Suitable for a pilot or a first quarter in production while volumes are proven. Not suitable as a permanent posture for a system under 7-year audit retention.

Change from Option B Saving
web 1 task instead of 2 (deploys cause a brief outage) $72.08
worker on Fargate Spot (~70% off; safe — every task is idempotent and retried) $60.55
RDS db.t4g.large Single-AZ instead of db.m7g.large Multi-AZ $174.16
ElastiCache single cache.t4g.micro, no replica (degrades gracefully per 02:820) $35.62
1 NAT Gateway instead of 2 $32.85
No WAF Bot Control $12.00
Reduced CloudWatch metrics/alarms, no AWS Config, no cross-region backup $21.00
Claude Haiku 4.5 for ATS scoring instead of Sonnet 4.5 ($1/M in, $5/M out) $47.00
Smaller storage footprint in month 16 $8.00
Total saving $463
Option A total $454/mo

What you give up, stated plainly: RTO on an AZ failure goes from seconds to roughly 2040 minutes (RDS single-AZ restore). Every deploy is a short outage. Scoring quality drops somewhat with Haiku — acceptable, because 05-security mandates that AI output is advisory only and no automatic path reaches a terminal-negative outcome, so a weaker model cannot reject anyone.

6.2 Option C — Scale: $2,800/mo

Priced at 3× the assumed volume — 100,000+ applications/year, ~75 concurrent users — which is where 02-system-architecture.md:10.2's scaling ladder steps 2, 3, 4 and 6 have all been climbed.

Line Configuration $/mo
Fargate web 4 tasks × 4 vCPU / 8 GB 576.64
Fargate worker avg 2.5 tasks × 4 vCPU / 8 GB 360.40
ALB higher LCU 45.00
RDS db.m7g.xlarge Multi-AZ + one db.m7g.large read replica for analytics and search 632.00
RDS storage + backups 300 GB 109.00
ElastiCache cache.m7g.large × 2 230.00
S3 2 TB + requests 47.00
WAF / Route 53 / CloudFront egress still under the free tier 25.00
NAT × 3 AZ + data 110.00
CloudWatch / GuardDuty / Config / Secrets / KMS / ECR 160.00
Bedrock 3× scoring + chatbot volume 480.00
Textract + SES 24.00
Option C total $2,799

Note that a 3× volume increase produces a ~2.9× cost increase — this architecture scales close to linearly, with no step-function cliff. The read replica at step 6 of the ladder is the realistic ceiling; 02-system-architecture.md:875 projects that PostgreSQL FTS + trigram will never be outgrown by this system (the extraction trigger sits 34 orders of magnitude away).


7. Malware scanning — a place where AWS is both cheaper and better

04-integrations-and-processing.md:1109 selects ClamAV in the worker image for Phase 1 and states its own limitation honestly: "ClamAV's detection rate on targeted or novel malware is materially below a commercial multi-engine service. It is a hygiene control, not a guarantee." It then names the upgrade path — a cloud-native scanner behind the same MalwareScanner port (§7.4).

On AWS that upgrade is available immediately, at trivial cost:

Option Monthly cost at 9,000 documents / 4.5 GB Detection quality Operational burden
ClamAV in the worker image $0 direct — but adds ~400 MB to the image (toward the 2 GB T1 trigger), needs a signature-update job, and consumes worker CPU Signature-based only Signature freshness is your problem
GuardDuty Malware Protection for S3 4.5 GB × $0.60 + 9 × $0.187 ≈ $4.38 AWS-managed multi-engine, continuously updated Zero — event-driven on s3:ObjectCreated

Recommendation: use GuardDuty Malware Protection for S3. It costs about $4/month at this volume, removes a dependency from the image, removes the signature-update scheduled job from the 24-job catalogue, keeps the worker CPU free for parsing, and gives strictly better detection. It fits the existing MalwareScanner port without changing anything above it — the adapter writes the verdict into virus_scan_status exactly as ClamAvScanner would, and the quarantine-prefix rule at 04-integrations-and-processing.md:635 is unchanged.

The $25 GuardDuty line in §5.4 covers this plus account-level threat detection (VPC flow log, DNS and CloudTrail analysis), which is worth having on its own.


8. Bedrock cost sensitivity — the only line that can surprise you

Everything else in this estimate is bounded by an instance size. Bedrock is bounded only by how many times the application calls it. This table is the one to keep.

Scenario Scoring model Chatbot model Caching Apps/mo Chat queries/mo $/mo
Floor Haiku 4.5 Haiku 4.5 on 1,700 2,000 $26
Lean (Option A) Haiku 4.5 Sonnet 4.5 on 3,300 4,400 $88
Baseline (Option B) Sonnet 4.5 Sonnet 4.5 on 3,300 4,400 $160
No caching Sonnet 4.5 Sonnet 4.5 off 3,300 4,400 $209
High volume Sonnet 4.5 Sonnet 4.5 on 5,000 8,000 $258
Worst realistic Sonnet 4.5 Sonnet 4.5 off 5,000 12,000 $412
Runaway (no guardrails) Sonnet 4.5 Sonnet 4.5 off rescore loop unbounded unbounded

Four controls that must exist before Bedrock is enabled in production

  1. The kill switch already in the design. 05-security:376 specifies a config setting that disables all provider calls and degrades the product. Wire it to a CloudWatch billing alarm.
  2. Idempotency on rescore batches. 06-api-boundaries.md:263 already requires an idempotency key on any POST that enqueues an async job, with the key doubling as the procrastinate queueing lock. This is what prevents an impatient double-click from costing $200.
  3. AWS Budgets with an action. Set a $300/mo Bedrock budget with an SNS alert at 80% and an IAM action at 100%. Costs nothing.
  4. Enable prompt caching from day one. It is a request parameter, not a project. On the chatbot path — where the system prompt and tool schemas are a large fixed prefix — it cuts cost ~40%.

Explicitly do NOT buy Bedrock Provisioned Throughput

Provisioned Throughput is priced per model-unit-hour and starts in the range of $4060/hour (~$30,000+/month for a single unit on a 1-month commitment). At this workload's volume that is roughly 190× more expensive than on-demand token pricing. It exists for sustained high-throughput inference. Use on-demand token pricing. If anyone proposes Provisioned Throughput for this system, the answer is no.


9. Commitment discounts — what to buy, and when

Do not buy any commitment until production has run for 30 days and the usage baseline is real. Then:

Commitment Applies to Discount Monthly saving Risk
Compute Savings Plan, 1-yr, no upfront Fargate web + worker (and any future Lambda/EC2) ~20% $46 Low — it is compute-generic, not service-locked
RDS Reserved Instance, 1-yr, no upfront db.m7g.large Multi-AZ ~33% $83 Medium — locks the instance class for 12 months
ElastiCache Reserved Node, 1-yr, no upfront cache.t4g.small × 2 ~30% $14 Low
S3 Intelligent-Tiering Candidate documents older than 90 days ~40% on aged objects $3 now, grows with volume None — automatic
Total $146/mo

Do not take 3-year terms in year one. The volume assumptions carry an explicit ASSUMPTION label (02-system-architecture.md:1218, risk A3: "Bulk job-board feeds could be 12 orders higher"). A 3-year RDS RI at ~52% off saves another $50/mo and would be the wrong trade against a sizing assumption the architecture itself flags as unvalidated.


10. Year-1 cash flow — production does not exist for seven months

07-implementation-plan.md §15.3 states plainly that Phase 1 alone is 2430 weeks and that within the first month what can be demonstrated is Phase 0 output plus the beginnings of the Phase 1 spine — not a working ATS. Budgeting a full production environment from month one would overstate year-1 spend by roughly $6,000.

Period What exists $/mo Subtotal
Months 12 Phase 0. Local docker compose only (02:960). AWS = an account, ECR, and IAM Identity Center $50 $100
Months 37 Staging live, auto-deploying on merge to main (02:493). Real test-mailbox traffic $249 $1,245
Months 812 Production live + staging + Developer support $1,206 $6,030
Year 1 total $7,375
Year 2 total (12 × $1,063 committed) $12,756
Year 3 (volume growth, ~1.2 TB storage, +15%) ~$14,700

Staging environment detail — $220/mo

Line Configuration $/mo
Fargate web 1 task × 1 vCPU / 2 GB 36.03
Fargate worker 1 task × 1 vCPU / 2 GB 36.03
ALB 1 + minimal LCU 22.27
RDS db.t4g.medium Single-AZ + 50 GB 53.20
ElastiCache cache.t4g.micro × 1 11.68
NAT Gateway 1 AZ + data 34.85
S3 + CloudWatch + Secrets 11.15
Bedrock Mocked by default (02:492); real-credential smoke tests only 15.00
Staging total $220.21

Optimisation: stop the Fargate services and the RDS instance outside business hours with an EventBridge rule and a small Lambda (12h × 5 days = 36% of the week). Saves ~$80/mo. The ALB and NAT Gateway run 24×7 regardless — $57 of the $220 is irreducible.

No per-developer cloud environment is priced, matching 02-system-architecture.md:496: "Two developers do not need six environments; they need one that behaves like production." Each additional full environment would add ~$220/mo.


11. Alternatives considered and rejected

Option Monthly (prod-equivalent) Verdict
ECS on Fargate $231 compute Chosen. Maps 1:1 onto ADR 0012's "one image, two revisions". No servers to patch, per-second billing, native autoscaling on both HTTP metrics and queue depth
AWS App Runner ~$228 for web alone Rejected. $0.064/vCPU-hr + $0.007/GB-hr is ~55% more than Fargate for the same shape, and it has no clean model for a long-running queue-consumer process. It optimises for a request-driven service, which is exactly half of this workload
Amazon EKS +$73/mo control plane, before nodes Rejected. The architecture's binding constraint is two developers, no ops staff (02:496, _decisions.md:287). Kubernetes adds a control plane, an upgrade cadence, an add-on ecosystem and a second scheduler to reason about, for zero capability this workload uses
EC2 + Docker Compose ~$120 for 2 × t4g.medium Rejected. The cheapest option on paper and the most expensive in practice: OS patching, AMI rebuilds, log shipping and capacity management all become the two developers' problem. Saves ~$110/mo and costs several days per quarter
AWS Lambda for the worker ~$20 Rejected. The 15-minute ceiling is survivable, but the worker holds a procrastinate LISTEN/NOTIFY connection and runs multi-second CPU-bound parsing with a memory cap and restricted OS user (04:1114) — a persistent process, not an event handler
Aurora Serverless v2 $44 floor, ~$175 realistic + I/O charges Rejected as default. The worker keeps a persistent connection, so it never scales to the floor. Compute is comparable but I/O-per-request billing makes the monthly number unpredictable — the opposite of what a budget document needs. Reconsider at Option C scale with I/O-Optimized
RDS Multi-AZ cluster (2 readable standbys) ~$380 Rejected for Phase 1. ~$127/mo more than Multi-AZ instance deployment for a read-scaling capability this workload does not need until step 6 of the scaling ladder
Amazon OpenSearch for search +$150 minimum Rejected. ADR-level decision: Phase 1 search is PostgreSQL FTS + pg_trgm, and 02:875 projects the extraction trigger sits 34 orders of magnitude away. Adding OpenSearch now buys a second datastore, a second backup story and a sync problem, for nothing
Amazon MQ / MSK for the queue +$130 / +$300 Rejected. ADR 0004 makes the queue PostgreSQL-backed specifically to preserve transactional enqueue. Kafka is named in _decisions.md as explicitly out of scope for Phase 1
Bedrock Provisioned Throughput ~$30,000 Rejected. ~190× on-demand at this volume. See §8
VPC Interface Endpoints (ECR, Secrets, Logs, Bedrock) +$58/mo Rejected at this scale. 4 services × 2 AZ × $0.01/hr costs more than the NAT data processing it would displace ($5.40). The S3 Gateway Endpoint is free and is kept. Revisit interface endpoints at Option C, or if a compliance requirement forbids internet egress

12. Cost optimisation levers, ranked by saving per unit of effort

# Lever Saving Effort Do it?
1 S3 Gateway Endpoint (free) so CV traffic bypasses NAT ~$25/mo, grows with volume 5 minutes of Terraform Day one, non-negotiable
2 Serve the frontend from S3 + CloudFront, not the ALB — 1 TB/mo egress is free ~$20/mo + lower ALB LCU Already the plan Day one
3 Fargate Spot for the worker service — tasks are idempotent and retried by design ~$61/mo One line in the capacity provider strategy Yes
4 Prompt caching on all Bedrock calls ~$49/mo A request parameter Yes
5 1-yr Compute Savings Plan + RDS RI after 30 days of real baseline ~$143/mo One purchase Yes, at month 2 of production
6 Off-hours shutdown for staging (EventBridge + Lambda) ~$80/mo Half a day Yes
7 Haiku 4.5 for bulk ATS scoring, Sonnet reserved for the chatbot ~$47/mo A model-id config change; AI is advisory only, so quality risk is contained Evaluate
8 S3 Intelligent-Tiering on candidate documents $3/mo now, ~$20/mo by year 3 A bucket lifecycle rule Yes
9 Graviton everywhere (m7g, t4g, cache.t4g) ~15% vs x86, already in the estimate Build ARM64 images Already assumed — do not regress to x86
10 Single NAT Gateway $33/mo Config Only in Option A
11 CloudWatch log retention 30 days, archive to S3 beyond ~$5/mo A retention setting Yes
12 Delete the decoded_attachments/ local-disk path (see §13) Prevents an EFS line item of ~$30150/mo Real engineering work Required regardless

Levers 16 and 8 together save $381/mo — 40% of the Option B bill — and none of them changes the architecture.


13. Repository gaps that must close before this estimate holds

The cost model above assumes the application is deployable as the architecture describes. Five things in the repository today contradict that. Four are correctness problems that also have a cost consequence.

# Finding Cost consequence if not fixed
1 Attachments are written to local disk. file_decoder.py:18 sets _DEFAULT_OUT_DIR to a directory beside the source file, and views.py:40-41 stores that absolute path in Inbox_Messages.file_path. On Fargate the task filesystem is ephemeral and per-task — files vanish on restart and are invisible to the other web replica Must move to S3 (already budgeted at $11.50/mo). "Fixing" it with EFS instead adds $30150/mo ($0.30/GB-mo Standard, plus throughput) and reintroduces a shared mutable filesystem the architecture does not want
2 No Dockerfile exists. docker-compose.yml provisions only minio and postgres — the application itself is not containerised Blocks ECS entirely. Prerequisite engineering, not an AWS cost
3 Migrations run on application startup. db_setup.py:226-244lifespan calls init_db(), which runs Alembic to head when db_auto_migrate is set. With 2+ web tasks this is a concurrent-migration race on every deploy Move to the one-off ECS migrate RunTask already priced at $0.15/mo. Set db_auto_migrate=false in the task definition
4 CORS is allow_origins=["*"] with allow_credentials=True. main.py:16-22 — browsers reject this combination outright, and no CloudFront or WAF configuration compensates for it None directly, but it will look like a CDN misconfiguration and burn debugging time at go-live
5 Database credentials live in backend/.env. .gitignore correctly excludes it, but the deployment model must be Secrets Manager + ECS task role, never an env file baked into an image Already budgeted at $4.50/mo

Item 1 is the one that matters most for this document: it is the difference between an $11.50/mo storage line and a $150/mo one, and it has to be resolved before the first production deploy either way.


14. What would change this number

# Risk Direction Magnitude
1 Region is not us-east-1 (likely — six jurisdictions, GDPR unresolved) +6% to +25% (§4.1)
2 Legal requires self-hosted models instead of Bedrock. 05-security:720 (BL-3) names this: "Phase 1 gains GPU infrastructure and an MLOps burden two developers cannot absorb" ↑↑↑ A single g5.xlarge is ~$730/mo on-demand; realistic HA inference is $1,5003,000/mo, more than doubling the total
3 Bulk job-board feeds arrive. Risk A3 (02:1218) warns volumes could be "12 orders higher" ↑↑ Option C, or beyond
4 Data residency forces multi-region. Directly conflicts with the one-database constraint (_decisions.md:283) and requires a business exception ↑↑ Roughly ×1.8 — a second full stack
5 Audit retention exceeds 7 years, or the immutable archive grows faster than projected Small — Glacier Deep Archive is $0.00099/GB-mo
6 Chatbot adoption exceeds 20 active users +$15/mo per additional 1,000 queries
7 pgvector embeddings land in Phase 2 (migration 028, ~200,000 rows per 03-database-design.md:2607) +$20/mo Bedrock embeddings, +~5 GB storage. Negligible — this is exactly why the architecture put vectors in the same database
8 Volumes stay at the low end (20k applications/yr, not 60k) $80/mo
9 Enterprise Discount Program / Private Pricing, if Utopia Brands has existing AWS spend 5% to 15%

15. Recommendations

  1. Budget $1,206/month for a fully HA production plus staging plus Developer support, in us-east-1. If EU/UK residency is required — decide this before provisioning — budget $1,270/month in eu-west-1.
  2. Deploy Option A (lean, $454/mo) for the first production quarter, then move to Option B once real volume is observed. The migration between them is instance-class changes and a replica count — hours of work, no re-architecture.
  3. Adopt Amazon Bedrock. It closes open item BL-3 (05-security:720) under the existing AWS agreement, with no new data processor and no new DPA to negotiate. This is the strongest platform-specific argument for AWS in this whole document.
  4. Replace ClamAV with GuardDuty Malware Protection for S3 at ~$4/mo (§7). Better detection, smaller image, one fewer scheduled job.
  5. Configure the free S3 Gateway Endpoint on day one. It is the single highest-value free configuration change available and its value grows with document volume.
  6. Fix the local-disk attachment path (file_decoder.py:18) before the first deploy. It is the only repository finding with a real cost consequence.
  7. Set an AWS Budget of $1,400/month with alerts at 80% and 100%, plus a separate $300 Bedrock budget wired to the kill switch already specified at 05-security:376.
  8. Buy no commitments until production has 30 days of real baseline, then take 1-year no-upfront terms only. The volume assumptions are labelled ASSUMPTION for a reason.
  9. Re-validate every line in the AWS Pricing Calculator for the chosen region before this document is used to commit spend (§4).

Appendix A — Unit prices used

Service Unit Price (us-east-1, Aug 2026)
Fargate vCPU-hour / GB-hour $0.04048 / $0.004445
Fargate Spot ~70% off on-demand
ALB hour / LCU-hour $0.0225 / $0.008
RDS db.m7g.large PostgreSQL instance-hour (Single-AZ) ~$0.1733
RDS db.t4g.large / db.t4g.medium instance-hour ~$0.1296 / ~$0.0650
RDS gp3 storage GB-month (Single-AZ / Multi-AZ) $0.115 / $0.23
RDS backup beyond free tier GB-month $0.095
ElastiCache cache.t4g.small / .micro node-hour ~$0.0324 / ~$0.016
S3 Standard GB-month $0.023
S3 PUT / GET per 1,000 $0.005 / $0.0004
S3 Glacier Instant Retrieval GB-month $0.004
S3 Gateway VPC Endpoint free
CloudFront egress GB (first 10 TB, after 1 TB/mo free) $0.085
AWS WAF web ACL / rule / million requests $5.00 / $1.00 / $0.60
AWS WAF Bot Control month / million requests $10.00 / $1.00
NAT Gateway hour / GB processed $0.045 / $0.045
Route 53 hosted zone / million queries $0.50 / $0.40
Secrets Manager secret-month / 10k API calls $0.40 / $0.05
KMS key-month / 10k requests $1.00 / $0.03
ECR GB-month $0.10
CloudWatch Logs GB ingest / GB-month stored $0.50 / $0.03
CloudWatch custom metric-month / alarm-month $0.30 / $0.10
GuardDuty Malware Protection for S3 GB scanned / 1,000 objects $0.60 / $0.187
Textract DetectDocumentText 1,000 pages $1.50
SES 1,000 outbound emails $0.10
Bedrock — Claude Sonnet 4.5 1M input / 1M output tokens $3.00 / $15.00
Bedrock — Claude Sonnet 4.5 cache 1M cache write / 1M cache read $3.75 / $0.30
Bedrock — Claude Haiku 4.5 1M input / 1M output tokens $1.00 / $5.00
ACM public certificates free
SSM Parameter Store (Standard) free
CloudTrail management events (first trail) free

Appendix B — Cost allocation tags

Apply these on every resource from day one; retrofitting tags is the reason most AWS bills are unattributable.

Tag Values
Project hr-ats-portal
Environment production | staging
Component web | worker | database | cache | storage | edge | ai | observability
CostCentre (Utopia Brands HR)
Owner talha | ahmed
DataClass candidate-pii | audit | public

Activate them as cost allocation tags in the Billing console — they are not usable in Cost Explorer until you do, and activation is not retroactive.