Provision AWS with Terraform

Use the official terraform-aws-rune module to spin up a Rune edge node on an EC2 instance — instance, security group, IAM instance profile, optional Elastic IP, cloud-init install, and an optional one-shot admin bootstrap.

The runestack/rune/aws module provisions a single Rune node on AWS: an EC2 instance, security group, IAM instance profile, an optional Elastic 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 AWS sibling of terraform-digitalocean-rune and terraform-hetzner-rune, and accepts almost identical inputs, so switching providers is mostly a module source swap.

:::note[Two AWS paths] This guide covers the module (runestack/rune/aws), the recommended path that mirrors the DO/Hetzner modules. The older single-file Deploy on AWS EC2 example (build-from-source, ports 8080/8081) still exists for reference but is superseded by this module. :::

:::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
  • AWS credentials configured (aws configure, AWS_PROFILE, or an assumed role) — the region comes from the aws provider, not a module variable
  • An existing EC2 key pair in the target region
  • A domain name with an A record you can point at the new instance

Minimal example

Worker node, no TLS, no bootstrap. Placed in the account's default VPC.

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.40, < 6.0"
    }
  }
}
 
provider "aws" {
  region = "eu-west-2"
}
 
module "rune" {
  source  = "runestack/rune/aws"
  version = "0.0.1"
 
  key_name  = "my-keypair"
  node_role = "worker"
}
 
output "ip" {
  value = module.rune.public_ip
}
terraform init
terraform apply

When the apply finishes, SSH in (Ubuntu AMIs log in as ubuntu) and bootstrap manually:

ssh ubuntu@$(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 an Elastic IP so the public address survives stop/start:

module "rune" {
  source  = "runestack/rune/aws"
  version = "0.0.1"
 
  key_name = "my-keypair"
 
  node_role    = "edge"
  acme_email   = "[email protected]"
  allocate_eip = true
}

What changes vs. the worker example:

  • The security group 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 — set up by the installer.
  • 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/aws"
  version = "0.0.1"
 
  key_name = "my-keypair"
 
  node_role    = "edge"
  acme_email   = "[email protected]"
  allocate_eip = 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

ECR pulls with no stored credentials

The module creates an IAM instance profile and, by default, attaches AmazonEC2ContainerRegistryReadOnly (create_iam_instance_profile + enable_ecr_access). That lets runed pull from a private ECR registry using the instance role — no access keys in Terraform state or on disk:

module "rune" {
  source  = "runestack/rune/aws"
  version = "0.0.1"
 
  key_name   = "my-keypair"
  node_role  = "edge"
  acme_email = "[email protected]"
 
  docker_registries = [{
    name      = "ecr"
    registry  = "123456789012.dkr.ecr.eu-west-2.amazonaws.com"
    auth_type = "ecr"
    region    = "eu-west-2"
  }]
}

The rendered runefile carries only auth.type = "ecr" + the region; runed calls ecr:GetAuthorizationToken through the instance role at pull time. IMDSv2 is required on the instance.

Common variables

VariableDefaultWhat it does
key_name— (required)Existing EC2 key pair to install for SSH / bootstrap.
node_roleedgeedge opens 80/443 + runs ACME; worker skips both.
acme_email""Let's Encrypt account email. Required for ACME on edge nodes.
instance_typet3.mediumEC2 instance type. Edge nodes terminating TLS should be ≥ 2 vCPU / 4 GB.
ami_idautoOverride the AMI. Defaults to the latest Canonical Ubuntu 24.04 (noble) x86_64.
vpc_id / subnet_id""Placement. Empty uses the account's default VPC + a default subnet.
allocate_eipfalseAllocate an Elastic IP for an address that survives stop/start. Recommended for anything you point DNS at.
root_volume_size40Root EBS volume size (GiB). Encrypted gp3 by default.
create_iam_instance_profiletrueCreate an IAM role + instance profile for the node.
enable_ecr_accesstrueAttach AmazonEC2ContainerRegistryReadOnly to the created role.
enable_ebs_volume_accessfalseAttach an EBS volume policy so the aws-ebs storage driver can provision/attach volumes via the instance role.
iam_instance_profile""Use an existing instance profile instead of creating one.
rune_versionv0.0.1-dev.46Release tag passed to install-server.sh. Bump per Rune release.
ssh_allowed_cidrs["0.0.0.0/0"]Tighten in production.
api_allowed_cidrs["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.
name""Override the instance name. Defaults to rune-<environment>.

The full schema lives in the module README. The AWS region is set on the aws provider block, not a module variable.

Outputs

OutputWhat it is
instance_idEC2 instance ID.
public_ipPublic IPv4 — the Elastic IP when allocate_eip = true, else the auto-assigned IP.
public_dnsPublic DNS name (empty for private-only instances).
private_ipPrivate IPv4 of the instance.
grpc_endpoint<ip>:7863 — paste into rune login --server.
http_endpointhttp://<ip>:7861 — REST API base URL.
security_group_idModule-created security group ID (empty when create_security_group = false).
iam_instance_profile_nameInstance profile attached to the node (created or pre-existing).
eip_allocation_idElastic IP allocation ID (empty when allocate_eip = 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).

Amazon EBS volumes

The companion aws-ebs storage driver provisions and attaches EBS volumes to Rune volumes using the same instance role. Set enable_ebs_volume_access = true on the module so the role can call the EC2 volume actions, then create an AZ-pinned StorageClass:

storageClass:
  name: ebs-gp3-euw2a
  driver: aws-ebs
  parameters:
    region: eu-west-2
    availabilityZone: eu-west-2a
    volumeType: gp3
    fsType: ext4

See the persistent storage guide for the full walk-through, including the IAM permissions, the hostname → instance resolution, and the instance-rebuild recovery path.

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 instance exists will not take effect on the running instance — the module sets lifecycle { ignore_changes = [user_data, ami] } so a version bump or a new upstream AMI publish does not silently destroy the instance. To roll a new config:

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

This destroys and recreates the instance — your service state lives in /var/lib/rune on the instance, so plan accordingly. EBS volumes provisioned via the aws-ebs driver survive the rebuild and re-attach to the new instance on boot (as long as it lands in the same AZ).

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.

Tear down

terraform destroy

Next