Egress Networks: Cloud NAT and Cloud Router
Scenario
You have provisioned a Virtual Machine sitting on a custom VPC subnet. For security, you explicitly did not give the VM a public IP address. Now, the VM needs to reach the internet to download a software patch via `apt-get`. You need to configure a Cloud NAT (Network Address Translation) gateway to give private resources outbound internet access.
- Create a custom google_compute_network and a private google_compute_subnetwork.
- Create a google_compute_router attached to the network in your region (Cloud NAT requires a Cloud Router).
- Create a google_compute_router_nat attached to the Cloud Router.
- Set the NAT ip allocation to AUTO_ONLY (Google provisions and manages the public IPs dynamically based on load).
- Set the NAT to apply to ALL_SUBNETWORKS_ALL_IP_RANGES.
Additional Context
Where's the Internet Gateway? Unlike AWS (where you build an IGW, an Elastic IP, a NAT Gateway, and manually edit Route Tables so `0.0.0.0/0` points to the NAT), GCP abstracts all of this magic. Creating a Cloud NAT attached to a Cloud Router automatically programmes the implicit VPC routes for all chosen subnetworks. Egress traffic just "works".
Cloud Router Dependency: Cloud NAT is actually a software-defined feature built on top of Cloud Router. Even though we aren't using BGP (Border Gateway Protocol) routing here, the Router control plane is required.
variables.tf
variable "project_id" { type = string }
variable "gcp_region" { type = string; default = "us-central1" }main.tf
provider "google" {
project = var.project_id
region = var.gcp_region
}
# 1. Custom VPC Network
resource "google_compute_network" "private_vpc" {
name = "private-app-vpc"
auto_create_subnetworks = false
}
# 2. Private Subnet (VMs here will get no public IPs)
resource "google_compute_subnetwork" "private_subnet" {
name = "private-subnet-us"
network = google_compute_network.private_vpc.id
region = var.gcp_region
cidr_range = "10.0.1.0/24"
}
# 3. Cloud Router (Prerequisite for Cloud NAT)
resource "google_compute_router" "router" {
name = "nat-router"
network = google_compute_network.private_vpc.name
region = var.gcp_region
}
# 4. Cloud NAT Configuration
resource "google_compute_router_nat" "nat" {
name = "nat-gateway"
router = google_compute_router.router.name
region = google_compute_router.router.region
# Tell GCP to allocate and scale the external NAT IPs automatically
nat_ip_allocate_option = "AUTO_ONLY"
# Allow all instances in all subnets in this region to use this NAT for egress
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
log_config {
enable = true
filter = "ERRORS_ONLY"
}
}
# 5. A test VM with NO public IP to prove it works
resource "google_compute_instance" "private_vm" {
name = "isolated-worker"
machine_type = "e2-micro"
zone = "${var.gcp_region}-a"
boot_disk {
initialize_params { image = "debian-cloud/debian-11" }
}
network_interface {
network = google_compute_network.private_vpc.name
subnetwork = google_compute_subnetwork.private_subnet.name
# No access_config block means NO public internet IP assigned
}
metadata_startup_script = "curl -s -o /dev/null http://example.com"
}outputs.tf
output "nat_id" {
value = google_compute_router_nat.nat.id
}Workflow Commands
terraform init
terraform apply -var="project_id=my-gcp-project-123"
# SSH into the private VM using IAP (Identity-Aware Proxy bypasses the need for public IPs)
gcloud compute ssh isolated-worker --zone=us-central1-a --tunnel-through-iap
# Inside the VM, verify outbound internet works:
# curl https://ifconfig.me
# ^ This will return the Google-managed NAT IP address, proving egress works.
terraform destroy -var="project_id=my-gcp-project-123"Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: nat_id = "my-gcp-project-123/us-central1/nat-router/nat-gateway"

