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 theawsprovider, 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
:80and:443to0.0.0.0/0so the ACME HTTP-01 challenge can complete. runedbinds privileged ports as an unprivileged user viacap_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
| Variable | Default | What it does |
|---|---|---|
key_name | — (required) | Existing EC2 key pair to install for SSH / bootstrap. |
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. |
instance_type | t3.medium | EC2 instance type. Edge nodes terminating TLS should be ≥ 2 vCPU / 4 GB. |
ami_id | auto | Override 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_eip | false | Allocate an Elastic IP for an address that survives stop/start. Recommended for anything you point DNS at. |
root_volume_size | 40 | Root EBS volume size (GiB). Encrypted gp3 by default. |
create_iam_instance_profile | true | Create an IAM role + instance profile for the node. |
enable_ecr_access | true | Attach AmazonEC2ContainerRegistryReadOnly to the created role. |
enable_ebs_volume_access | false | Attach 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_version | v0.0.1-dev.46 | Release 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. |
bootstrap | false | Run 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
| Output | What it is |
|---|---|
instance_id | EC2 instance ID. |
public_ip | Public IPv4 — the Elastic IP when allocate_eip = true, else the auto-assigned IP. |
public_dns | Public DNS name (empty for private-only instances). |
private_ip | Private IPv4 of the instance. |
grpc_endpoint | <ip>:7863 — paste into rune login --server. |
http_endpoint | http://<ip>:7861 — REST API base URL. |
security_group_id | Module-created security group ID (empty when create_security_group = false). |
iam_instance_profile_name | Instance profile attached to the node (created or pre-existing). |
eip_allocation_id | Elastic IP allocation ID (empty when allocate_eip = 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). |
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
- Bootstrap & first user — what the bootstrap step actually does.
- Persistent storage — wire EBS volumes into your services.
- Expose a service — wire a service to a public hostname with ACME.
- Operations → Configuration — the
runefile.tomlreference.