Build Production-Ready GCP Infrastructure from Scratch: A Complete Console Guide
A 4-Part Series for Complete Beginners
Table of Contents
- Part 1: Foundation - Project Setup, VPC & Networking ← You are here
- Part 2: Security Services - Secrets, Bastion & IAM
- Part 3: Database & Compute Resources
- Part 4: Observability & Load Balancer
Part 1: Foundation - Project Setup, VPC & Networking
Overview
In this first part of our series, you'll build the networking foundation for a production-ready GCP infrastructure. We'll create a custom VPC with 5 subnets, configure Cloud NAT for internet access, set up service accounts, and implement service account-targeted firewall rules.
What you'll build:
- Custom VPC (10.0.0.0/16) in europe-west1 region
- 5 subnets for different application tiers
- Cloud NAT Gateway for outbound internet access
- 4 Service Accounts for IAM-based security
- Firewall rules with service account targeting
Estimated time: 45-60 minutes
Estimated cost: ~$32/month (Cloud NAT only)
Prerequisites
Before we begin, ensure you have:
- GCP Account - Sign up at console.cloud.google.com
- Billing Account - Required for creating resources
- Free Tier Credit - New accounts get $300 credit for 90 days
- Required Permissions - Project Owner or Editor role
Cost Alert: This tutorial will incur charges. With the free tier credit, you can follow along for free. After that, expect ~$350/month total for all infrastructure.
Why GCP for Your Infrastructure?
- EU Data Residency - Host in europe-west1 (Belgium) for compliance
- Private Network - No public IPs needed for database and application servers
- Managed Services - Focus on code, not infrastructure management
- Built-in Security - IAM-based access control, VPC Service Controls
Step 1: Create or Select GCP Project
Navigation Path
- Go to console.cloud.google.com
- Click the project dropdown at the top (next to "Google Cloud Platform")
- Click "New Project"
Project Configuration
Fill in the form:
| Field | Value | Notes |
|---|---|---|
| Project name | My GCP Infrastructure |
Display name |
| Project ID | my-gcp-project-id |
Must be globally unique |
| Organization | (Your organization) | Leave blank for personal |
| Location | No organization |
For personal projects |
Pro Tip: Project IDs cannot be changed after creation. Choose carefully and avoid spaces.
Click "Create" and wait 1-2 minutes for project creation.
Verify Project Creation
You should see a notification: "Project 'My GCP Infrastructure' is ready."
Click "Select project" to switch to your new project.
Enable Required APIs
Some resources require specific APIs to be enabled:
- Navigate to APIs & Services → Library
- Search for and enable these APIs:
- Compute Engine API
- Cloud SQL API
- Secret Manager API
- Identity-Aware Proxy API
Why: Enabling APIs early prevents "API not enabled" errors during resource creation.
Step 2: Create Custom VPC (10.0.0.0/16)
What is a VPC?
A Virtual Private Cloud (VPC) is your isolated network in Google Cloud. Think of it as your own data center network in the cloud.
Navigation Path
- From the main menu (hamburger icon ≡), navigate to: Networking → VPC networks
- Click "Create VPC network"
VPC Configuration
Fill in the form:
| Field | Value | Notes |
|---|---|---|
| Name | dev-network |
Environment-prefixed for clarity |
| Description | Development environment VPC |
Optional but recommended |
| Custom VPC creation | ✓ Enable | CRITICAL: Uncheck "Automatic subnet creation" |
| Routing mode | Regional |
Recommended for most cases |
| MTU | 1460 |
Default (leave unchanged) |
VPC Flow Logs Configuration
Scroll down to "VPC flow logs" section:
| Field | Value | Notes |
|---|---|---|
| Off/On | On | Enable for security monitoring |
| Sampling | 50% |
Balance cost vs visibility |
| Aggregation interval | 5 sec |
Most granular option |
| Metadata | Include all |
Full logging for forensics |
Cost Alert: VPC Flow Logs cost ~$0.005/GB. With 50% sampling, expect ~$5-10/month for moderate traffic.
Why Flow Logs: Essential for troubleshooting network issues and security audits. They show all traffic flows through your VPC.
Dynamic Routing Mode
Leave as "Regional" (default).
Create the VPC
Click "Create" at the bottom.
Wait 30-60 seconds for creation. You should see:
VPC network "dev-network" was created successfully.
Verify VPC Creation
You should see dev-network in your VPC networks list with:
- Subnets: 0 (we'll add these next)
- Routing mode: Regional
- Flow logs: On
Step 3: Create 5 Subnets (Sequential /24)
What are Subnets?
Subnets divide your VPC into smaller networks. Each subnet hosts a specific tier of your application:
- Public subnet (load balancer, bastion)
- Private subnets (backend, database, cache, observability)
Why europe-west1?
We're using europe-west1 (Belgium) for:
- EU Data Residency - Data stays within Europe
- Low Latency - Good for European users
- Cost - Competitive pricing compared to other regions
Pro Tip: For production, consider multi-region deployment for disaster recovery.
Subnet Overview
| Subnet Name | CIDR | Purpose |
|---|---|---|
public-subnet |
10.0.1.0/24 | Bastion, Load Balancer |
private-backend |
10.0.2.0/24 | Backend VMs (NestJS app) |
private-data |
10.0.3.0/24 | Cloud SQL PostgreSQL |
private-cache |
10.0.4.0/24 | Redis + PgBouncer |
private-obs |
10.0.5.0/24 | Prometheus, Loki, Grafana |
Why /24: Provides 251 usable IP addresses per subnet (sufficient for most applications).
Create Each Subnet
We'll create all 5 subnets. The process is identical for each, just with different values.
Subnet 1: public-subnet (10.0.1.0/24)
- Click on
dev-networkin your VPC list - Click the "SUBNETS" tab
- Click "Create subnet"
Fill in the form:
| Field | Value | Notes |
|---|---|---|
| Name | public-subnet |
Descriptive name |
| Description | Public subnet for bastion and load balancer |
Documentation |
| Region | europe-west1 |
Belgium |
| IP address range | 10.0.1.0/24 |
Manual CIDR |
| Private Google Access | ✓ Enable | CRITICAL - see below |
CRITICAL - Private Google Access: This allows VMs without public IPs to reach Google services (Cloud SQL, Secret Manager, etc.). Without this, private VMs cannot access GCP services!
Flow Logs for Subnet 1
Scroll down to "Flow logs" section:
| Field | Value | Notes |
|---|---|---|
| Off/On | On | Same as VPC |
| Sampling | 50% |
Consistent with VPC |
| Aggregation interval | 5 sec |
Consistent with VPC |
| Metadata | Include all |
Consistent with VPC |
Click "Add" to create the subnet.
Subnet 2: private-backend (10.0.2.0/24)
Repeat the process with these values:
| Field | Value |
|---|---|
| Name | private-backend |
| Region | europe-west1 |
| IP address range | 10.0.2.0/24 |
| Private Google Access | ✓ Enable |
| Flow logs | On (same settings) |
Subnet 3: private-data (10.0.3.0/24)
| Field | Value |
|---|---|
| Name | private-data |
| Region | europe-west1 |
| IP address range | 10.0.3.0/24 |
| Private Google Access | ✓ Enable |
| Flow logs | On |
Why separate subnet: Isolates database layer for security. Database VMs have different firewall rules than backend VMs.
Subnet 4: private-cache (10.0.4.0/24)
| Field | Value |
|---|---|
| Name | private-cache |
| Region | europe-west1 |
| IP address range | 10.0.4.0/24 |
| Private Google Access | ✓ Enable |
| Flow logs | On |
Subnet 5: private-obs (10.0.5.0/24)
| Field | Value |
|---|---|
| Name | private-obs |
| Region | europe-west1 |
| IP address range | 10.0.5.0/24 |
| Private Google Access | ✓ Enable |
| Flow logs | On |
Verify All Subnets
You should now see 5 subnets under dev-network:
| Subnet | Region | CIDR | Private IP Access | Flow Logs |
|---|---|---|---|---|
| public-subnet | europe-west1 | 10.0.1.0/24 | On | On |
| private-backend | europe-west1 | 10.0.2.0/24 | On | On |
| private-data | europe-west1 | 10.0.3.0/24 | On | On |
| private-cache | europe-west1 | 10.0.4.0/24 | On | On |
| private-obs | europe-west1 | 10.0.5.0/24 | On | On |
Step 4: Create Cloud Router
What is a Cloud Router?
Cloud Router manages dynamic routing for your Cloud NAT Gateway. It's required for NAT functionality.
Navigation Path
- Navigate to Hybrid Connectivity → Cloud Routers
- Click "Create Router"
Router Configuration
| Field | Value | Notes |
|---|---|---|
| Name | dev-nat-router |
Descriptive name |
| Network | dev-network |
Our VPC |
| Region | europe-west1 |
Same as subnets |
| Google ASN | 65000 |
Default (leave unchanged) |
Why ASN: Autonomous System Number uniquely identifies your router. 65000 is the default for private networks.
Click "Create" and wait for creation (should be instant).
Step 5: Create Cloud NAT Gateway
What is Cloud NAT?
Cloud NAT allows VMs without public IPs to access the internet. This is essential for:
- Downloading packages (apt, npm)
- Calling external APIs
- Software updates
Navigation Path
- Navigate to Network Services → Cloud NAT
- Click "Get started" or "Create Cloud NAT Gateway"
Gateway Configuration
Basic Settings
| Field | Value | Notes |
|---|---|---|
| Gateway name | dev-nat-gateway |
Descriptive |
| Cloud Router | dev-nat-router |
Router we created |
| Region | europe-west1 |
Same region |
NAT Mapping (Critical Section)
| Field | Value | Notes |
|---|---|---|
| Custom single region NAT | Select | For single region |
| Cloud Router | dev-nat-router |
Already populated |
| Source subnetworks | ✓ Select all 5 subnets | All subnets need internet |
| NAT IP allocation | Manual | Critical for HA |
Why Manual IP Allocation: Gives us control over outbound IPs. We'll create 2 IPs for high availability.
NAT IP Allocation
Click "Add reserved IPs" twice:
IP 1:
| Field | Value |
|-------|-------|
| Name | dev-nat-ip-1 |
| Static IP address | (Leave blank - auto-allocate) |
IP 2:
| Field | Value |
|-------|-------|
| Name | dev-nat-ip-2 |
| Static IP address | (Leave blank - auto-allocate) |
Why 2 IPs: High availability. If one IP fails, the other handles traffic.
Cost Alert: ~$0.045/hr per NAT gateway = ~$32/month. Static IPs are free when attached to NAT.
Logging
Leave "Log and monitor" disabled (not needed for NAT).
Create the NAT Gateway
Click "Create" and wait 1-2 minutes.
Verify Cloud NAT
You should see:
- Status: Running
- Cloud Router: dev-nat-router
- External IPs: 2 IPs assigned
- Subnets: All 5 subnets mapped
Step 6: Create Service Accounts
What are Service Accounts?
Service accounts are identities for applications (not humans). They allow VMs to authenticate with Google Cloud services.
Why Service Account Targeting?
Security Best Practice: Instead of using network tags (which can be spoofed), we use service accounts to target firewall rules. This provides IAM-based security.
Why SA-targeted is more secure: If someone compromises your VM, they can't change network tags to bypass firewall rules. Service account targeting is enforced at the IAM level.
Create Service Accounts
We'll create 4 service accounts for different tiers.
Service Account 1: Backend SA
- Navigate to IAM & Admin → Service Accounts
- Click "Create Service Account"
Fill in the form:
| Field | Value | Notes |
|---|---|---|
| Service account name | backend-dev-sa |
Tier-environment pattern |
| Service account description | Service account for backend VMs |
Documentation |
| Service account ID | (Auto-filled) | backend-dev-sa@PROJECT_ID.iam |
Click "Create and continue".
Skip the "Grant this service account access to project" step (we'll add roles later).
Click "Done".
Service Account 2: Cache SA
Repeat the process:
| Field | Value |
|---|---|
| Name | cache-dev-sa |
| Description | Service account for Redis/PgBouncer VM |
Service Account 3: Observability SA
| Field | Value |
|---|---|
| Name | observability-dev-sa |
| Description | Service account for monitoring tools |
Service Account 4: Bastion SA
| Field | Value |
|---|---|
| Name | bastion-dev-sa |
| Description | Service account for bastion host |
Add IAM Roles to Service Accounts
Now we'll add roles to allow VMs to write logs and metrics.
For Backend, Cache, and Observability SAs:
- Click on the service account (e.g.,
backend-dev-sa) - Click the "Permissions" tab
- Click "Grant access"
Add these roles:
| Role | Purpose |
|---|---|
Logs Writer |
Allow VMs to write to Cloud Logging |
Monitoring Metric Writer |
Allow VMs to send metrics to Cloud Monitoring |
Add Principal: backend-dev-sa@PROJECT_ID.iam.gserviceaccount.com
Select Role: Logs Writer → Monitoring Metric Writer
Repeat for cache-dev-sa and observability-dev-sa.
For Bastion SA:
Bastion only needs Logs Writer role.
Step 7: Create Firewall Rules (Service Account Targeted)
Firewall Rule Priority
GCP evaluates firewall rules by priority number:
- Lower numbers = Higher priority (evaluated first)
- Deny rules typically use priority 500
- Allow rules typically use priority 1000+
Why this order: Deny rules (higher priority) are evaluated before allow rules. This ensures we can block specific traffic before allowing general traffic.
Rule 1: Deny SMTP Egress (Priority 500)
Purpose: Prevent compromised VMs from sending spam.
- Navigate to VPC networks → Firewall
- Click "Create firewall rule"
Fill in the form:
| Field | Value | Notes |
|---|---|---|
| Name | dev-firewall-deny-smtp-egress |
Descriptive |
| Network | dev-network |
Our VPC |
| Priority | 500 |
High priority (deny) |
| Direction of traffic | Egress |
Outbound |
| Action on match | Deny |
Block traffic |
| Target service accounts | Select all 4 SAs | Apply to all private VMs |
Filters section:
| Field | Value |
|-------|-------|
| Destination filter | IPv4 range |
| IPv4 range | 0.0.0.0/0 | All destinations |
Protocols and ports:
| Field | Value |
|-------|-------|
| Allow protocols | Specified protocols and ports |
| TCP | ✓ Checked |
| Ports | 25 | SMTP port |
Logging:
| Field | Value |
|-------|-------|
| Log config | On | Enable for security |
| Metadata | Include all | Full logging |
Click "Create".
Why log deny rules: Detect potential compromise. If a VM tries to send SMTP traffic, it's suspicious.
Rule 2: Allow SSH to Backend (Priority 1000)
Purpose: Allow SSH access to backend VMs for troubleshooting.
| Field | Value |
|---|---|
| Name | dev-firewall-allow-ssh-backend |
| Network | dev-network |
| Priority | 1000 |
| Direction | Ingress |
| Action | Allow |
| Target service accounts | backend-dev-sa |
Source filter:
| Field | Value |
|-------|-------|
| Source IPv4 ranges | 0.0.0.0/0 | All sources (restrict in prod!) |
Security Warning: In production, restrict SSH to specific IPs or VPN ranges.
Protocols and ports:
| Field | Value |
|-------|-------|
| TCP | ✓ Checked |
| Ports | 22 | SSH |
Logging:
| Field | Value |
|-------|-------|
| Log config | On | Critical - log SSH access |
| Metadata | Include all | Full metadata |
Click "Create".
Rule 3: Allow Internal HTTP (Priority 1001)
Purpose: Allow internal HTTP/HTTPS traffic between services (load balancer → backend).
| Field | Value |
|---|---|
| Name | dev-firewall-allow-http-backend |
| Network | dev-network |
| Priority | 1001 |
| Direction | Ingress |
| Action | Allow |
| Target service accounts | backend-dev-sa |
Source filter:
| Field | Value |
|-------|-------|
| Source IPv4 ranges | 10.0.0.0/8 | Internal VPC traffic only |
Why 10.0.0.0/8: Covers all our subnets (10.0.1.0-10.0.5.0). More secure than 0.0.0.0/0.
Protocols and ports:
| Field | Value |
|-------|-------|
| TCP | ✓ Checked |
| Ports | 80, 443 | HTTP and HTTPS |
Logging:
| Field | Value |
|-------|-------|
| Log config | Off | Too noisy for production |
Click "Create".
Rule 4: Allow Health Checks (Priority 1002)
Purpose: Allow GCP health checkers to probe backend VMs.
| Field | Value |
|---|---|
| Name | dev-firewall-allow-health-checks |
| Network | dev-network |
| Priority | 1002 |
| Direction | Ingress |
| Action | Allow |
| Target service accounts | backend-dev-sa |
Source filter:
| Field | Value |
|-------|-------|
| Source IPv4 ranges | 35.191.0.0/16, 130.211.0.0/22 | GCP health check ranges |
Why these IPs: These are GCP's health checker IP ranges. Without this rule, health checks fail and load balancer won't route traffic.
Protocols and ports:
| Field | Value |
|-------|-------|
| TCP | ✓ Checked |
| Ports | 80, 443 | HTTP and HTTPS |
Logging:
| Field | Value |
|-------|-------|
| Log config | Off | Health checks are too noisy |
Click "Create".
Rule 5: Allow Backend to Cache (Priority 1003)
Purpose: Allow backend VMs to connect to Redis (6379) and PgBouncer (6432).
| Field | Value |
|---|---|
| Name | dev-allow-backend-to-cache |
| Network | dev-network |
| Priority | 1003 |
| Direction | Ingress |
| Action | Allow |
| Source service accounts | backend-dev-sa |
| Target service accounts | cache-dev-sa |
Service Account Source: This is the key to SA-targeted firewall. We're allowing traffic FROM backend SA TO cache SA.
Protocols and ports:
| Field | Value |
|-------|-------|
| TCP | ✓ Checked |
| Ports | 6379, 6432 | Redis and PgBouncer |
Logging:
| Field | Value |
|-------|-------|
| Log config | On | Useful for debugging |
Click "Create".
Verify All Firewall Rules
You should see 5 firewall rules:
| Rule Name | Priority | Direction | Action | Target |
|---|---|---|---|---|
| dev-firewall-deny-smtp-egress | 500 | Egress | Deny | All SAs |
| dev-firewall-allow-ssh-backend | 1000 | Ingress | Allow | backend-dev-sa |
| dev-firewall-allow-http-backend | 1001 | Ingress | Allow | backend-dev-sa |
| dev-firewall-allow-health-checks | 1002 | Ingress | Allow | backend-dev-sa |
| dev-allow-backend-to-cache | 1003 | Ingress | Allow | cache-dev-sa |
Part 1 Verification Checklist
Before moving to Part 2, verify:
- [ ] VPC
dev-networkexists in europe-west1 - [ ] 5 subnets created with correct CIDRs (10.0.1.0/24 - 10.0.5.0/24)
- [ ] Private Google Access enabled on all subnets
- [ ] Cloud NAT
dev-nat-gatewayhas 2 external IPs - [ ] Cloud Router
dev-nat-routerstatus is "Ready" - [ ] 4 service accounts created (backend, cache, observability, bastion)
- [ ] Service accounts have Logs Writer role
- [ ] All 5 firewall rules visible in VPC → Firewall
Cost Summary - Part 1
| Component | Monthly Cost | Notes |
|---|---|---|
| Cloud NAT Gateway | ~$32 | 2 static IPs + NAT gateway fee |
| VPC Flow Logs | ~$5-10 | Depends on traffic volume |
| Total Part 1 | ~$37-42 | ~$42/month |
Cost Optimization: In production, consider:
- Reducing Flow Logs sampling to 25%
- Using 1 NAT IP instead of 2 (lower HA)
- Disabling Flow Logs on non-critical subnets
Troubleshooting - Part 1
Issue: VPC Creation Fails
Symptom: "API not enabled" error
Solution:
- Navigate to APIs & Services → Dashboard
- Search for "Compute Engine API"
- Click "Enable"
Issue: Subnet Creation Fails
Symptom: "IP range overlaps with existing subnet"
Solution:
- Check existing subnet CIDRs in VPC details
- Ensure each subnet has unique /24 range
- Verify you're using 10.0.1.0 through 10.0.5.0 (not overlapping)
Issue: Cloud NAT Shows "Failed"
Symptom: NAT gateway status is "Failed"
Solution:
- Verify Cloud Router exists and is in same region
- Check NAT IP allocation (should show 2 IPs)
- Ensure subnets have no external IP (Cloud NAT only for private IPs)
Issue: Firewall Rule Not Working
Symptom: Traffic blocked unexpectedly
Solution:
- Check rule priority (lower = higher priority)
- Verify service account email matches exactly
- Check implicit deny: If no rule allows, traffic is blocked by default
What's Next - Part 2?
In Part 2: Security Services, you'll build:
- Secret Manager for secure credential storage
- Bastion host with IAP tunneling for secure SSH
- IAM permissions for access control
- OS Login for keyless SSH authentication
Continue to Part 2: Security Services →
References
- GCP VPC Documentation
- Cloud NAT Documentation
- Firewall Rules Documentation
- Service Account Documentation
Foundational Infrastructure Complete! Your VPC, subnets, NAT gateway, service accounts, and firewall rules are ready. Next, we'll add security services (Secrets, Bastion, IAM).














Top comments (0)