Skip to main content
Custom install stacks provision additional infrastructure with the customer’s install stack, before the sandbox or any components run. The same [[custom_nested_stacks]] configuration works across AWS, Azure, and GCP:
  • AWS deploys CloudFormation nested stacks.
  • Azure deploys linked ARM templates through an Azure Deployment Stack.
  • GCP instantiates reviewed Terraform modules from the install-stack repository.
GCP deliberately does not execute an arbitrary Terraform module named by app configuration. It only allows modules implemented and reviewed in the selected install-stack repository’s gcp/modules collection.

Backward compatibility

Existing valid AWS stack.toml files require no changes. The name, template_url, index, and parameters fields retain their original behavior. Azure and GCP support is additive, and apps without custom_nested_stacks continue to produce the same install stack. Configurations with duplicate names or indices now fail during sync instead of producing ambiguous resources. Per-install overrides also keep the existing merge behavior: an override with the same name replaces the app-level entry, while a new name is appended.

Capabilities

The GCP differences are intentional safety boundaries, not configuration differences. Add a reviewed module to the install-stack repository when you need a new GCP resource type.

Use Cases

  • Create Kubernetes namespaces or access entries for an existing cluster.
  • Add network, database, DNS, storage, or identity resources that must exist before the sandbox.
  • Pass those resources’ outputs to the sandbox or application components.
  • Provision stable infrastructure that should share the install stack’s lifecycle rather than redeploy with a component.

Configuration

Custom stacks are configured in [[custom_nested_stacks]] blocks in stack.toml. The block shape is the same for every cloud; only template_url identifies a cloud-specific implementation.

AWS

Relative templates are uploaded by Nuon during app sync. Existing public S3 URLs continue to work.

Azure

Azure templates must be compiled ARM JSON. A .bicep template_url or a non-JSON body fails at sync with the compile command to run:
Relative ARM templates are uploaded during sync. A public HTTPS URL also works.

GCP

For a fork, replace the repository prefix but preserve //gcp/modules/<name>. The module must exist in that fork’s GCP install stack. The built-in collection supports bucket, kms, service_account, and dns.

Properties

Use a relative AWS or Azure template path when the template lives beside the app config. Nuon uploads it during sync.

Apply the Install Stack

After syncing the app and creating an install:
  • AWS customers open the install’s CloudFormation quick link and create or update the stack as usual.
  • Azure and GCP customers apply the corresponding nuonco/stack Terraform module. Custom stacks are read from the authenticated stack_config data source; no custom-stack variables are required.
The Stack provider reads NUON_API_TOKEN and NUON_API_URL from the environment. See The Nuon Stack Terraform Provider for authentication and complete examples.

Use Custom Stack Outputs

All clouds report outputs under the same path:
For example, a sandbox or component can consume the name output from the storage stack:
Output names retain the template or curated module’s spelling. Azure matching is case-insensitive internally, so ARM’s output-name normalization does not drop values.

GCP Curated Modules

Booleans are the strings "true" or "false". Destructive options default to "false". Each module names resources from the install ID and stack name.
GCP does not support deleting Cloud KMS key rings or keys. Destroy removes the kms module from Terraform state, but the key material remains in the project. A later apply creates a new suffixed key instead of adopting or replacing the retained key.

Execution Order

AWS and Azure custom stacks execute after the built-in network and runner stacks. The index field determines the order among custom stacks:
  1. Built-in VPC or VNet stack
  2. Built-in runner stack
  3. Custom nested stacks ordered by index, ascending.
Each AWS or Azure stack depends on the previous one. A failure prevents later stacks from executing. GCP validates unique indices, but curated modules do not depend on each other. Terraform cannot order mixed module types from runtime index values without cycles or state-address changes. Set any value another stack needs in parameters or install inputs; do not rely on index for GCP data flow.

Reserved Parameters

For AWS and Azure, Nuon injects reserved parameters when the template declares them. They are never hoisted or exposed for manual input. GCP curated modules receive the install ID, project, region, and configured parameter map directly from the Terraform install-stack module.

Nuon Identity Parameters

If your template defines any of these as parameters, Nuon populates them automatically. If your template does not define them, they are omitted — no error is raised.

Role Enable Parameters

Your app’s IAM role configuration (in permissions/ and break_glass/) generates Enable* parameters in the parent stack (e.g., EnableRunnerProvision, EnableRunnerDeprovision). These are also treated as reserved parameters in custom nested stacks. If your template declares one of these parameters, Nuon passes through the parent stack’s parameter value (a Ref to the parent parameter). If your template does not declare the parameter, it is not injected. Your nested template can then use its own Conditions block to conditionally create resources based on whether a role is enabled:

Azure Reserved Parameters

On azure-bicep, the reserved set follows ARM’s camelCase convention and adds two more: Azure has no equivalent of the Role Enable parameters — per-operation access is granted through managed identities rather than template conditions.

Azure Managed Identities

If an azure-bicep custom nested stack declares a Microsoft.ManagedIdentity/userAssignedIdentities resource, Nuon adds a subscription-level role assignment granting that identity */register/action, so it can register Azure resource providers. ARM does not allow subscription-scoped deployments inside a linked deployment, so this has to live in the parent template — which means Nuon needs the identity’s principalId as an output. Your template must expose an output named identityPrincipalId, or one ending in that suffix (for example bauleiterIdentityPrincipalId, useful when a stack has several identities’ outputs to keep distinct). Matching is case-insensitive and an exact identityPrincipalId wins over a suffixed one.
If a stack declares a managed identity but exposes no matching output, install stack generation fails with an error listing the outputs it did find. This has no CloudFormation equivalent — IAM resources in an AWS nested stack are already account-scoped, so nothing needs hoisting to the parent.

First-Class Output Wiring

On AWS and Azure, if a custom template parameter matches an output from the built-in network or runner stack, Nuon wires it automatically. This lets the custom template consume built-in infrastructure without explicit configuration. For example, if the VPC template outputs VPC and RunnerSubnet, and your custom template declares parameters with those same names, they will be automatically populated:
These auto-wired parameters are not hoisted to the parent stack. On azure-bicep, the equivalent source is the built-in VNet deployment. A parameter named vnetId, vnetName, runnerSubnetId, runnerSubnetName, publicSubnet1Id, privateSubnet1Name (and the rest of the subnet outputs) is wired to [reference('vnetDeployment').outputs.<name>.value].

Inter-Stack Output Wiring

On AWS and Azure, outputs from earlier custom stacks are automatically wired to matching parameters in later custom stacks. If stack A (index 0) declares an output called SharedSubnetID, and stack B (index 1) declares a parameter called SharedSubnetID, Nuon supplies stack A’s value. This works across any number of stacks in the chain — stack C can consume outputs from both stack A and stack B. Auto-wired parameters are not hoisted to the parent stack. Precedence: First-class outputs (from the VPC and runner stacks) always take priority over custom stack outputs. If both the built-in network stack and a custom stack produce an output with the same name, the built-in output is used.
GCP curated modules currently expose outputs to Nuon, but do not wire one custom module’s output into another module.

Parameter Hoisting

On AWS and Azure, non-reserved template parameters that are not auto-wired or explicitly mapped are hoisted into the generated parent template. This means:
  • AWS parameters appear in the CloudFormation console when the customer creates or updates the stack.
  • AWS parameters are grouped under a label matching the stack name.
  • Default values from your template are preserved.
  • Parameter types are preserved.
Important: Hoisted parameter names must be unique across all nested stacks (including the VPC and runner stacks). If two stacks hoist a parameter with the same name, install stack generation fails with a conflict error. Names that are auto-wired or explicitly mapped never reach the parent, so any number of stacks may declare them.

Parameter Mapping

Use the parameters field to set a template parameter from your app config. This is useful for passing install-specific values into your nested template without hoisting them to the CloudFormation UI.
When a parameter is set explicitly:
  • Its value is rendered when the install stack is generated.
  • It is removed from the hoisted parameter set (not shown in the CloudFormation UI). Because the value is resolved before the customer applies the stack, it cannot be edited in the CloudFormation console.
  • If a referenced install input has no value, it resolves to that input’s declared default (an empty string if it has none).

Templating

Parameter values are full Go templates with sprig functions available, so you can compose literals, branch on an optional input, or transform a value:
Parameters are rendered before the customer applies the stack, so they can only reference state that exists at that point: .nuon.install.inputs.*, .nuon.inputs.inputs.*, .nuon.install.id, .nuon.app.* and .nuon.org.*.Sandbox outputs, component outputs, action outputs and install stack outputs are rejected when you sync your app config — they only exist after the stack has been applied. Reference those from component or sandbox vars instead.

Authoring AWS and Azure Templates

Template Structure

AWS templates must be valid CloudFormation YAML or JSON. At minimum:
Azure templates must be valid compiled ARM JSON with a resource-group or subscription deployment schema. Nuon reads their parameters, resources, and outputs; it does not compile Bicep source. GCP custom stacks are Terraform child modules maintained under gcp/modules in the install-stack repository. Each curated module defines its own accepted parameters and returns a single outputs object.

Naming Conventions

The name field in stack.toml is converted to a CamelCase CloudFormation logical ID. For example: The logical ID must not conflict with existing resources in the parent stack (e.g., VPC, RunnerAutoScalingGroup).

Validation Rules

The following conditions will cause a sync error:
  • Missing name or template_url
  • Duplicate name values
  • Duplicate index values across custom stacks
  • An invalid GCP curated module path
  • An azure-bicep template whose template_url ends in .bicep, or whose contents are not ARM JSON
  • A parameter template that references state unavailable before the install stack is applied
AWS and Azure generation additionally rejects invalid template documents, deployment names, output collisions, and hoisted parameter conflicts before the customer applies the stack.

Using Lambda Custom Resources

For resources that CloudFormation cannot manage natively (e.g., Kubernetes namespaces), use a Lambda-backed custom resource pattern:
  1. Define an IAM role with the minimum permissions needed
  2. Create a Lambda function inline (using ZipFile) or reference an S3 artifact
  3. Create a Custom:: resource that invokes the Lambda

Handling Delete Events

Lambda custom resources receive Create, Update, and Delete request types. Always handle the Delete event, even if it is a no-op. Failing to respond to a delete event will cause the CloudFormation stack deletion to hang.

Hosting AWS Templates

Public AWS nested template URLs must point to S3. Alternatively, use a relative path and Nuon will upload the template to its managed template bucket during app sync. Upload your templates to an S3 bucket and reference them using the full S3 URL:
Azure accepts a public HTTPS URL or an uploaded relative ARM JSON path. GCP uses a curated module path rather than a hosted template.

Example: Kubernetes Namespaces

See the byo-eks example app config for a complete working example that uses custom nested stacks to create Kubernetes namespaces and EKS access entries.

Permissions

The CloudFormation stack execution role must have permission to create the resources defined in your nested templates. For Lambda-backed custom resources, this includes:
  • iam:CreateRole, iam:PutRolePolicy, iam:AttachRolePolicy, iam:DeleteRole, iam:DeleteRolePolicy, iam:DetachRolePolicy
  • lambda:CreateFunction, lambda:DeleteFunction, lambda:InvokeFunction
  • Any permissions the Lambda function itself needs (passed via its IAM role)
These permissions are distinct from the permissions in the permissions/ directory in the app config. They are the permissions the customer has at the moment they execute the CloudFormation template.