VPC (Virtual Private Cloud)

A VPC is an isolated virtual network inside a cloud account, with its own address range, subnets, and routing rules. What it holds and why builds run in one.

A VPC (virtual private cloud) is an isolated virtual network inside a cloud account, with its own private IP address range, subnets that divide that range, and routing rules that decide which traffic stays inside and which leaves. Every instance a cloud provider launches for you sits in one, and the VPC configuration decides what that instance can reach and what can reach it.

For a GitHub Actions workload the practical consequence is reachability. A build step that installs from an internal package registry or runs migrations against a private database needs a machine inside the network where those endpoints live, and the VPC is that network.

Definition

Each major cloud provider ships the same construct under its own name.

  • AWS calls it a VPC and defines it as a virtual network dedicated to your AWS account, logically isolated from other virtual networks in the cloud (What is Amazon VPC, checked on 2026-08-13).
  • Google Cloud calls it a VPC network: a virtual version of a physical network implemented inside Google's production network (VPC network overview, checked on 2026-08-13).
  • Azure calls it a virtual network, or VNet, and describes it as the fundamental building block for a private network in Azure (Azure Virtual Network overview, checked on 2026-08-13).

The names differ and the parts are the same everywhere.

PartWhat it decidesAWS resource
Address rangeThe pool of private IP addresses available to everything insideVPC IPv4 CIDR block
SubnetsHow the range is split, and which zone a machine lands inSubnet
Route tablesWhere traffic for a given destination is sentRoute table
GatewaysWhether and how traffic reaches the public internetInternet gateway, NAT device
Private service pathsWhether provider service traffic stays off the public internetA private route to AWS services configured on the VPC
Packet filtersWhich flows are allowed between machines and out of the networkSecurity group, network ACL

The address range constrains everything downstream

A VPC is created with a private IPv4 CIDR block. On AWS that block can be between /16 and /28, and every subnet is carved from it. AWS also reserves five addresses in each subnet CIDR: the first four and the last.

CIDRTotal addressesUsable in an AWS subnet
/1665,53665,531
/204,0964,091
/24256251
/266459
/281611

Two limits follow from that arithmetic. A fleet of short lived machines consumes an address per running machine, so a subnet sized at /26 supports 59 concurrent instances and no more, whatever the compute quota says. And the range has to avoid overlapping with any network it is later peered or tunneled to, because overlapping ranges cannot be routed between. AWS documents default quotas of 5 VPCs per Region and 200 subnets per VPC, both adjustable on request (Amazon VPC quotas, checked on 2026-08-13).

Scope: what a VPC spans

Scope differs by provider and it changes how the network is laid out.

ProviderVPC scopeSubnet scope
AWSOne RegionOne Availability Zone
Google CloudGlobal, spanning all regionsOne region
AzureOne regionWithin the VNet address space, spanning zones

On AWS the zone binding is the part that shapes a build fleet. Capacity for a given instance type is per zone, so a fleet restricted to one subnet is restricted to one zone's capacity. Spreading subnets across three Availability Zones spreads that risk.

Public subnets and private subnets

The distinction is a routing property rather than a subnet setting. A subnet whose route table sends 0.0.0.0/0 to an internet gateway is public, and machines in it can hold public IP addresses. A subnet whose default route points at a NAT device is private: outbound connections work, and nothing on the public internet can open a connection inward.

Private placement has a metered cost, because NAT devices are billed by the hour and by the gigabyte processed. In US East (N. Virginia) AWS charges $0.045 per hour per device plus $0.045 per GB processed in each direction (Amazon VPC pricing, checked on 2026-08-13). Configuring S3 access directly on the VPC keeps that traffic off the metered path.

Example

Take an existing VPC in us-east-1 with the range 10.20.0.0/16, three private subnets holding the build machines and three public subnets holding the NAT devices.

SubnetCIDRAvailability ZoneDefault routeWhat runs there
private-a10.20.0.0/20us-east-1aNAT device in public-aBuild machines
private-b10.20.16.0/20us-east-1bNAT device in public-bBuild machines
private-c10.20.32.0/20us-east-1cNAT device in public-cBuild machines
public-a10.20.240.0/24us-east-1aInternet gatewayNAT device, load balancers
public-b10.20.241.0/24us-east-1bInternet gatewayNAT device, load balancers
public-c10.20.242.0/24us-east-1cInternet gatewayNAT device, load balancers

An internal package registry and a staging database already run in this VPC and resolve through a private hosted zone. A self hosted runner launched into private-a is inside the same address range, so the job below reaches both over the VPC's local route with no public exposure of either service.

name: integration
on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: [self-hosted, linux, x64]
    steps:
      - uses: actions/checkout@v4

      - name: Install from the internal registry
        run: npm ci --registry https://npm.internal.example.com

      - name: Apply migrations against the staging database
        run: psql "postgres://db.internal.example.com:5432/app" -f migrations.sql

      - name: Show where this machine sits
        run: |
          TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token \
            -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
          curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
            http://169.254.169.254/latest/meta-data/local-ipv4

The final step prints a 10.20.x.x address, which is the machine's place in the range above. Each destination the job touches takes a different path out of the subnet.

Destination in the jobPathLeaves the VPC
npm.internal.example.com at 10.20.x.xLocal route inside the VPCNo
db.internal.example.com at 10.20.x.xLocal route inside the VPCNo
github.com and api.github.comDefault route to the NAT device, then the internet gatewayYes, and the bytes are metered
An S3 bucket in the same RegionA direct route to S3 when the VPC is configured for it, otherwise the NAT pathNo when the direct route is configured

Run the same job on a machine outside this VPC and the first two steps fail at DNS resolution, because neither hostname exists in public DNS and neither address is routable from outside. The usual workarounds all widen the network surface: publishing the registry, allowlisting a changing set of source addresses, or proxying through a bastion host. Placing the machine in the subnet removes the question, and the security group on that machine becomes the control that decides what the build is allowed to open.

FAQ

What is a VPC in plain terms?

A VPC is a private network you own inside a cloud provider's account. It has an IP address range you choose, subnets that carve that range into smaller blocks, route tables that decide where traffic goes, and gateways that decide whether traffic can leave. Every instance the provider launches for you sits in one.

What is the difference between a VPC and a subnet?

The VPC holds the whole address range and the routing configuration. A subnet is one block of that range with its own route table, and on AWS a subnet lives in exactly one Availability Zone. Machines are placed in subnets rather than in the VPC directly, so subnet choice decides the zone and the default route a machine gets.

Why would build machines run inside a VPC?

Because the services a build step needs often have no public address. A machine in the same VPC reaches an internal package registry or a database endpoint directly over the private range, which leaves both services unpublished and removes the proxy that would otherwise sit in front of them. Outbound access to the public internet is then a separate routing decision.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.