[[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 AWSstack.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
Azure
.bicep template_url or a non-JSON body fails at sync with the compile
command to run:
GCP
//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
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/stackTerraform module. Custom stacks are read from the authenticatedstack_configdata source; no custom-stack variables are required.
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:name output from the storage stack:
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.
Execution Order
AWS and Azure custom stacks execute after the built-in network and runner stacks. Theindex field determines the
order among custom stacks:
- Built-in VPC or VNet stack
- Built-in runner stack
- Custom nested stacks ordered by
index, ascending.
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
Onazure-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 anazure-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.
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 outputsVPC and RunnerSubnet, and your custom template declares parameters with
those same names, they will be automatically populated:
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 calledSharedSubnetID, 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.
Parameter Mapping
Use theparameters 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.
- 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:Authoring AWS and Azure Templates
Template Structure
AWS templates must be valid CloudFormation YAML or JSON. At minimum: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
Thename 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
nameortemplate_url - Duplicate
namevalues - Duplicate
indexvalues across custom stacks - An invalid GCP curated module path
- An
azure-biceptemplate whosetemplate_urlends in.bicep, or whose contents are not ARM JSON - A parameter template that references state unavailable before the install stack is applied
Using Lambda Custom Resources
For resources that CloudFormation cannot manage natively (e.g., Kubernetes namespaces), use a Lambda-backed custom resource pattern:- Define an IAM role with the minimum permissions needed
- Create a Lambda function inline (using
ZipFile) or reference an S3 artifact - Create a
Custom::resource that invokes the Lambda
Handling Delete Events
Lambda custom resources receiveCreate, 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: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:DetachRolePolicylambda: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.