Skip to content
Deployments

Build a private network with two virtual machines

Deployment · Updated Jun 2026

Coming from another cloud?

▸AWS·Amazon Virtual Private Cloud

Amazon Virtual Private Cloudhigh

  • 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.
AWS docs ↗
▸Azure·Virtual Network (VNet)

Virtual Network (VNet)high

  • 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.
Azure docs ↗
▸DigitalOcean·VPC (VPC Network)

VPC (VPC Network)high

  • 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 ().
DigitalOcean docs ↗
▸Google Cloud·VPC Network

VPC Networkhigh

  • 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.
Google Cloud docs ↗
▸Hetzner·Networks

Networkshigh

  • 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 ().
Hetzner docs ↗

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.

Starting template$127.00/mo

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

$132.00/mo

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

$66.00

m2a.large

2 dedicated vCPU, 8 GiB RAM, 0.5 Gbps

$66.00

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

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

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 more
$0.00

Private 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.

$0.00

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.

$0.00

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.

$28.00/mo

Production on dedicated CPU

The headline estimate above; predictable steady-load performance.

$127.00/mo

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.

Your laptopPublicStaticexternal networkFloating IP203.0.113.xQuake AI projectRoutertutorial-routerPrivate subnet192.168.100.0/24Bastion VMtutorial-bastion192.168.100.10Internal VMtutorial-internal192.168.100.20 SSH 22SSH 22
Click to zoom
Private subnet with bastion pattern: your laptop reaches the bastion through a floating IP; the bastion reaches the internal instance over the private subnet

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:

bash
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:

  1. In the Console, go to Compute > Key Pairs > Import Key Pair.
  2. Enter a name like tutorial-key.
  3. Display your public key with cat ~/.ssh/id_ed25519.pub and paste the contents into the Public Key field.
  4. 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.

  1. Go to Network > Networks > Create Network.
  2. Set the Network Name to tutorial-network.
  3. Leave the Create Subnet toggle on (the default).
  4. Fill the subnet fields:
    • Subnet Name: tutorial-subnet
    • IP Version: ipv4 (the default)
    • CIDR: 192.168.100.0/24
    • DNS: 1.1.1.1 and 1.0.0.1
  5. 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.

  1. Go to Network > Routers > Create Router.
  2. Set the Name to tutorial-router.
  3. Optionally enter a description like Tutorial router for private subnet to PublicStatic.
  4. Select Attach to Public Network and choose PublicStatic from 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.
  5. Select OK to create the router.

Now connect the router to the private subnet:

  1. In the Routers list, find tutorial-router.
  2. On the router's row, select the Networking icon and choose Connect Private Network.
  3. Select the row for tutorial-subnet.
  4. 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.

  1. Go to Network > Security Groups > Create Security Group.
  2. Set the Name to tutorial-ssh.
  3. Enter a description like Allow SSH ingress for the private network deployment.
  4. Select OK to create the group.

Now add the SSH rule:

  1. On the tutorial-ssh row, open the Settings gear icon dropdown and select Create Rule.
  2. In the Protocol dropdown, select SSH. The Console auto-fills the port (22) and direction (Ingress).
  3. Set Source to CIDR with the value 0.0.0.0/0.
  4. 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:

  1. Select Compute > Instances > Create Instance.
  2. Name the instance.
  3. Select us-east-1a for the availability zone.
  4. Select flavor m2a.large.
  5. Select Ubuntu-24.04 for the image and set the disk to 20 GiB. Check Deleted with the instance.
  6. Select Next: Network Config. Select your project network and subnet.
  7. Select the default, tutorial-ssh security groups.
  8. Select Next: System Config. Select Keypair for the login type and choose your key pair.
  9. 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:

  1. Select Compute > Instances > Create Instance.
  2. Name the instance.
  3. Select us-east-1a for the availability zone.
  4. Select flavor m2a.large.
  5. Select Ubuntu-24.04 for the image and set the disk to 20 GiB. Check Deleted with the instance.
  6. Select Next: Network Config. Select your project network and subnet.
  7. Select the default, tutorial-ssh security groups.
  8. Select Next: System Config. Select Keypair for the login type and choose your key pair.
  9. 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.

  1. Go to Network > Floating IPs > Allocate IP.
  2. 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.
  3. Leave Batch Allocate off.
  4. Optionally enter a description like Tutorial floating IP for bastion.
  5. Select OK to allocate the IP.

The new floating IP appears in the Floating IPs list, unassociated. Now associate it with the bastion:

  1. Go to Compute > Instances and find tutorial-bastion.
  2. On the instance row, open the Networking icon menu and select Associate Floating IP.
  3. For Instance IP, select the private address shown for the bastion.
  4. For IP Address, select the floating IP you allocated.
  5. 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:

bash
ssh ubuntu@YOUR_FLOATING_IP

Replace 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:

bash
ip -4 addr show ens3

Expected 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 86342sec

Now reach the internal VM. Test connectivity first with a ping to the internal VM's private address:

bash
ping -c 3 192.168.100.20

Replace 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 ms

The 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:

bash
ssh -A ubuntu@YOUR_FLOATING_IP

Now 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:

bash
hostname

Expected output:

tutorial-internal

You 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:

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:

  1. Disassociate and release the floating IP. Go to Network > Floating IPs, find the floating IP, and select Disassociate, then Release IP.
  2. Delete the instances. Go to Compute > Instances, select tutorial-bastion and tutorial-internal, and select Delete. Confirm the deletion.
  3. Disconnect the router from the subnet. Go to Network > Routers, find tutorial-router, open the Networking icon menu, and select Disconnect Private Network.
  4. Delete the router. Select tutorial-router and select Delete.
  5. Delete the network. Go to Network > Networks, select tutorial-network, and select Delete. The subnet deletes with the network.
  6. Delete the security group. Go to Network > Security Groups, select tutorial-ssh, and select Delete Security Group.
  7. Optionally remove the key pair. If you uploaded tutorial-key only 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.

Was this page helpful?