Provision Google Cloud with Terraform

Use the official terraform-google-rune module to spin up a Rune edge node on a Compute Engine instance — instance, firewall, service account, optional static IP, cloud-init install, and an optional one-shot admin bootstrap.

The runestack/rune/google module provisions a single Rune node on Google Cloud: a Compute Engine instance, network-tag firewall rules, a service account, an optional static external IP, cloud-init install, and an optional in-module rune admin bootstrap that hands you a ready-to-paste rune login command.

It's the GCP sibling of terraform-aws-rune, terraform-digitalocean-rune and terraform-hetzner-rune, and accepts almost identical inputs.

:::caution[Pre-1.0] The module is at v0.0.1 and tracks Rune's pre-1.0 development. Pin the module version (version = "0.0.1") and the Rune version (rune_version = "v0.0.1-dev.46") until v1.0. :::

What you need

  • Terraform ≥ 1.5
  • GCP credentials (gcloud auth application-default login, or GOOGLE_APPLICATION_CREDENTIALS) — the project and region come from the google provider, not module variables
  • An SSH public key
  • A domain name with an A record you can point at the new instance

Minimal example

Worker node, no TLS, no bootstrap, in the default network.

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 5.0, < 7.0"
    }
  }
}
 
provider "google" {
  project = "my-gcp-project"
  region  = "europe-west2"
}
 
module "rune" {
  source  = "runestack/rune/google"
  version = "0.0.1"
 
  zone           = "europe-west2-a"
  ssh_public_key = file("~/.ssh/id_ed25519.pub")
  node_role      = "worker"
}
 
output "ip" {
  value = module.rune.public_ip
}
terraform init
terraform apply

When the apply finishes, SSH in (the module creates the rune login user) and bootstrap manually:

ssh rune@$(terraform output -raw ip) \
  'sudo rune admin bootstrap --out-file /tmp/rune-admin.token'

Edge node with ACME-managed TLS

Same recipe, with edge ingress, Let's Encrypt, and a static external IP so the public address survives stop/start:

module "rune" {
  source  = "runestack/rune/google"
  version = "0.0.1"
 
  zone           = "europe-west2-a"
  ssh_public_key = file("~/.ssh/id_ed25519.pub")
 
  node_role          = "edge"
  acme_email         = "[email protected]"
  allocate_static_ip = true
}

What changes vs. the worker example:

  • A firewall rule opens :80 and :443 to 0.0.0.0/0 so the ACME HTTP-01 challenge can complete.
  • runed binds privileged ports as an unprivileged user via cap_net_bind_service.
  • The ACME orchestrator registers an account with Let's Encrypt using acme_email.

Point your DNS at the (now stable) address:

api.example.com.   IN   A   <module.rune.public_ip>

then deploy a service with expose.tls.mode: auto (see Expose a service).

With automated bootstrap

Set bootstrap = true and the module SSHes in, runs rune admin bootstrap, copies the token to local disk, and prints a ready-to-paste rune login command:

module "rune" {
  source  = "runestack/rune/google"
  version = "0.0.1"
 
  zone           = "europe-west2-a"
  ssh_public_key = file("~/.ssh/id_ed25519.pub")
 
  node_role          = "edge"
  acme_email         = "[email protected]"
  allocate_static_ip = true
 
  bootstrap                 = true
  bootstrap_ssh_private_key = file("~/.ssh/id_ed25519")
  bootstrap_token_path      = "rune-admin.token"
}
 
output "login" {
  value = module.rune.rune_login_command
}

After terraform apply:

$(terraform output -raw login)
rune get nodes

Persistent Disks with no service-account key

The module creates a service account and attaches it. Grant it disk permissions with enable_pd_csi_access = true (adds roles/compute.storageAdmin), and the gce-pd storage driver provisions, attaches and snapshots Persistent Disks through the instance's own credentials — no key in Terraform state or on disk:

module "rune" {
  source  = "runestack/rune/google"
  version = "0.0.1"
 
  zone                 = "europe-west2-a"
  ssh_public_key       = file("~/.ssh/id_ed25519.pub")
  node_role            = "edge"
  acme_email           = "[email protected]"
  enable_pd_csi_access = true
}

Then create a zone-pinned StorageClass (no credentialsJSON):

storageClass:
  name: pd-balanced-euw2a
  driver: gce-pd
  parameters:
    zone: europe-west2-a
    diskType: pd-balanced
    fsType: ext4

enable_artifact_registry_access (on by default) similarly grants roles/artifactregistry.reader for private image pulls.

Common variables

VariableDefaultWhat it does
ssh_public_key— (required)SSH public key contents installed for ssh_user.
zoneeurope-west2-aGCE zone for the instance and its gce-pd disks.
node_roleedgeedge opens 80/443 + runs ACME; worker skips both.
acme_email""Let's Encrypt account email. Required for ACME on edge nodes.
machine_typee2-mediumGCE machine type.
imageubuntu-os-cloud/ubuntu-2404-lts-amd64Boot image (family or self-link).
network / subnetworkdefault / ""VPC placement.
allocate_static_ipfalseReserve a static external IP that survives stop/start.
create_service_accounttrueCreate + attach a service account for the node.
enable_pd_csi_accessfalseGrant roles/compute.storageAdmin so the gce-pd driver can manage disks.
enable_artifact_registry_accesstrueGrant roles/artifactregistry.reader for private image pulls.
rune_versionv0.0.1-dev.46Release tag passed to install-server.sh.
ssh_allowed_cidrs / api_allowed_cidrs["0.0.0.0/0"]Tighten in production.
bootstrapfalseRun rune admin bootstrap automatically after cloud-init.
name""Override the instance name. Defaults to rune-<environment>.

The full schema lives in the module README. The GCP project and region are set on the google provider block, not module variables.

Outputs

OutputWhat it is
instance_idGCE instance ID.
public_ipExternal IPv4 — the static IP when allocate_static_ip = true, else the ephemeral IP.
private_ipInternal IPv4 of the instance.
zoneZone the instance (and its disks) live in.
grpc_endpoint<ip>:7863 — paste into rune login --server.
http_endpointhttp://<ip>:7861 — REST API base URL.
network_tagNetwork tag targeted by the firewall rules.
service_account_emailService account attached to the node.
static_ipReserved static external IP (empty when allocate_static_ip = false).
bootstrap_token_pathAbsolute path to the saved admin token (empty when bootstrap = false).
rune_login_commandReady-to-paste rune login command (empty when bootstrap = false).

Cloud-init runs once

The installer is rendered into the user-data metadata key and cloud-init runs it only on first boot. Changing rune_version, cluster_cidr, or anything else that lands in runefile.toml after the instance exists will not take effect on the running instance — the module sets lifecycle { ignore_changes = [metadata["user-data"], boot image] } so a version bump or new image doesn't silently destroy the instance. To roll a new config:

terraform apply -replace=module.rune.google_compute_instance.this

This destroys and recreates the instance — service state lives in /var/lib/rune, so plan accordingly. Persistent Disks provisioned via the gce-pd driver survive the rebuild and re-attach to the new instance on boot (as long as it lands in the same zone).

Re-rotate the admin token

terraform apply -replace=module.rune.null_resource.bootstrap[0]

Tear down

terraform destroy

Next