Provision DigitalOcean with Terraform

Use the official terraform-digitalocean-rune module to spin up a Rune edge node on a DigitalOcean droplet — droplet, firewall, cloud-init install, and an optional one-shot admin bootstrap.

The runestack/rune/digitalocean module provisions a single Rune node on DigitalOcean: droplet, firewall, cloud-init install, optional project attachment, and an optional in-module rune admin bootstrap that hands you a ready-to-paste rune login command.

If you want to do the same install by hand, follow Deploy on a DigitalOcean droplet instead. This guide is the Terraform path.

:::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.3") and the Rune version (rune_version = "v0.0.1-dev.22") until v1.0. :::

What you need

Minimal example

Worker node, no TLS, no bootstrap. Useful for trying the module end-to-end before adding edge ingress.

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = ">= 2.40, < 3.0"
    }
  }
}
 
data "digitalocean_ssh_key" "main" {
  name = "my-laptop"
}
 
module "rune" {
  source  = "runestack/rune/digitalocean"
  version = "0.0.3"
 
  ssh_key_ids = [data.digitalocean_ssh_key.main.id]
  node_role   = "worker"
}
 
output "ip" {
  value = module.rune.ipv4_address
}
terraform init
terraform apply

When the apply finishes, SSH in and bootstrap manually:

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

Edge node with ACME-managed TLS

Same recipe, with edge ingress and Let's Encrypt:

module "rune" {
  source  = "runestack/rune/digitalocean"
  version = "0.0.3"
 
  ssh_key_ids = [data.digitalocean_ssh_key.main.id]
 
  node_role  = "edge"
  acme_email = "[email protected]"
}

What changes vs. the worker example:

  • Firewall opens :80 and :443 to 0.0.0.0/0 so the ACME HTTP-01 challenge can complete.
  • The droplet binds privileged ports as the unprivileged rune user via cap_net_bind_service — set up by the installer.
  • The ACME orchestrator is enabled and registers an account with Let's Encrypt using acme_email.

Once the droplet is up, point your DNS at it:

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

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/digitalocean"
  version = "0.0.3"
 
  ssh_key_ids = [data.digitalocean_ssh_key.main.id]
 
  node_role  = "edge"
  acme_email = "[email protected]"
 
  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

The token file is written to bootstrap_token_path (default ./rune-admin.token) and is reusable on the same machine.

:::caution[Operator CLI version] Module 0.0.3 emits --default-namespace (renamed from --namespace to disambiguate from the per-operation --namespace flag used elsewhere in the CLI). The printed rune login command therefore requires Rune CLI v0.0.1-dev.23 on the operator's machine. If you must support an older CLI, pin the module to 0.0.2. :::

Common variables

VariableDefaultWhat it does
ssh_key_ids— (required)DigitalOcean SSH key IDs / fingerprints to install on the droplet.
node_roleedgeedge opens 80/443 + runs ACME; worker skips both.
acme_email""Let's Encrypt account email. Required for ACME on edge nodes.
regionlon1DigitalOcean region slug.
droplet_sizes-2vcpu-4gbDroplet size. Edge nodes terminating TLS should be at least s-1vcpu-2gb.
imageubuntu-24-04-x64Base image. Tested on Ubuntu 24.04 LTS.
rune_versionv0.0.1-dev.22Release tag passed to install-server.sh. Bump per Rune release.
cluster_cidr10.96.0.0/16CIDR used by the Rune networking layer.
ssh_allowed_cidrs["0.0.0.0/0", "::/0"]Tighten in production.
api_allowed_cidrs["0.0.0.0/0", "::/0"]Locks down the gRPC + HTTP API ports.
bootstrapfalseRun rune admin bootstrap automatically after cloud-init.
bootstrap_ssh_private_key""Required (PEM, sensitive) when bootstrap = true.
enable_backupsfalseWeekly droplet backups.
enable_monitoringtrueDigitalOcean monitoring agent.
project_id""Optional project to attach the droplet to.

The full schema lives in the module README.

Outputs

OutputWhat it is
ipv4_addressPublic IPv4 of the droplet.
ipv6_addressPublic IPv6 (empty if enable_ipv6 = false).
grpc_endpoint<ip>:7863 — paste into rune login --server.
http_endpointhttp://<ip>:7861 — REST API base URL.
firewall_idFirewall ID, or empty when create_firewall = 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 cloud-init and runs only on first boot. Changing rune_version, cluster_cidr, or anything else that lands in runefile.toml after the droplet exists will not take effect on the running droplet. To roll a new config:

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

This destroys and recreates the droplet — your service state lives in /var/lib/rune on the droplet, so plan accordingly.

Re-rotate the admin token

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

Re-runs the SSH bootstrap and overwrites the local token file. rune admin bootstrap itself refuses to run twice unless you reset the auth state on the server.

Tear down

terraform destroy

Next