Skip to content

010. Network topology: no IGW, no NAT (for now)

  • Date: 2026-06-03
  • Status: Proposed
  • In the context of running rag-sample as a personal demo with per-environment VPCs (RAG001), Aurora Serverless v2 at min_capacity = 0, and a tight idle-cost target,
  • facing the choice of what egress paths the VPC offers to anything that might run inside it, and what entry paths it offers to anything outside,
  • we decided for a “for now” preference of zero VPC-internet boundary infrastructure: no Internet Gateway, no NAT Gateway, no PrivateLink endpoints, in either QA or Prod; the per-env VPC carries only private subnets, an Aurora subnet group, and a security group for the Aurora cluster,
  • and neglected
    • NAT Gateway in each VPC, which is the standard pattern for letting in-VPC workloads reach AWS service public endpoints, at roughly $32 per AZ per month idle plus data-transfer fees, doubled across two environments,
    • PrivateLink interface endpoints per AWS service (Bedrock runtime, Secrets Manager, possibly Aurora Data API if a future component runs in-VPC), at roughly $7.30 per AZ per service per month, scaling linearly with both the service list and the AZ count,
    • a public-subnet posture with workloads given public IPs, which avoids NAT but broadens the inbound attack surface for no operational gain at this scale,
  • to achieve
    • the scale-to-zero idle cost the demo depends on; the alternatives below are the largest single fixed-cost lines this stack would carry,
    • a minimal network-control-plane surface to reason about, which suits a single operator,
    • the freedom to defer the VPC-vs-no-VPC posture of each application component until that component actually exists; today the only thing inside the VPC is the Aurora cluster itself,
  • accepting
    • that any application component that needs to run inside the VPC is blocked from reaching AWS services until this decision is revisited; this is enforced by absence, not by policy,
    • that the Aurora cluster is reachable only via paths that do not require in-VPC origin (today, the RDS Data API public endpoint, see RAG011),
    • that adding a future in-VPC workload triggers the reversal procedure below, not a quiet terraform apply,
    • that “for now” means the demo phase; a genuine production deployment would re-evaluate this against the residency, egress-control, and observability requirements of that environment.

The per-env VPC contains only what Aurora itself requires: two private subnets across two AZs, an Aurora subnet group, and an Aurora security group whose only inbound rule is from intra-VPC source CIDRs (which today admits nothing, since nothing else runs in the VPC). There is no aws_internet_gateway, no aws_nat_gateway, no aws_vpc_endpoint. The network topology document captures this layout end-to-end (network topology).

The data-access path for application components is constrained by this decision: any component running outside the VPC must reach Aurora through a public-endpoint contract, and any component running inside the VPC would need a way out to Bedrock and Secrets Manager that this ADR does not provide. The choice of which contract to standardize on is made separately in RAG011; this ADR does not pick it.

CI deployment of Terraform runs from GitHub-hosted runners against the AWS APIs directly; no self-hosted runner inside the VPC is required, so the absence of NAT does not affect the deploy path.

Observability (RAG008) flows out of Lambda directly to CloudWatch, X-Ray, and Bedrock via their public endpoints from the Lambda execution environment, which is itself outside the VPC. No VPC-side observability infrastructure (VPC Flow Logs to S3, GuardDuty VPC findings, etc.) is provisioned beyond AWS defaults.

The table below compares the per-environment monthly idle cost of the realistic options, before data-transfer fees. Doubled across QA and Prod for the running total.

OptionPer env, monthly idleTwo envs, monthly idle
No IGW, no NAT, no endpoints (chosen)$0$0
NAT Gateway, 1 AZ~$32~$64
NAT Gateway, 2 AZ (HA)~$64~$128
PrivateLink, 1 AZ, per service required~$7.30~$14.60
PrivateLink, 2 AZ, per service required~$14.60~$29.20
Public subnets, public IPs on workloads~$0~$0

A realistic in-VPC posture for this stack would need Bedrock Runtime and Secrets Manager at minimum, often Aurora Data API if used from in-VPC, and frequently S3 (gateway endpoint is free). A 2-AZ PrivateLink posture across three required services across two environments lands near $90/month idle, larger than the current entire AWS bill estimate.

The public-subnet option is rejected for security reasons (inbound attack surface, the deny-all-by-absence property of the current design).

This decision is “for now.” It must be revisited, and this ADR must be amended or superseded, before any of the following occur:

  • A workload that must run inside the VPC and reach Bedrock, Secrets Manager, or any AWS service whose endpoint is public. Choose NAT or per-service PrivateLink and amend the cost model in cost.
  • A data-residency, egress-allowlisting, or network-DLP requirement that mandates outbound traffic from any AWS-side workload traverse a controlled egress. Same choice as above, plus probable route-table changes.
  • A move to dedicated AWS accounts per environment (the migration path noted in RAG001), which is the natural moment to reset this decision against the production-grade requirements of that environment.
  • The introduction of a self-hosted CI runner or a long-running process for observability ingestion, both of which historically pressure teams into adding NAT.

When reversal happens, the work looks like this:

  1. Pick NAT, per-service PrivateLink, or a mix. Capture the choice and the per-service inclusion list in a new ADR.
  2. Add the corresponding Terraform to the network module behind per-env feature flags so QA can adopt it before Prod, or so one env can pilot while the other holds.
  3. Update Budgets thresholds (#33) to absorb the new fixed line.
  4. Update architecture/cost.md and architecture/network-topology.md to reflect the new picture.
  5. Re-evaluate RAG011; the data-access contract may or may not change, but its constraints will.

This ADR extracts and replaces the network-topology rationale that previously lived in RAG001’s Notes section. RAG001 is about environment separation (two envs, separate VPCs, gated promotion). The reasoning about IGW, NAT, PrivateLink, and the data-access path is its own decision and belongs here. RAG001’s content has been amended to reflect that split.