Google Cloud · Real infrastructure

One network.
One Ubuntu computer.

Build a Compute Engine VM, understand its network and sign-in, then recreate the infrastructure and configure the server using code.

Ubuntu 24.04Windows + Linux laptopsYour IP onlyTerraform + Ansible

These are sample names, not our infrastructure. Before copying commands, read Customize the examples: paths, keys, IPs and names. Substitute your own authorized account and resources. Existing lab? Retain its original resource names, usernames and state; renamed samples can cause replacement. See the existing-lab warning before applying updated recipes.

A project, a neighborhood and a computer

The project is the label on your learning box. The VPC is the neighborhood. A network tag is a label that tells the firewall which house its rule applies to. The guest agent turns your public SSH key into a login on Ubuntu.

Your laptopPublic IPv4 /32
Custom VPC + firewallTarget: contoso-infra-code tag
Ubuntu VMSSH 22 · demo HTTP 80
PieceChoiceWhat to learn
NetworkCustom-mode VPC, one 10.44.1.0/24 subnetVPC is global, subnet is regional, VM is zonal.
Locationus-central1 / us-central1-aSubnet and server locations must fit together.
Computee2-micro, Ubuntu 24.04 amd64Small shared-core learning VM; not a guarantee of free usage.
Disk10 GB pd-standard boot disk, auto-deleteGoogle-managed encryption at rest. No snapshots or extra disks.
AddressEphemeral external IPv4Can cost money and changes after stop/start. Not a reserved address.
FirewallTCP 22/80 from your /32 to the VM tagNo default-network allow-SSH rule. Other inbound traffic is denied unless higher-level policies allow it.

The custom VPC gets an internet default route; no separate AWS-style internet gateway resource is created. This VM has an external IP for direct IPv4 connectivity. Default implied egress is permissive for downloads; organization firewall policies can change the effective behavior.

Metadata SSH is a sandbox choice. This guide uses instance-specific SSH metadata, not OS Login, and blocks project-wide SSH keys. If your organization requires OS Login or prohibits external IPs, stop and use its authorized access flow. Do not turn off those policies to follow this example.

No workload service account is attached, since Nginx needs no Google API access. There is no load balancer, Cloud NAT, static address or CDN. The page is unencrypted HTTP, restricted to your IP, and must contain no sensitive data. Do not bypass browser security warnings; use the terminal test if necessary.

Google VPC conceptsFirewall rules and implied rulesMetadata SSH and OS LoginOfficial Ubuntu image family

1. Confirm billing and permissions

  1. Complete the Google account/project setup, enable two-step verification and confirm your own trial balance and expiration. Use a dedicated learning project linked to the intended billing account.
  2. An administrator can authorize Compute Admin and Service Usage Consumer on that isolated project for this recipe. Compute Admin is broad compute access: do not use it as a general production permission. API enablement or billing changes may need the administrator separately.
  3. Enable the Compute Engine API in the learning project. No service-account key download or attached VM service account is required here.
  4. Create a project-scoped monthly $5 budget with spend and forecast alerts. Inspect credit filters so covered usage does not hide the underlying cost. Alerts are delayed, not a cap.
  5. Estimate 4 running hours of e2-micro in us-central1, 10 GB standard persistent disk for its full lifetime, an external IPv4 for assigned hours and a little Premium-tier outgoing data. Check actual free-service eligibility separately rather than assuming all components qualify.

Do not upgrade trial billing to overcome a quota or service restriction without understanding the billing change. Stop between sessions, but destroy at the end. Stopping retains the disk, which can keep costing money. The example uses an ephemeral IP; a separately reserved static IP has different retention/billing behavior.

Google calculatorVM pricingDisk pricingExternal IP pricingBudget limitations

Ready: correct project/billing account, authorized permissions, enabled API, budget and a price estimate covering compute, disk, IP and traffic.

2. Prepare your laptop and two cloud logins

Use Ubuntu in WSL for all later commands so SSH keys, gcloud, Terraform and Ansible stay in one environment. Your Windows laptop can fully administer this Linux VM.

Administrator PowerShell · install WSL
wsl --install -d Ubuntu

Restart if prompted, finish Ubuntu setup, then use its terminal and home directory. See WSL setup.

Install Google Cloud CLI and Terraform using official instructions inside that environment. CLI authentication and Terraform's Application Default Credentials (ADC) are separate stores.

Ubuntu / WSL · project and authentication
gcloud auth login
gcloud config set project YOUR-PROJECT-ID
gcloud auth application-default login
gcloud auth application-default set-quota-project YOUR-PROJECT-ID
gcloud services enable compute.googleapis.com --project YOUR-PROJECT-ID
gcloud auth list
gcloud config get-value project
terraform version

Replace every project placeholder and verify the active account. Ask the administrator if API enablement or quota-project assignment is denied. Keep ADC outside the project/source control; do not upload it to a VM or bucket.

Ubuntu / WSL · local project and SSH key
sudo apt update
sudo apt install -y openssh-client python3-venv
mkdir -p ~/contoso-gcp-infra
cd ~/contoso-gcp-infra
# Edit this path if you want a different dedicated key location.
LAB_SSH_KEY="$HOME/.ssh/contoso_gcp"
# Stop if either file exists: never overwrite a key.
if [ -e "$LAB_SSH_KEY" ] || [ -e "$LAB_SSH_KEY.pub" ]; then
  printf '%s\n' 'Key already exists. Choose a new path, or reuse it only if authorized.'
else
  # Choose a passphrase. Continue only if creation and unlocking succeed.
  mkdir -p "$(dirname "$LAB_SSH_KEY")" &&
  ssh-keygen -t rsa -b 4096 -f "$LAB_SSH_KEY" -C contoso &&
  eval "$(ssh-agent -s)" &&
  ssh-add "$LAB_SSH_KEY" &&
  cat "$LAB_SSH_KEY.pub" &&
  realpath "$LAB_SSH_KEY.pub"
fi

The final contoso comment identifies the intended username when adding this key in the console. Terraform explicitly prefixes the metadata key with that username. The public .pub half can be imported; keep the private half secret. Find your laptop connection's public IPv4 and append /32.

Google Terraform authenticationGuest agent account management

Your key path is your choice. $HOME expands to the home directory of the Ubuntu/WSL user running these commands; no username is hard-coded. Keep using the same LAB_SSH_KEY in this terminal. In a new terminal, set it again to your actual private-key path and unlock the key. Terraform needs the matching .pub file: replace its path placeholder with the output of realpath "$LAB_SSH_KEY.pub". Ansible needs the private file: edit both the inventory IP and its key path. A literal $LAB_SSH_KEY is not expanded inside HCL or an INI inventory.

3. Build once in the console

Use the learning project throughout and names ending in manual. Do not use or alter a shared default network.

  1. Create a VPC named contoso-infra-manual in custom mode, with one subnet in us-central1 using 10.44.1.0/24. Keep the normal internet default route. Do not add suggested broad allow-SSH/internal rules.
  2. Create an ingress firewall rule for that VPC named contoso-infra-manual-learner, priority 1000, target tag contoso-infra-manual, source your public IPv4 /32, allowed TCP ports 22 and 80. Do not use 0.0.0.0/0 as source.
  3. Create VM contoso-infra-manual in us-central1-a, E2 shared-core e2-micro, with official Ubuntu 24.04 LTS x86/64 from ubuntu-os-cloud. Choose a 10 GB standard persistent boot disk with deletion on VM deletion and provider-managed encryption.
  4. Choose the custom VPC/subnet, an ephemeral external IPv4 and Premium network tier. Add the contoso-infra-manual network tag. Leave “Allow HTTP/HTTPS traffic” boxes unchecked: the explicit firewall rule already restricts HTTP.
  5. Under service account select No service account. Add only your public SSH key to this instance, with username contoso. Block project-wide SSH keys.
  6. For this permitted sandbox use metadata enable-oslogin=FALSE, block-project-ssh-keys=TRUE, and enable-guest-attributes=TRUE before first boot. If enforced OS Login policy conflicts, stop and use the approved organization flow rather than overriding it.
  7. Enable Shielded VM Secure Boot, vTPM and integrity monitoring for the supported Ubuntu image. Do not add startup scripts, snapshot schedules, paid monitoring agents, extra disks or load balancing.
  8. Review the cost and configuration, create once and wait for running. Record the external IP, zone and boot disk. Verify no workload service account is attached and the firewall targets the intended network tag.

The custom network does not add general internal communication rules like Google's default network. Organization/hierarchical policies may still affect traffic, so inspect effective rules if behavior differs.

Prove it: the VM uses your custom subnet and tag, and the only lab-created ingress allow is from your address.

4. Fetch the server's host key before SSH

Guest attributes, enabled before boot, let the guest agent publish host public keys. Retrieve them through your authenticated Google API connection:

Ubuntu / WSL · retrieve manual VM host keys
gcloud compute instances get-guest-attributes contoso-infra-manual \
  --project YOUR-PROJECT-ID --zone us-central1-a --query-path='hostkeys/'

Find a returned host public key of the type SSH will present, such as ssh-ed25519. Save its complete public-key value (not the whole table row) into a local UTF-8 text file named server-host.pub. Calculate its fingerprint, then compare it with the first connection prompt:

Ubuntu / WSL · verify and connect
ssh-keygen -lf server-host.pub
ssh -i "${LAB_SSH_KEY:?Set LAB_SSH_KEY to your private-key path}" contoso@YOUR-SERVER-IP

Only accept a matching fingerprint. If hostkeys are empty, wait for the guest agent and inspect serial port 1 output through the console. If you cannot independently verify the key, stop—do not use StrictHostKeyChecking=no or trust a network key scan as proof. Guest attributes are writable by processes inside the VM; they help first-connection verification, not protection against an already-compromised guest.

Inside the manual VM · inspect and configure
sudo cloud-init status --wait
cat /etc/os-release
ip -brief address
free -h
df -h
sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx
curl -I http://127.0.0.1/
exit

Investigate cloud-init errors before continuing. From the allowed laptop visit http://YOUR-SERVER-IP/ or run curl -I http://YOUR-SERVER-IP/. The default Nginx page proves both the application and external network path work. Browser-based SSH can use a different source path, so it is not the access method tested by this /32 lab.

Google: host-key verificationGuest attributes and limitations

5. Rebuild the infrastructure from code

Clean up the manual copy first. The recipe creates a new custom VPC/subnet, firewall and VM called contoso-infra-code inside your existing project. It does not create the project, configure billing or import the manual resources.

Save the recipe and variables in ~/contoso-gcp-infra. Replace all placeholders; the IP shown below is not a usable source address for your connection.

terraform.tfvars
project_id        = "YOUR-PROJECT-ID"
allowed_ipv4_cidr = "203.0.113.10/32"
public_key_path   = "/REPLACE/WITH/YOUR/ABSOLUTE/PUBLIC-KEY-PATH.pub"
Ubuntu / WSL · review then build
gcloud config get-value project
terraform init
terraform fmt
terraform validate
terraform plan -out=lab.tfplan
# Check the actual project, machine type, disk, IP and /32 firewall target.
terraform apply lab.tfplan
terraform output

A saved plan applies without a further confirmation. Stop before applying unexpected services, broad firewall rules or replacements. Keep state, saved plans and credentials private. Keep the provider lockfile. An image-family lookup can select a newer image on a later plan, potentially replacing the VM; review this deliberately and pin approved images for production.

For the new VM, repeat host-key retrieval with contoso-infra-code instead of contoso-infra-manual. Verify SSH, wait for cloud-init and exit. No Nginx is installed yet—Ansible handles that next. Inspect the VM's service-account setting to confirm none is attached.

If your public IP changes, change only the source CIDR in variables and review/apply the new plan. Do not broaden the firewall to make login easier.

Terraform VM resourceTerraform firewall resourceDisk encryption

6. Reuse the same server configuration

The Linux application lesson is shared across all three clouds. Download the inventory, playbook and template into one folder, replace the inventory IP, and use your Google lab SSH key. Username is contoso, not AWS's ubuntu.

Ubuntu / WSL · install and configure
python3 -m venv ~/.venvs/contoso-gcp-infra
source ~/.venvs/contoso-gcp-infra/bin/activate
python -m pip install ansible-core
ansible-playbook -i inventory.ini server.yml --syntax-check
ansible lab -i inventory.ini -m ansible.builtin.ping
ansible-playbook -i inventory.ini server.yml
# Edit page_message in server.yml, then preview the difference.
ansible-playbook -i inventory.ini server.yml --check --diff
ansible-playbook -i inventory.ini server.yml
curl http://YOUR-SERVER-IP/

Keep the passphrase-unlocked key in your SSH agent and host-key checking enabled. Ansible ping checks SSH/Python. The guest-created contoso user has administrative sudo access in this sandbox; this is not a production role design.

Check mode is a simulation, best used after the first real installation. Repeat runs should normally leave the package/template/service unchanged, though cache refreshes can report a change. An HTML edit is an OS configuration change, not a Terraform network change.

Prove it: the edited page is served, repeated configuration is stable, and the cloud firewall still allows only your address.

Ansible templatesCheck mode limitations

7. Diagnose the server and network

SymptomCheck
API / quota permission errorCorrect ADC account/project, Service Usage Consumer, API enablement and compute quota. Do not download a powerful service-account key as a workaround.
SSH timeoutRunning VM, current external IP, network tag, firewall source /32 and effective organization rules.
Public-key login rejectedcontoso username, matching instance SSH metadata, guest-agent readiness and enforced OS Login policy.
Hostkeys absentGuest attributes enabled before initial boot, supported guest agent, its startup logs and API permission.
HTTP fails while SSH worksLocal curl and Nginx status, then port 80 and the firewall's target tag.
IP changed after stop/startRead the new address, refresh Terraform with a reviewed plan/apply and update inventory. Independently verify any new host-key prompt.
Charges after stoppingRetained boot disk, snapshots, reserved IPs or other services outside this recipe.
Inside Ubuntu · inspect service and capacity
sudo nginx -t
systemctl status nginx --no-pager
sudo journalctl -u nginx -n 30 --no-pager
sudo ss -lntp
df -h
free -h

Use the VM's monitoring views for platform metrics; guest memory/disk-fullness visibility may require an agent. Price optional logging and monitoring additions before enabling them. e2-micro is a shared-core size, not a promise of continuously dedicated CPU.

Stop/start and ephemeral IP behaviorGoogle VM monitoring

8. Remove the whole exercise

Destructive. The boot disk is set to auto-delete. Save wanted content locally and confirm every resource is this disposable lab before deleting it.

Manual copy

  1. Delete only contoso-infra-manual in us-central1-a and confirm boot-disk deletion. Do not retain its disk by accident.
  2. Remove the manual learner firewall rule, its manual subnet, and the dedicated manual VPC. Wait for VM network interfaces to disappear if a dependency blocks deletion.
  3. Check disks, snapshots and external addresses for retained extras you added outside the guide. Do not remove unrelated resources or the default network.

Code copy

Ubuntu / WSL · reviewed Terraform cleanup
gcloud config get-value project
terraform state list
terraform plan -destroy -out=cleanup.tfplan
# Check the actual project and every proposed deletion.
terraform apply cleanup.tfplan
terraform state list
gcloud auth application-default revoke
gcloud auth revoke

Use the original state/folder and keep the public key available for planning. The project and billing account are not Terraform-owned here and are not deleted. If the project is exclusively disposable and now empty, you may separately shut it down; never do that to a shared project.

Confirm both manual/code VMs, boot disks, firewall rules and networks are gone. Check billing after data catches up. Destroying resources does not erase previous charges.

Finished: you can rebuild a server on any of the three clouds, explain its access and costs, and clean up the environment.

Review infrastructure foundations · Compare the storage-only lesson