Skip to main content
The Terraform stack templates live in the open-source nuonco/install-stacks repo. The defaults work out of the box in most cases, but if you need to meet specific networking, compliance, or naming requirements you can fork the repo, or create your own customer terraform module from scratch. The runner and control plane have some requirements that the Stack must fulfill. Each cloud has its own contract — the shape is similar but the resource types and payload keys differ. As long as your fork preserves the contract for the clouds you support, you can change anything else freely.

How the templates read their configuration

Each stack template fetches internal configuration from the Nuon control plane using the nuonco/stack Terraform provider, then layers the customer-provided inputs and secrets over that.
If you fork a module, keep this pattern: the customer supplies only phone_home_id, an optional api_url, the target cloud region, and any secret values — everything else comes from the data source. The attribute reference for every value the data source exposes is in The Nuon Stack Terraform Provider.

AWS

The AWS stack lives in install-stacks/aws/.

Runner instance

  • An EC2 instance (typically launched by an Auto Scaling Group) in a subnet with outbound HTTPS to the Nuon API and to GitHub raw — the boot process downloads init-mng-v2.sh and the runner binary over the public internet.
  • IMDSv2 must be enabled with required tokens. The runner authenticates to ctl-api using its Instance Identity Document, which is read from IMDS.
  • Instance tags must include:
    • nuon_runner_id
    • nuon_runner_api_url
    • nuon_install_id — used in the per-install CloudWatch log group path.
    init-mng-v2.sh reads these via ec2:DescribeTags. Missing or misnamed tags cause the runner to fail to start.
  • user_data must export RUNNER_AUTH_METHOD=iid before invoking the init script. The init script defaults to sts auth (legacy) if this is not set.

Runner IAM role

Attached to the instance via an instance profile. The inline policy must allow:
  • sts:AssumeRole on every operation, break-glass, and custom role the stack creates. The runner switches into these roles to execute components.
  • secretsmanager:GetSecretValue and secretsmanager:DescribeSecret on the secret ARN pattern your stack uses (the default stack uses <prefix>-*). Required for components that consume secrets.
  • logs:CreateLogGroup, CreateLogStream, PutLogEvents, DescribeLogStreams on /nuon/<install_id>/* and runner-* log groups.
  • ec2:DescribeTags on * — the init script’s tag lookups.

Operation, break-glass, and custom roles

  • One IAM role per [operation_role], [[break_glass_role]], and [[custom_role]] block declared in stack.toml.
  • Trust policy must allow sts:AssumeRole from:
    • The runner role ARN — the runner assumes them when executing components or actions.
    • The Nuon control-plane principals (data.stack_config.this.aws.nuon_support_iam_role_arns, falling back to account root) — used for ad-hoc actions and break-glass operations initiated from ctl-api.
  • Permissions on each role come from your stack.toml (provision_inline_policy, provision_managed_policy_arns, etc.). The stack’s job is to translate those into IAM resources.
  • Role names must match each.key from stack.toml verbatim. ctl-api looks roles up by exact name, and the default stack deliberately doesn’t double-prefix break-glass / custom roles. If you rewrap names with a prefix you’ll hit the 64-character IAM role-name limit and break role lookups.

Phone-home payload

After terraform apply succeeds, the stack reports a JSON payload to the control plane — either through the stack_phone_home resource or by POSTing to the phone_home_url from the data source. ctl-api persists every key in this payload as an install stack output, accessible from app templates as nuon.install_stack.outputs.<key>. The required keys (matching the CloudFormation phone-home Lambda payload exactly):

GCP

The GCP stack lives in install-stacks/gcp/.

Runner instance

  • A google_compute_instance (or MIG) in a subnet with outbound HTTPS to the Nuon API and to GitHub raw.
  • The runner authenticates using the runner service account’s GCP-issued identity token — no shared secrets in metadata.
  • The same init-mng-v2.sh flow applies: instance metadata must carry nuon_runner_id, nuon_runner_api_url, and nuon_install_id so the init script can read them.

Runner service account

Attached to the runner instance. The IAM policy must allow:
  • iam.serviceAccounts.getAccessToken and iam.serviceAccounts.signBlob on every operation, break-glass, and custom service account the stack creates — the runner impersonates these to execute components.
  • Read access to whatever Secret Manager secrets the stack provisions (the default stack scopes by name prefix).
  • logging.logEntries.create for runner logs.

Operation, break-glass, and custom service accounts

  • One service account per [operation_role], [[break_glass_role]], and [[custom_role]] block in stack.toml.
  • Each must grant the runner service account roles/iam.serviceAccountTokenCreator so the runner can impersonate it.
  • Permissions on each service account come from your stack.toml (inline + managed policies are translated into IAM bindings on the project).
  • Service account names must match each.key from stack.toml verbatim. ctl-api looks them up by exact short name (the part before @<project>.iam.gserviceaccount.com).

Phone-home payload

After terraform apply succeeds, the stack reports a JSON payload to the control plane (via the stack_phone_home resource or a POST to the phone_home_url from the data source). Required keys:

What you can customize freely

  • VPC / VNet / VPC layout, CIDRs, subnet count and sizes — as long as the runner subnet has working egress to the Nuon API.
  • NAT vs public-subnet egress.
  • Instance type / machine type and image — anything that supports cloud-init and can run the runner binary works (AL2023, Ubuntu LTS, Amazon Linux 2, Debian, COS).
  • Tags / labels, KMS keys, access logging, flow logs, private service endpoints, peering, transit gateways, DNS zones.
  • Wrapping the published module from a parent Terraform configuration instead of forking — install-stacks/aws and install-stacks/gcp are normal Terraform modules and can be consumed directly:
    The module reads the rest of its configuration from the control plane via the stack_config data source, so you only pass phone_home_id, the target region, and any secret overrides.

Contributing

We welcome contributions to nuonco/install-stacks. If you make any changes that you think others might find useful, please open a PR.