Provision DigitalOcean with Terraform
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.
What you need
Section titled “What you need”- Terraform ≥ 1.5
- A DigitalOcean account with an API token (
export DIGITALOCEAN_TOKEN=...) - An SSH key uploaded to DigitalOcean
- A domain name with an A record you can point at the new droplet
Minimal example
Section titled “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 initterraform applyWhen 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
Section titled “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"}What changes vs. the worker example:
- Firewall opens
:80and:443to0.0.0.0/0so the ACME HTTP-01 challenge can complete. - The droplet binds privileged ports as the unprivileged
runeuser viacap_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
Section titled “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"
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 nodesThe token file is written to bootstrap_token_path (default
./rune-admin.token) and is reusable on the same machine.
Common variables
Section titled “Common variables”| Variable | Default | What it does |
|---|---|---|
ssh_key_ids | — (required) | DigitalOcean SSH key IDs / fingerprints to install on the droplet. |
node_role | edge | edge opens 80/443 + runs ACME; worker skips both. |
acme_email | "" | Let’s Encrypt account email. Required for ACME on edge nodes. |
region | lon1 | DigitalOcean region slug. |
droplet_size | s-2vcpu-4gb | Droplet size. Edge nodes terminating TLS should be at least s-1vcpu-2gb. |
image | ubuntu-24-04-x64 | Base image. Tested on Ubuntu 24.04 LTS. |
rune_version | v0.0.1-dev.22 | Release tag passed to install-server.sh. Bump per Rune release. |
cluster_cidr | 10.96.0.0/16 | CIDR 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. |
bootstrap | false | Run rune admin bootstrap automatically after cloud-init. |
bootstrap_ssh_private_key | "" | Required (PEM, sensitive) when bootstrap = true. |
enable_backups | false | Weekly droplet backups. |
enable_monitoring | true | DigitalOcean monitoring agent. |
project_id | "" | Optional project to attach the droplet to. |
The full schema lives in the module README.
Outputs
Section titled “Outputs”| Output | What it is |
|---|---|
ipv4_address | Public IPv4 of the droplet. |
ipv6_address | Public IPv6 (empty if enable_ipv6 = false). |
grpc_endpoint | <ip>:7863 — paste into rune login --server. |
http_endpoint | http://<ip>:7861 — REST API base URL. |
firewall_id | Firewall ID, or empty when create_firewall = false. |
bootstrap_token_path | Absolute path to the saved admin token (empty when bootstrap = false). |
rune_login_command | Ready-to-paste rune login command (empty when bootstrap = false). |
Cloud-init runs once
Section titled “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.thisThis destroys and recreates the droplet — your service state lives
in /var/lib/rune on the droplet, so plan accordingly.
Re-rotate the admin token
Section titled “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
Section titled “Tear down”terraform destroy- Bootstrap & first user — what the bootstrap step actually does.
- Expose a service — wire a service to a public hostname with ACME.
- Operations → Configuration — the
runefile.tomlreference.