Create production-ready AWS VPCs with IPAM-backed CIDR allocation, dual-stack IPv6 support, and flexible NAT strategies. Designed to grow from individual projects to enterprise deployments.
The simplest configuration with explicit CIDR blocks:
apiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: my-network
namespace: dev
spec:
region: us-east-1
providerConfigRef:
name: default
vpc:
cidr: "10.1.0.0/16"
subnets:
- name: my-network-public-a
cidr: "10.1.0.0/24"
availabilityZone: a
public: true
- name: my-network-public-b
cidr: "10.1.1.0/24"
availabilityZone: b
public: true
- name: my-network-private-a
cidr: "10.1.16.0/20"
availabilityZone: a
- name: my-network-private-b
cidr: "10.1.32.0/20"
availabilityZone: bCost: ~$0/mo | Created: VPC, 4 subnets, IGW, route tables
For enterprise deployments, use IPAM for automatic CIDR allocation:
apiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: my-network
namespace: dev
spec:
region: us-east-1
providerConfigRef:
name: default
ipam:
ipv4:
enabled: true
poolId: ipam-pool-0123456789abcdef0
netmaskLength: 16
ipv6Gua:
enabled: true
poolId: ipam-pool-0fedcba9876543210
netmaskLength: 56
resourcePlanning:
# The IPAM administrator creates pools sourced from this workload VPC.
providerConfigRef:
name: network
ipv4:
enabled: true
scopeId: ipam-scope-private0123456789
# externalName: ipam-pool-existing-ipv4-planning
ipv6:
enabled: true
scopeId: ipam-scope-public0123456789
# externalName: ipam-pool-existing-ipv6-planning
subnetLayout:
ipv6NetmaskLength: 64
availabilityZones: [a, b, c]
public:
enabled: true
netmaskLength: 24
private:
enabled: true
netmaskLength: 20
nat:
enabled: falseCost: ~$0/mo | Created: VPC, 6 subnets, IGW, Egress-Only IGW, routes
For Kubernetes workloads, NAT Gateways are often unnecessary:
- Public ingress via Load Balancers - The platform handles external traffic to your services
- IPv6 egress is free - Pods use the Egress-Only Internet Gateway for outbound IPv6 traffic
- VPC Endpoints for AWS services - Access ECR, S3 via private endpoints
This saves ~$32/mo. Add NAT later if you need IPv4 egress to external services.
- No CIDR planning - Automatic allocation from centrally managed pools
- No conflicts - IPAM prevents overlapping ranges across VPCs
- Multi-account ready - Share pools via RAM when you scale
For Amazon-provided IPv6 GUA, use a Regional IPAM pool (for example /52) to
allocate a /56 to each VPC. Subnets then allocate /64s from a VPC
resource-planning pool whose source resource is that VPC.
When the planning pools and workload VPC are in different AWS accounts, share
the planning pools with the workload account or its OU through AWS RAM. The
Foundation ramShares API can manage this after the Network status exposes the
planning pool IDs.
- EKS Auto Mode - IPv6 prevents IP exhaustion when scaling
- Future-proof - Native dual-stack from day one
- Cost savings - Egress-Only IGW is free (vs $32/mo NAT per AZ for IPv4)
NAT (Network Address Translation) allows resources in private subnets to initiate outbound connections to the internet while remaining unreachable from the internet.
Common use cases requiring outbound internet access:
- Pulling container images from Docker Hub or external registries
- Calling external APIs (payment processors, SaaS services)
- Downloading OS updates and security patches
- Sending logs/metrics to external platforms
| Component | Cost |
|---|---|
| NAT Gateway (per hour) | |
| Data processing | $0.045/GB |
This is per NAT Gateway. With HighlyAvailable (one per AZ), you're paying 3x the base cost.
| Strategy | NAT Gateways | Monthly Cost | Use Case |
|---|---|---|---|
| None | 0 | $0 | Isolated workloads, no internet needed |
| SingleAz | 1 | ~$32 | Dev/test, cost-sensitive |
| HighlyAvailable | 1 per AZ (3) | ~$96 | Production, uptime-critical |
With dual-stack networking, IPv6 traffic uses an Egress-Only Internet Gateway instead of NAT:
| Feature | NAT Gateway (IPv4) | Egress-Only IGW (IPv6) |
|---|---|---|
| Hourly cost | ~$0.045/hr | Free |
| Data processing | $0.045/GB | Free |
| HA requirement | 1 per AZ | 1 total (regional) |
apiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: dev-vpc
spec:
region: us-east-1
providerConfigRef:
name: default
vpc:
cidr: "10.1.0.0/16"
subnets:
- name: dev-public-a
cidr: "10.1.0.0/24"
availabilityZone: a
public: true
- name: dev-public-b
cidr: "10.1.1.0/24"
availabilityZone: b
public: true
- name: dev-private-a
cidr: "10.1.16.0/20"
availabilityZone: a
- name: dev-private-b
cidr: "10.1.32.0/20"
availabilityZone: bapiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: team-vpc
spec:
region: us-west-2
providerConfigRef:
name: default
tags:
team: platform
environment: production
vpc:
cidr: "10.2.0.0/16"
subnets:
- name: team-public-a
cidr: "10.2.0.0/24"
availabilityZone: a
public: true
- name: team-public-b
cidr: "10.2.1.0/24"
availabilityZone: b
public: true
- name: team-public-c
cidr: "10.2.2.0/24"
availabilityZone: c
public: true
- name: team-private-a
cidr: "10.2.16.0/20"
availabilityZone: a
- name: team-private-b
cidr: "10.2.32.0/20"
availabilityZone: b
- name: team-private-c
cidr: "10.2.48.0/20"
availabilityZone: c
nat:
enabled: true
strategy: SingleAzapiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: prod-vpc
spec:
region: us-east-1
providerConfigRef:
name: aws-prod
tags:
environment: production
cost-center: engineering
vpc:
cidr: "10.5.0.0/16"
subnets:
- name: ha-public-a
cidr: "10.5.0.0/24"
availabilityZone: a
public: true
- name: ha-public-b
cidr: "10.5.1.0/24"
availabilityZone: b
public: true
- name: ha-public-c
cidr: "10.5.2.0/24"
availabilityZone: c
public: true
- name: ha-private-a
cidr: "10.5.16.0/20"
availabilityZone: a
- name: ha-private-b
cidr: "10.5.32.0/20"
availabilityZone: b
- name: ha-private-c
cidr: "10.5.48.0/20"
availabilityZone: c
nat:
enabled: true
strategy: HighlyAvailableapiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: dual-stack
spec:
region: us-east-1
providerConfigRef:
name: default
vpc:
cidr: "10.3.0.0/16"
ipv6:
ula:
enabled: true
cidr: "fd00:dead:beef::/56"
ipamPoolId: ipam-pool-0abc123
subnets:
- name: ds-public-a
cidr: "10.3.0.0/24"
availabilityZone: a
public: true
ipv6:
ulaCidr: "fd00:dead:beef:0::/64"
- name: ds-public-b
cidr: "10.3.1.0/24"
availabilityZone: b
public: true
ipv6:
ulaCidr: "fd00:dead:beef:1::/64"
- name: ds-private-a
cidr: "10.3.16.0/20"
availabilityZone: a
ipv6:
ulaCidr: "fd00:dead:beef:100::/64"
- name: ds-private-b
cidr: "10.3.32.0/20"
availabilityZone: b
ipv6:
ulaCidr: "fd00:dead:beef:101::/64"
nat:
enabled: true
strategy: SingleAzapiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: dual-stack-amazon
spec:
region: us-east-1
providerConfigRef:
name: default
vpc:
cidr: "10.4.0.0/16"
ipv6:
amazonProvided:
enabled: true
subnets:
- name: dsa-public-a
cidr: "10.4.0.0/24"
availabilityZone: a
public: true
- name: dsa-public-b
cidr: "10.4.1.0/24"
availabilityZone: b
public: true
- name: dsa-private-a
cidr: "10.4.16.0/20"
availabilityZone: a
- name: dsa-private-b
cidr: "10.4.32.0/20"
availabilityZone: b
nat:
enabled: true
strategy: SingleAzapiVersion: aws.hops.ops.com.ai/v1alpha1
kind: Network
metadata:
name: enterprise-vpc
namespace: acme-prod
spec:
region: us-east-1
providerConfigRef:
name: acme-aws-prod
tags:
environment: production
compliance: soc2
cost-center: "12345"
ipam:
ipv4:
enabled: true
poolId: ipam-pool-enterprise123
netmaskLength: 16
ipv6Ula:
enabled: true
poolId: ipam-pool-ipv6-enterprise
netmaskLength: 56
subnetLayout:
availabilityZones: [a, b, c]
public:
enabled: true
netmaskLength: 24
private:
enabled: true
netmaskLength: 18
nat:
enabled: true
strategy: HighlyAvailable
transitGateway:
enabled: true
config:
tgwId: tgw-abc123
routeTablePropagation: true
flowLogs:
enabled: true
config:
destination: s3
logDestinationArn: arn:aws:s3:::acme-vpc-flow-logs
trafficType: ALL| Configuration | NAT Strategy | Monthly Cost |
|---|---|---|
| Minimal (no NAT) | None | $0 |
| With SingleAz NAT | SingleAz | ~$32 |
| Production (HA NAT) | HighlyAvailable | ~$96 |
| Enterprise (HA + TGW) | HighlyAvailable | ~$132+ |
Note: IPv6 egress via Egress-Only IGW is free. Only IPv4 NAT Gateways cost money.
| Field | Type | Required | Description |
|---|---|---|---|
region |
string | Yes | AWS region |
providerConfigRef.name |
string | No | AWS ProviderConfig name (default: "default") |
providerConfigRef.kind |
string | No | Provider config kind (default: "ProviderConfig") |
tags |
object | No | Additional AWS tags |
managementPolicies |
[]string | No | Management policies (default: ["*"]) |
| Field | Type | Required | Description |
|---|---|---|---|
cidr |
string | No* | IPv4 CIDR block (e.g., "10.1.0.0/16") |
ipv6.ula.enabled |
boolean | No | Enable ULA IPv6 |
ipv6.ula.cidr |
string | No | ULA IPv6 CIDR (e.g., "fd00::/56") |
ipv6.ula.ipamPoolId |
string | No | IPAM pool ID for ULA IPv6 |
ipv6.amazonProvided.enabled |
boolean | No | Request Amazon-provided /56 IPv6 |
forProvider |
object | No | Pass-through for VPC forProvider fields |
*Required unless using ipam.ipv4
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Subnet name |
cidr |
string | No* | IPv4 CIDR block |
availabilityZone |
string | Yes | AZ suffix (a, b, c) |
public |
boolean | No | Public subnet (default: false) |
ipv6.ulaCidr |
string | No | ULA IPv6 CIDR for subnet |
ipv6.amazonProvidedCidr |
string | No | Amazon IPv6 CIDR for subnet |
*Required unless using ipam.ipv4
| Field | Type | Description |
|---|---|---|
ipv4.enabled |
boolean | Enable IPv4 CIDR allocation from IPAM |
ipv4.poolId |
string | IPAM pool ID |
ipv4.netmaskLength |
int | VPC netmask (default: 16) |
ipv6Ula.enabled |
boolean | Enable IPv6 ULA CIDR allocation from IPAM |
ipv6Ula.poolId |
string | IPAM pool ID for IPv6 |
ipv6Ula.netmaskLength |
int | VPC IPv6 netmask (default: 56) |
ipv6Gua.enabled |
boolean | Enable IPv6 GUA allocation from a regional IPAM pool |
ipv6Gua.poolId |
string | Regional Amazon-provided or BYOIP IPv6 IPAM pool ID |
ipv6Gua.netmaskLength |
int | VPC IPv6 netmask (default: 56) |
resourcePlanning.providerConfigRef |
object | ProviderConfig for the IPAM administrator account |
resourcePlanning.managementPolicies |
[]string | Management operations for planning pools |
resourcePlanning.ipv4.enabled |
boolean | Create an IPv4 planning pool sourced from the VPC |
resourcePlanning.ipv4.externalName |
string | Existing IPv4 planning pool ID to import |
resourcePlanning.ipv4.scopeId |
string | Private IPAM scope containing the IPv4 regional pool |
resourcePlanning.ipv6.enabled |
boolean | Create an IPv6 planning pool sourced from the VPC |
resourcePlanning.ipv6.externalName |
string | Existing IPv6 planning pool ID to import |
resourcePlanning.ipv6.publicIpSource |
string | IPv6 public address source (amazon or byoip) |
resourcePlanning.ipv6.scopeId |
string | IPAM scope containing the IPv6 regional pool |
| Field | Type | Description |
|---|---|---|
ipv4PoolId |
string | IPv4 VPC resource-planning IPAM pool ID |
ipv6PoolId |
string | IPv6 VPC resource-planning IPAM pool ID |
ipv6NetmaskLength |
int | IPv6 subnet netmask (default: 64) |
availabilityZones |
[]string | AZs for subnet creation (default: [a, b, c]) |
public.enabled |
boolean | Create public subnets (default: true) |
public.netmaskLength |
int | Public subnet netmask (default: 24) |
private.enabled |
boolean | Create private subnets (default: true) |
private.netmaskLength |
int | Private subnet netmask (default: 20) |
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false | Enable NAT Gateways |
strategy |
string | SingleAz | SingleAz, HighlyAvailable, None |
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false | Enable TGW attachment |
config.tgwId |
string | - | Transit Gateway ID |
config.routeTablePropagation |
boolean | true | Enable route propagation |
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false | Enable VPC Flow Logs |
config.destination |
string | cloudwatch | cloudwatch or s3 |
config.logDestinationArn |
string | - | Destination ARN |
config.trafficType |
string | ALL | ALL, ACCEPT, REJECT |
config.iamRoleArn |
string | - | IAM role for CloudWatch |
The Network exposes observed state in status:
status:
ready: true
ipam: # Only present when using IPAM
ipv4:
cidr: "10.100.0.0/16"
ipv6Ula:
cidr: "fd00:dead:beef::/56"
ipv6Gua:
cidr: "2600:1f18:abc::/56"
resourcePlanning:
ipv4PoolId: ipam-pool-vpc-ipv4-abc123
ipv6PoolId: ipam-pool-vpc-ipv6-def456
network:
name: my-network
region: us-east-1
vpcId: vpc-abc123
cidr:
ipv4: "10.100.0.0/16"
ipv6Ula: "fd00:dead:beef::/56"
ipv6Gua: "2600:1f18:abc::/56"
ipv6AmazonProvided: "2600:1f18:abc::/56" # If using Amazon-provided
availabilityZones:
- us-east-1a
- us-east-1b
subnets:
public:
- name: my-network-public-a
id: subnet-pub-a
availabilityZone: us-east-1a
ipv4CidrBlock: "10.100.0.0/24"
ipv6CidrBlock: "fd00:dead:beef:0::/64"
private:
- name: my-network-private-a
id: subnet-priv-a
availabilityZone: us-east-1a
ipv4CidrBlock: "10.100.16.0/20"
ipv6CidrBlock: "fd00:dead:beef:100::/64"
routeTables:
public:
- name: my-network-public
id: rtb-pub
private:
- name: my-network-private-rt-a
id: rtb-priv-a
availabilityZone: us-east-1a
natGateways:
- name: my-network-nat-a
id: nat-abc123
availabilityZone: us-east-1a
eipAllocationId: eipalloc-abc123
internetGateway:
id: igw-abc123
egressOnlyInternetGateway:
id: eigw-abc123
transitGatewayAttachment:
id: tgw-attach-abc123
state: availableApache-2.0