Deploy GCP Linux Desktop (HyOps Blueprint)¶
- Purpose: Provision an Ubuntu 22.04 VM in GCP with a public IP, XFCE4 desktop, and XRDP: accessible via any standard RDP client.
- Owner: Platform engineering
- Trigger: Need for a GCP Linux host with GUI access: operator lab, GUI tooling, or a cost-effective alternative to a Windows desktop VM
- Impact: Creates a GCP Compute instance and two named firewall rules (RDP + SSH); incurs GCP compute cost while running
- Severity: P2
-
Pre-reqs:
hyops init gcpcomplete for the target env,gcloudSDK installed,XRDP_USER_PASSWORDset in bootstrap vault -
Rollback strategy:
hyops blueprint destroytears down all steps in reverse order. See §Teardown.
Context¶
Blueprint ref: gcp/linux-desktop@v1
Location: hybridops-core/blueprints/gcp/linux-desktop@v1/blueprint.yml
Step flow:
platform/gcp/vm-firewall-rules#gcp_linux_desktop_firewall: ingress rules for TCP 3389 (RDP) and TCP 22 (SSH), both scoped tosource_rangesplatform/gcp/platform-vm#gcp_linux_desktop_vm: Ubuntu 22.04 VM, public IP,allow-rdp+allow-sshtagsplatform/linux/desktop-xrdp#gcp_linux_desktop_config: Ansible: installs XFCE4, XRDP, and Firefox; suppresses the polkit colour-manager prompt; sets the login password from vault
The Ansible step reads XRDP_USER_PASSWORD from the bootstrap vault (load_vault_env: true). The password must be in the vault before running blueprint deploy.
Cost¶
An e2-standard-2 Ubuntu VM in europe-west2 costs roughly £0.05–0.07/hr. Stop the instance when not in use to pause compute billing. Firewall rules have no cost.
Stop:
gcloud compute instances stop platform-desktop-linux-desktop-01 \
--project <PROJECT_ID> --zone <ZONE>
Start:
gcloud compute instances start platform-desktop-linux-desktop-01 \
--project <PROJECT_ID> --zone <ZONE>
Preconditions and safety checks¶
-
GCP init is ready:
hyops state show --env dev --init gcp -
XRDP_USER_PASSWORDis in the bootstrap vault:hyops secrets set XRDP_USER_PASSWORD=<your-password> --env dev
The password is passed to chpasswd on the VM using the format username:password. Do not use a colon (:) in the password.
-
Confirm your current public IP (used to scope the firewall rules):
curl -s https://checkip.amazonaws.com -
Review the two
CHANGE_MEvalues to set before deploy: CHANGE_ME_GCP_ZONE: e.g.,europe-west2-bCHANGE_ME_YOUR_IP/32: your operator public IP in CIDR notation (appears twice: RDP and SSH rules)
Steps¶
- Initialise the blueprint config
hyops blueprint init --env dev \ --ref gcp/linux-desktop@v1 \ --dest-name linux-desktop.yml
This writes the blueprint to:
~/.hybridops/envs/dev/config/blueprints/linux-desktop.yml
- Set real values
Open ~/.hybridops/envs/dev/config/blueprints/linux-desktop.yml and replace:
- Both instances of
CHANGE_ME_YOUR_IP/32→ your public IP, e.g.,203.0.113.42/32 CHANGE_ME_GCP_ZONE→ e.g.,europe-west2-b
Optional overrides (defaults are usable as-is):
machine_type: defaulte2-standard-2boot_disk_size_gb: default40-
ssh_private_key_file: default~/.ssh/id_ed25519; update if your key is elsewhere -
Validate
hyops blueprint validate \ --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" -
Preflight
hyops blueprint preflight --env dev \ --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" -
Deploy
hyops blueprint deploy --env dev \ --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \ --execute
All three steps run in sequence. Typical duration: 5–8 minutes (XFCE4 + XRDP install takes 2–3 minutes on the VM).
- Get connection details
The deploy output includes the RDP host, port, and user. Retrieve from state if needed:
cat ~/.hybridops/envs/dev/state/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.json \
| python3 -m json.tool | grep -A5 '"outputs"'
Or from the VM state directly:
gcloud compute instances describe platform-desktop-linux-desktop-01 \
--project <PROJECT_ID> --zone <ZONE> \
--format="get(networkInterfaces[0].accessConfigs[0].natIP)"
- RDP in
Connect with any RDP client to <PUBLIC_IP>:3389.
- User:
opsadmin - Password: the value set as
XRDP_USER_PASSWORDin §Preconditions
Example with xfreerdp:
xfreerdp /v:<PUBLIC_IP> /u:opsadmin /p:<PASSWORD> /cert-ignore
- Windows: built-in Remote Desktop Connection (
mstsc) - macOS: Microsoft Remote Desktop (App Store)
- Linux:
xfreerdporrdesktop
Verification¶
Primary state:
~/.hybridops/envs/dev/state/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.json
~/.hybridops/envs/dev/state/modules/platform__gcp__platform-vm/instances/gcp_linux_desktop_vm.json
~/.hybridops/envs/dev/state/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.json
Cross-check in GCP:
# VM running
gcloud compute instances describe platform-desktop-linux-desktop-01 \
--project <PROJECT_ID> --zone <ZONE> \
--format="get(status)"
# Firewall rules present
gcloud compute firewall-rules list \
--project <PROJECT_ID> \
--filter="targetTags~allow-rdp OR targetTags~allow-ssh" \
--format="table(name,sourceRanges,allowed)"
Teardown¶
Destroy all blueprint resources in reverse order using the blueprint file:
hyops blueprint destroy --env dev \
--file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \
--execute
This destroys steps in the order: config → VM → firewall. A confirmation prompt lists all steps and their current state before proceeding. Use --yes to skip the prompt in non-interactive contexts.
Manual teardown (individual steps)
If only specific steps need retargeting, destroy each module individually in reverse apply order. The --inputs flag is required because zone and other required inputs are not stored in the state record directly.
# 1. Remove Ansible config state (no infrastructure to tear down)
hyops destroy --env dev \
--module platform/linux/desktop-xrdp \
--state-instance gcp_linux_desktop_config \
--inputs "$HOME/.hybridops/envs/dev/config/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.inputs.yml"
# 2. Destroy the VM
hyops destroy --env dev \
--module platform/gcp/platform-vm \
--state-instance gcp_linux_desktop_vm \
--inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__platform-vm/instances/gcp_linux_desktop_vm.inputs.yml"
# 3. Destroy the firewall rules
hyops destroy --env dev \
--module platform/gcp/vm-firewall-rules \
--state-instance gcp_linux_desktop_firewall \
--inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.inputs.yml"
Troubleshooting¶
RDP connection refused¶
Check in order:
- Public IP is correct: retrieve again via
gcloud compute instances describe - Firewall rules exist and
source_rangesincludes your current IP (your IP may have changed) -
VM status is
RUNNING:gcloud compute instances describe platform-desktop-linux-desktop-01 \ --project <PROJECT_ID> --zone <ZONE> \ --format="get(status)" -
XRDP is running on the VM:
ssh opsadmin@<PUBLIC_IP> "systemctl status xrdp"
Wrong password at RDP login¶
The XRDP user password is set from XRDP_USER_PASSWORD in the bootstrap vault at deploy time. If the wrong value was in vault, update the vault and re-run the config step:
hyops secrets set XRDP_USER_PASSWORD=<correct-password> --env dev
hyops apply --env dev \
--module platform/linux/desktop-xrdp \
--state-instance gcp_linux_desktop_config \
--inputs "$HOME/.hybridops/envs/dev/config/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.inputs.yml"
Ansible step fails with UNREACHABLE¶
Cause: VM not yet accepting SSH connections at the time Ansible ran (connectivity_wait_s: 90 is usually sufficient but may not be enough on a slow boot).
Fix: re-run the config step; the VM and firewall steps will skip (skip_if_state_ok: true):
hyops blueprint deploy --env dev \
--file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \
--execute
source_ranges locked you out (IP changed)¶
Fix: update source_ranges in the blueprint config and re-run the firewall step:
hyops apply --env dev \
--module platform/gcp/vm-firewall-rules \
--state-instance gcp_linux_desktop_firewall \
--inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.inputs.yml"