Build a private network with two virtual machines
Coming from another cloud?
▸AWS·Amazon Virtual Private Cloud
Amazon Virtual Private Cloud
- AWS VPC is regional with CIDR /16-/28.
- OpenStack Networks project-scoped L2 with flexible CIDR.
- AWS requires IGW for public.
- OpenStack provider nets or floating IPs.
▸Azure·Virtual Network (VNet)
Virtual Network (VNet)
- Azure VNets are strictly regional Layer 3 overlays scoped to one subscription with no L2 VLAN support ().
- VNets and subnets creation free, but subnets min /29 with Azure reserving 5 IPs per subnet ().
- Managed via ARM REST APIs/PowerShell/CLI vs Neutron REST API.
- Isolated per subscription; peering for cross-VNet connectivity vs OpenStack project networks connected via routers.
▸DigitalOcean·VPC (VPC Network)
VPC (VPC Network)
- Region-scoped: a VPC network is created in a specific datacenter region and resources must be in that same region to be attached, whereas OpenStack Neutron networks/subnets are generally available across all AZs in a region and attachments are controlled by network reachability rather than an explicit region slug ().
- Resource migration is limited: Droplets require snapshot/recreate to move between VPCs and some resources (Kubernetes clusters, load balancers, NAT gateways) cannot be migrated between VPCs, whereas in OpenStack you typically can attach/detach ports or move router interfaces without recreating servers (behavior depends on deployment, but Neutron’s object model supports it) ().
- NAT is a managed NAT Gateway with tiered capacity (1–16 increments; each increment gives 25 Mbps symmetrical bandwidth and 100 GiB outbound transfer/month) and can be set as the default gateway for the VPC, whereas OpenStack commonly expresses egress via Neutron routers with SNAT and does not use this specific ‘size tier’ model ().
- Security-policy coupling differs: DigitalOcean notes Cloud Firewall rules affect both public and VPC traffic and rules must specify whether they apply to the public or private IP range, whereas in OpenStack security groups are generally applied to ports and are not framed as “public vs private IP range” rule modes ().
▸Google Cloud·VPC Network
VPC Network
- GCP VPC is global (spans all regions) whereas OpenStack Neutron networks are project-scoped and region-local.
- GCP uses shared VPC for cross-project networking (requires org-level config); OpenStack uses shared networks via admin.
- Subnets in GCP are regional, auto-mode creates one per region automatically; OpenStack requires explicit subnet creation.
- GCP VPC Flow Logs per-subnet; OpenStack has no native equivalent without external tools.
▸Hetzner·Networks
Networks
- Private networks (vSwitch-like but called Networks) created via API, up to 10.0.0.0/8 subnets, attach to servers/LBs.
- CCM supports route controller for pod networking when enabled; no native provider networks like OpenStack.
- Limited to Hetzner regions, IPv4 only for private (IPv6 public).
- Networks are free with no usage charges, unlike potential metering in OpenStack clouds ().
Build a private network with two virtual machines
Stand up a private subnet with two Ubuntu instances on Quake AI: a bastion reachable through a floating IP and an internal instance reachable only over the private network. You operate the network and instances yourself; this is a kept-running bastion layout, not a managed VPC product.
Monthly cost estimate
Pricing calculator ↗Sized as a custom package on dedicated vCPU.
Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.
What each resource is for
2× m2a.large
m2a.large · 2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps
Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.
Included in baseline
m2a.large
2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps
m2a.large
2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps
Compute + RAM rate basis
4 vCPU + 16 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.
Public IP (included)
1 included with the custom package
Package promotional discount
Flat −$5.00/mo on the custom package (same promotion as named plans).
Included at no charge
These line items are zero on Quake AI. Many other providers meter them separately.
Data transfer (inbound and outbound)
Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.
AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.
Learn morePrivate networking
Private networks, subnets, Neutron routers, and security groups are included with the plan.
VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.
Control-plane API requests
OpenStack API calls for provisioning and management are included.
Some managed services on other clouds meter API calls or charge for premium control-plane features.
Dev/test vs production
Start on shared CPU for dev/test, then promote to dedicated for production with a flavor resize. The network, storage, and template stay the same.
Dev/test on shared CPU
Burstable s1a flavors; suited to prototyping and low or bursty load.
Production on dedicated CPU
The headline estimate above; predictable steady-load performance.
Saves $99.00/mo while you build on shared CPU.
Shared flavors carry less RAM (m2a.large (8 GiB RAM) -> s1a.small (2 GiB RAM)). A resize reboots the instance; data on attached volumes persists. Size the dedicated flavor for the RAM your production workload needs.
Pricing data last validated: . For current rates, check quake.ai/pricing.
Prerequisites#
You need:
- An SSH key pair uploaded to your project, or follow Step 1 to create and upload one
- An SSH client on your local machine (built into macOS and Linux; Windows users should set up a Linux CLI environment)
- Familiarity with SSH; see Launch your first server if you have not connected to an instance before
Use the Console for network and instance steps and your local terminal for SSH.
Step 1: Generate and upload an SSH key pair#
If you already have an SSH key pair uploaded to your project, skip the generation and the upload, and move to Step 2.
Generate an SSH key pair on your local machine if you do not already have one:
ssh-keygen -t ed25519 -C "[email protected]"When prompted, press Enter to accept the default file location (~/.ssh/id_ed25519). Optionally set a passphrase.
Now upload the public key to your Quake AI project so the platform injects it into the instances you launch later:
- In the Console, go to Compute > Key Pairs > Import Key Pair.
- Enter a name like
tutorial-key. - Display your public key with
cat ~/.ssh/id_ed25519.puband paste the contents into the Public Key field. - Select OK.
The key pair appears in your key pair list. You select it during the instance launch step.
Step 2: Create the private network and subnet#
The private network holds the IP addresses your instances use to talk to each other. The subnet defines the address range, DNS servers, and gateway.
- Go to Network > Networks > Create Network.
- Set the Network Name to
tutorial-network. - Leave the Create Subnet toggle on (the default).
- Fill the subnet fields:
- Subnet Name:
tutorial-subnet - IP Version:
ipv4(the default) - CIDR:
192.168.100.0/24 - DNS:
1.1.1.1and1.0.0.1
- Subnet Name:
- Select OK to create the network and subnet.
The new network appears in Network > Networks. The platform allocates 192.168.100.2 through 192.168.100.254 as the DHCP pool for this subnet. Your two virtual machines pick addresses from that pool when they boot.
Step 3: Create a router and attach it to the network#
The router connects tutorial-network to PublicStatic, the external network that holds public IP addresses. Without the router, instances on the private subnet cannot reach the internet and floating IPs cannot reach the instances.
- Go to Network > Routers > Create Router.
- Set the Name to
tutorial-router. - Optionally enter a description like
Tutorial router for private subnet to PublicStatic. - Select Attach to Public Network and choose
PublicStaticfrom the dropdown. An optional Subnet field appears when you need to constrain allocation to a specific subnet; leave it blank when you connect a private subnet with Connect Private Network afterward, as this deployment does. - Select OK to create the router.
Now connect the router to the private subnet:
- In the Routers list, find
tutorial-router. - On the router's row, select the Networking icon and choose Connect Private Network.
- Select the row for
tutorial-subnet. - Select OK.
The router now sits between tutorial-subnet and PublicStatic. Traffic from your floating IP reaches the router, the router forwards it to the bastion VM you launch in Step 5, and return traffic flows back the same way.
Step 4: Create a security group with an SSH rule#
The default security group blocks all inbound traffic to instance ports. You create a separate group named tutorial-ssh for the SSH rule so the rules stay organized and removable during cleanup without affecting the default group.
- Go to Network > Security Groups > Create Security Group.
- Set the Name to
tutorial-ssh. - Enter a description like
Allow SSH ingress for the private network deployment. - Select OK to create the group.
Now add the SSH rule:
- On the
tutorial-sshrow, open the Settings gear icon dropdown and select Create Rule. - In the Protocol dropdown, select SSH. The Console auto-fills the port (
22) and direction (Ingress). - Set Source to CIDR with the value
0.0.0.0/0. - Select OK to create the rule.
The rule allows inbound TCP traffic on port 22 from any source IP. In production, narrow the Source CIDR to your office or VPN range. For this deployment, 0.0.0.0/0 keeps SSH working regardless of which network you connect from.
Step 5: Launch the bastion virtual machine#
The bastion is the public-facing VM. It carries the floating IP and accepts SSH connections from your laptop.
In the Console, go to Compute > Instances > Create Instance. Name the instance tutorial-bastion when the wizard asks. Then walk the rest of the Create Instance wizard with these settings:
Create a new instance:
- Select Compute > Instances > Create Instance.
- Name the instance.
- Select
us-east-1afor the availability zone. - Select flavor
m2a.large. - Select Ubuntu-24.04 for the image and set the disk to 20 GiB. Check Deleted with the instance.
- Select Next: Network Config. Select your project network and subnet.
- Select the default, tutorial-ssh security groups.
- Select Next: System Config. Select Keypair for the login type and choose your key pair.
- Select Next: Confirm Config and confirm.
Assign a floating IP after the instance launches.
When the wizard reaches Network Config, select tutorial-network and tutorial-subnet (the network and subnet you created in Step 2). At the System Config step, select the tutorial-key key pair you uploaded in Step 1.
After you confirm, the bastion takes 30 to 60 seconds to reach Active status. Note the IP Addresses value on the instance row; this is the bastion's private address on tutorial-subnet (for example, 192.168.100.10).
Step 6: Launch the second virtual machine#
The internal VM never receives a floating IP. Your laptop cannot reach it directly. You log in by SSHing first to the bastion, then from the bastion to the internal VM's private address.
Go to Compute > Instances > Create Instance again. Name this one tutorial-internal. Walk the same wizard with the same settings:
Create a new instance:
- Select Compute > Instances > Create Instance.
- Name the instance.
- Select
us-east-1afor the availability zone. - Select flavor
m2a.large. - Select Ubuntu-24.04 for the image and set the disk to 20 GiB. Check Deleted with the instance.
- Select Next: Network Config. Select your project network and subnet.
- Select the default, tutorial-ssh security groups.
- Select Next: System Config. Select Keypair for the login type and choose your key pair.
- Select Next: Confirm Config and confirm.
Select tutorial-network and tutorial-subnet at the Network Config step and tutorial-key at the System Config step, identical to Step 5.
Once tutorial-internal reaches Active, note its private address (for example, 192.168.100.20). You use that address from the bastion in Step 8.
Step 7: Allocate a floating IP and associate it with the bastion#
A floating IP is a public IP address you allocate from PublicStatic and bind to one instance at a time. Binding the IP to tutorial-bastion makes the bastion reachable from the public internet.
- Go to Network > Floating IPs > Allocate IP.
- In the Network dropdown, select
PublicStatic. An optional Owned Subnet dropdown appears when you need to constrain allocation to a specific subnet; leave it blank for the default pool. - Leave Batch Allocate off.
- Optionally enter a description like
Tutorial floating IP for bastion. - Select OK to allocate the IP.
The new floating IP appears in the Floating IPs list, unassociated. Now associate it with the bastion:
- Go to Compute > Instances and find
tutorial-bastion. - On the instance row, open the Networking icon menu and select Associate Floating IP.
- For Instance IP, select the private address shown for the bastion.
- For IP Address, select the floating IP you allocated.
- Select OK.
You can also associate from Network > Floating IPs: on the unassociated row, open the Settings gear icon and select Associate, then pick the bastion under Instance IP.
The instance row now shows both the private address and the floating IP. Copy the floating IP for the next step.
Step 8: Connect via SSH and reach the internal virtual machine#
Open your local terminal and SSH to the floating IP:
ssh ubuntu@YOUR_FLOATING_IPReplace YOUR_FLOATING_IP with the address you copied in Step 7. The first time you connect, SSH prompts you to verify the host key fingerprint:
The authenticity of host 'YOUR_FLOATING_IP' can't be established.
ED25519 key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?Type yes and press Enter. You arrive at the bastion's shell:
ubuntu@tutorial-bastion:~$Confirm the bastion's private address on the subnet:
ip -4 addr show ens3Expected output (the address ends in .10 in this example; yours may differ):
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 192.168.100.10/24 brd 192.168.100.255 scope global dynamic ens3
valid_lft 86342sec preferred_lft 86342secNow reach the internal VM. Test connectivity first with a ping to the internal VM's private address:
ping -c 3 192.168.100.20Replace 192.168.100.20 with the actual private address of tutorial-internal from Step 6. Expected output:
PING 192.168.100.20 (192.168.100.20) 56(84) bytes of data.
64 bytes from 192.168.100.20: icmp_seq=1 ttl=64 time=0.412 ms
64 bytes from 192.168.100.20: icmp_seq=2 ttl=64 time=0.298 ms
64 bytes from 192.168.100.20: icmp_seq=3 ttl=64 time=0.301 msThe ping confirms the two VMs sit on the same subnet and can talk over the private network.
SSH from the bastion to the internal VM. The private key for tutorial-key lives on your laptop, not on the bastion, so add the -A flag to your original ssh connection if you have not already, or copy the key to the bastion. The simplest path is to enable agent forwarding on the connection from your laptop. Disconnect from the bastion (exit), then reconnect with agent forwarding:
ssh -A ubuntu@YOUR_FLOATING_IPNow from the bastion, SSH to the internal VM's private address:
You arrive at the internal VM's shell:
ubuntu@tutorial-internal:~$Confirm you are on the internal VM:
hostnameExpected output:
tutorial-internalYou have two instances on a private network: one reachable from the internet through a floating IP and one reachable only through the bastion.
Next steps#
Now that you have a working private network with a bastion, deepen these areas:
- How to allocate floating IP addresses: allocate, associate, and disassociate floating IPs on existing instances
- How to create a security group: the canonical reference for security group creation across Console, CLI, API, and Terraform
- Routers concepts: how routers route between private subnets and external networks, including SNAT and DNAT behavior
- Floating IPs concepts: the relationship between floating IPs, ports, and instances
- How to create a VM on a private network: Console, CLI, and API tabs for the same layout
For an automated version of this architecture, see Infrastructure as Code with OpenTofu.
Clean up#
To avoid ongoing resource usage, delete the resources you created. Delete from instances outward so the resources release cleanly:
- Disassociate and release the floating IP. Go to Network > Floating IPs, find the floating IP, and select Disassociate, then Release IP.
- Delete the instances. Go to Compute > Instances, select
tutorial-bastionandtutorial-internal, and select Delete. Confirm the deletion. - Disconnect the router from the subnet. Go to Network > Routers, find
tutorial-router, open the Networking icon menu, and select Disconnect Private Network. - Delete the router. Select
tutorial-routerand select Delete. - Delete the network. Go to Network > Networks, select
tutorial-network, and select Delete. The subnet deletes with the network. - Delete the security group. Go to Network > Security Groups, select
tutorial-ssh, and select Delete Security Group. - Optionally remove the key pair. If you uploaded
tutorial-keyonly for this deployment, go to Compute > Key Pairs, select it, and select Delete Key Pair. Keep it if you plan to use it for future instances.
All resources release immediately.