# create a static API token for your org Source: https://docs.nuon.co/api-ref/accounts/create-an-api-token post /v1/account/static-token Creates a long-lived static API token. By default (token_identity "service_account") each token gets its own dedicated service account and only grants access to the current org; the role param controls the token's permissions (any role assignable to API tokens; see GET /v1/roles?context=api_token) and defaults to org_read_only. With token_identity "personal" the token is issued against your own account instead: it uses your account's existing roles, is not limited to the current org, and the role param must be empty. # delete a static API token Source: https://docs.nuon.co/api-ref/accounts/delete-an-api-token delete /v1/account/static-tokens/{token_id} Deletes a static API token belonging to your current org. For service account tokens, the dedicated service account is deleted as well; for personal tokens, only the token is deleted and your account is untouched. Once deleted, the token can no longer be used to access the API. # list your org's static API tokens Source: https://docs.nuon.co/api-ref/accounts/list-api-tokens get /v1/account/static-tokens Lists the static API tokens for your current org. Token secrets are never returned. # create action workflow config Source: https://docs.nuon.co/api-ref/actions/create-action-workflow-config post /v1/action-workflows/{action_workflow_id}/configs Create an action workflow configuration. # create an action workflow run for an install Source: https://docs.nuon.co/api-ref/actions/create-an-action-workflow-run-for-an-install post /v1/installs/{install_id}/action-workflows/runs AppWorkflowConfigId param has been deprecated and is no longer being consumed, the api uses currently install id to lookup related appworkflowconfigId # create an app action workflow Source: https://docs.nuon.co/api-ref/actions/create-an-app post /v1/apps/{app_id}/action-workflows Create an action workflow for an app. # delete an action workflow Source: https://docs.nuon.co/api-ref/actions/delete-an-app delete /v1/action-workflows/{action_workflow_id} Delete an action workflow. # get action workflow for an app Source: https://docs.nuon.co/api-ref/actions/get-action-workflow-for-an-app get /v1/action-workflows/{action_workflow_id}/configs Returns all action workflow configurations. # get action workflow runs by install id Source: https://docs.nuon.co/api-ref/actions/get-action-workflow-runs-by-install-id get /v1/installs/{install_id}/action-workflows/runs Returns all action workflow runs for an install. # get action workflow runs by install id and run id Source: https://docs.nuon.co/api-ref/actions/get-action-workflow-runs-by-install-id-and-run-id get /v1/installs/{install_id}/action-workflows/runs/{run_id} Return an install action workflow run by id. # get action workflows for an app Source: https://docs.nuon.co/api-ref/actions/get-action-workflows-for-an-app get /v1/apps/{app_id}/action-workflows Returns all action workflows for the provided app. # get an app action workflow by action workflow id Source: https://docs.nuon.co/api-ref/actions/get-an-app-action-workflow get /v1/action-workflows/{action_workflow_id} Return an app action workflow by id. # get an app action workflow Source: https://docs.nuon.co/api-ref/actions/get-an-app-action-workflow-1 get /v1/apps/{app_id}/action-workflows/{action_workflow_id} Return an app action workflow by id. # get an app action workflow config Source: https://docs.nuon.co/api-ref/actions/get-an-app-action-workflow-config get /v1/action-workflows/configs/{action_workflow_config_id} Return an action workflow configuration by id. # get latest runs for all action workflows by install id Source: https://docs.nuon.co/api-ref/actions/get-latest-runs-for-all-action-workflows-by-install-id get /v1/installs/{install_id}/action-workflows/latest-runs Returns the most recent workflow run for each install action workflow. # get recent runs for an action workflow by install id Source: https://docs.nuon.co/api-ref/actions/get-recent-runs-for-an-action-workflow-by-install-id get /v1/installs/{install_id}/action-workflows/{action_workflow_id}/recent-runs Returns recent workflow runs for an install action workflow. # patch an app Source: https://docs.nuon.co/api-ref/actions/patch-an-app patch /v1/action-workflows/{action_workflow_id} Update an app action workflow configuration. # create an app Source: https://docs.nuon.co/api-ref/apps/create-an-app post /v1/apps Create a new app. # Create an app installer Source: https://docs.nuon.co/api-ref/apps/create-an-app-installer post /v1/installers # Create an app runner config Source: https://docs.nuon.co/api-ref/apps/create-an-app-runner-config post /v1/apps/{app_id}/runner-config # create an app sandbox config Source: https://docs.nuon.co/api-ref/apps/create-an-app-sandbox-config post /v1/apps/{app_id}/sandbox-config Create a sandbox configuration for an app. # create an app secret Source: https://docs.nuon.co/api-ref/apps/create-an-app-secret post /v1/apps/{app_id}/secret Create an app secret that can be used to configure components. To reference an app secret, use `.nuon.secrets.`. **NOTE** secrets can only be written, or deleted, not read. # Create app input configs Source: https://docs.nuon.co/api-ref/apps/create-app-input-configs post /v1/apps/{app_id}/input-config App input configs allow you to declare the inputs for your application, and do things such as require customer inputs or expose configuration knobs in your application. # delete an app Source: https://docs.nuon.co/api-ref/apps/delete-an-app delete /v1/apps/{app_id} Delete an app. # Delete an app installer Source: https://docs.nuon.co/api-ref/apps/delete-an-app-installer delete /v1/installers/{installer_id} # delete an app secret Source: https://docs.nuon.co/api-ref/apps/delete-an-app-secret delete /v1/apps/{app_id}/secret/{secret_id} Delete an app secret. # get all apps for the current org Source: https://docs.nuon.co/api-ref/apps/get-all-apps-for-the-current-org get /v1/apps Returns all apps for the authenticated user. # get an app Source: https://docs.nuon.co/api-ref/apps/get-an-app get /v1/apps/{app_id} Return an app. # get an app config Source: https://docs.nuon.co/api-ref/apps/get-an-app-config get /v1/apps/{app_id}/config/{app_config_id} Fetch an app config by id. # get an app config template Source: https://docs.nuon.co/api-ref/apps/get-an-app-config-template get /v1/apps/{app_id}/template-config Create an application template which provides a fully rendered config that can be modified and used to kickstart any application. # Get an app installer Source: https://docs.nuon.co/api-ref/apps/get-an-app-installer get /v1/installers/{installer_id} # get app configs Source: https://docs.nuon.co/api-ref/apps/get-app-configs get /v1/apps/{app_id}/configs Returns all configs for the app. # Get app input configs Source: https://docs.nuon.co/api-ref/apps/get-app-input-configs get /v1/apps/{app_id}/input-config # get app runner configs Source: https://docs.nuon.co/api-ref/apps/get-app-runner-configs get /v1/apps/{app_id}/runner-configs Returns all runner configurations for the provided app. # get app sandbox configs Source: https://docs.nuon.co/api-ref/apps/get-app-sandbox-configs get /v1/apps/{app_id}/sandbox-configs Returns all sandbox configurations for the provided app. # get app secrets Source: https://docs.nuon.co/api-ref/apps/get-app-secrets get /v1/apps/{app_id}/secrets List all secrets for an app. **NOTE** this does not return any sensitive values, as secrets are write only. # get latest app config Source: https://docs.nuon.co/api-ref/apps/get-latest-app-config get /v1/apps/{app_id}/latest-config Returns the most recent config for the provided app. # get latest app input config Source: https://docs.nuon.co/api-ref/apps/get-latest-app-input-config get /v1/apps/{app_id}/input-latest-config Returns the most recent input config for the provided app. # get latest app runner config Source: https://docs.nuon.co/api-ref/apps/get-latest-app-runner-config get /v1/apps/{app_id}/runner-latest-config Returns the most recent runner config for the provided app. # get latest app sandbox config Source: https://docs.nuon.co/api-ref/apps/get-latest-app-sandbox-config get /v1/apps/{app_id}/sandbox-latest-config Returns the most recent sandbox config for the provided app. # Patch v1apps config Source: https://docs.nuon.co/api-ref/apps/patch-v1apps-config patch /v1/apps/{app_id}/config/{app_config_id} Update an app config, setting status and state. # Post v1apps config Source: https://docs.nuon.co/api-ref/apps/post-v1apps-config post /v1/apps/{app_id}/config Create an app config, by pushing the contents of a config file. The API will automatically configure the app according to the config file in the background. # Post v1apps input config Source: https://docs.nuon.co/api-ref/apps/post-v1apps-input-config post /v1/apps/{app_id}/input-config App input configs allow you to declare the inputs for your application, and do things such as require customer inputs or expose configuration knobs in your application. # Render an app installer Source: https://docs.nuon.co/api-ref/apps/render-an-app-installer get /v1/installer/{installer_slug}/render # update an app Source: https://docs.nuon.co/api-ref/apps/update-an-app patch /v1/apps/{app_id} Update an app's configuration. # Update an app installer Source: https://docs.nuon.co/api-ref/apps/update-an-app-installer patch /v1/installers/{installer_id} # create a component Source: https://docs.nuon.co/api-ref/components/create-a-component post /v1/apps/{app_id}/components Create a new component for an app. # create a helm component config Source: https://docs.nuon.co/api-ref/components/create-a-helm-component-config post /v1/components/{component_id}/configs/helm Create a helm component config. # create a terraform component config Source: https://docs.nuon.co/api-ref/components/create-a-terraform-component-config post /v1/components/{component_id}/configs/terraform-module Create a terraform component config. # create an external image component config Source: https://docs.nuon.co/api-ref/components/create-an-external-image-component-config post /v1/components/{component_id}/configs/external-image Create an external image component config. # create component build Source: https://docs.nuon.co/api-ref/components/create-component-build post /v1/components/{component_id}/builds Create a build for a component. # delete a component Source: https://docs.nuon.co/api-ref/components/delete-a-component delete /v1/components/{component_id} Delete a component. # get a build Source: https://docs.nuon.co/api-ref/components/get-a-build get /v1/components/builds/{build_id} Returns builds for one or all components in an app. # get a build for a component Source: https://docs.nuon.co/api-ref/components/get-a-build-for-a-component get /v1/components/{component_id}/builds/{build_id} Returns builds for one or all components in an app. # get a component Source: https://docs.nuon.co/api-ref/components/get-a-component get /v1/components/{component_id} Return a component by id. # get a component's dependencies Source: https://docs.nuon.co/api-ref/components/get-a-components-dependencies get /v1/components/{component_id}/dependencies Returns all dependencies for the provided component. # get a components for a specific app Source: https://docs.nuon.co/api-ref/components/get-a-components-for-a-specific-app get /v1/apps/{app_id}/component/{component_name_or_id} Return an app component by id or name. # Get all builds for a component Source: https://docs.nuon.co/api-ref/components/get-all-builds-for-a-component get /v1/components/{component_id}/builds # get all components for an app Source: https://docs.nuon.co/api-ref/components/get-all-components-for-an-app get /v1/apps/{app_id}/components Returns all components for the provided app. # get all components for an org Source: https://docs.nuon.co/api-ref/components/get-all-components-for-an-org get /v1/components Returns all components for the provided organization. # get all configs for a component Source: https://docs.nuon.co/api-ref/components/get-all-configs-for-a-component get /v1/components/{component_id}/configs Returns all configurations for the provided component. # get builds for components Source: https://docs.nuon.co/api-ref/components/get-builds-for-components get /v1/builds Returns all builds for the provided component. # Get component build logs Source: https://docs.nuon.co/api-ref/components/get-component-build-logs get /v1/components/{component_id}/builds/{build_id}/logs # Get component build plan Source: https://docs.nuon.co/api-ref/components/get-component-build-plan get /v1/components/{component_id}/builds/{build_id}/plan # get latest build for a component Source: https://docs.nuon.co/api-ref/components/get-latest-build-for-a-component get /v1/components/{component_id}/builds/latest Returns the most recent build for the provided component. # get latest config for a component Source: https://docs.nuon.co/api-ref/components/get-latest-config-for-a-component get /v1/components/{component_id}/configs/latest Returns the most recent config for the provided component. # update a component Source: https://docs.nuon.co/api-ref/components/update-a-component patch /v1/components/{component_id} Update a component's configuration. # Allow user to be added to an org waitlist. Source: https://docs.nuon.co/api-ref/general/allow-user-to-be-added-to-an-org-waitlist post /v1/general/waitlist Add an entry to the waitlist. # Get config for cli Source: https://docs.nuon.co/api-ref/general/get-config-for-cli get /v1/general/cli-config Returns CLI configuration and settings. # Get current user Source: https://docs.nuon.co/api-ref/general/get-current-user get /v1/general/current-user Returns the current authenticated user account. # Get jsonschema for config file (deprecated query form) Source: https://docs.nuon.co/api-ref/general/get-jsonschema-for-config-file get /v1/general/config-schema Return jsonschemas for Nuon configs. These can be used in frontmatter in most editors that have a TOML LSP (such as [Taplo](https://taplo.tamasfe.dev/) configured. ```toml #:schema https://api.nuon.co/v1/general/config-schema/inputs description = "description" ``` You can pass in a valid source argument to render within a specific config file: - input - input-group - installer - sandbox - runner - docker_build - container_image - helm - terraform - runbook - job # Get regions for a cloud platform Source: https://docs.nuon.co/api-ref/general/get-regions-for-a-cloud-platform get /v1/general/cloud-platform/{cloud_platform}/regions Return region metadata for the Nuon supported cloud platforms. # Publish a metric from different nuon clients for telemetry purposes Source: https://docs.nuon.co/api-ref/general/publish-a-metric-from-different-nuon-clients-for-telemetry-purposes post /v1/general/metrics # Create an installer Source: https://docs.nuon.co/api-ref/installers/create-an-installer post /v1/installers # Delete an installer Source: https://docs.nuon.co/api-ref/installers/delete-an-installer delete /v1/installers/{installer_id} # Get an installer Source: https://docs.nuon.co/api-ref/installers/get-an-installer get /v1/installers/{installer_id} # Get installers for current org Source: https://docs.nuon.co/api-ref/installers/get-installers-for-current-org get /v1/installers # Render an installer Source: https://docs.nuon.co/api-ref/installers/render-an-installer get /v1/installer/{installer_id}/render # Update an installer Source: https://docs.nuon.co/api-ref/installers/update-an-installer patch /v1/installers/{installer_id} # create an app install Source: https://docs.nuon.co/api-ref/installs/create-an-app-install post /v1/apps/{app_id}/installs Create a new install for an app. # Create an app install from an installer Source: https://docs.nuon.co/api-ref/installs/create-an-app-install-from-an-installer post /v1/installer/{installer_slug}/installs # create install inputs Source: https://docs.nuon.co/api-ref/installs/create-install-inputs post /v1/installs/{install_id}/inputs Create input values for an install. # delete an install Source: https://docs.nuon.co/api-ref/installs/delete-an-install delete /v1/installs/{install_id} Delete an install. # deploy a build to an install Source: https://docs.nuon.co/api-ref/installs/deploy-a-build-to-an-install post /v1/installs/{install_id}/deploys Create a new deployment for an install. # deploy all components on an install Source: https://docs.nuon.co/api-ref/installs/deploy-all-components-on-an-install post /v1/installs/{install_id}/components/deploy-all Deploy all components to an install. This walks the graph order of the install's app, and will trigger a deploy for each on the specified install. # deprovision an install Source: https://docs.nuon.co/api-ref/installs/deprovision-an-install post /v1/installs/{install_id}/deprovision Deprovision an install sandbox. # get all deploys to an install Source: https://docs.nuon.co/api-ref/installs/get-all-deploys-to-an-install get /v1/installs/{install_id}/deploys Returns all deployments for an install. # get all installs for an app Source: https://docs.nuon.co/api-ref/installs/get-all-installs-for-an-app get /v1/apps/{app_id}/installs Returns all installs for the provided app. # get all installs for an org Source: https://docs.nuon.co/api-ref/installs/get-all-installs-for-an-org get /v1/installs Returns all installs for the provided organization. # get an install Source: https://docs.nuon.co/api-ref/installs/get-an-install get /v1/installs/{install_id} Return an install by id. # Get an install component Source: https://docs.nuon.co/api-ref/installs/get-an-install-component get /v1/installs/{install_id}/component/{component_id} # get an install component outputs Source: https://docs.nuon.co/api-ref/installs/get-an-install-component-outputs get /v1/installs/{install_id}/components/{component_id}/outputs Return the latest outputs for a component. **NOTE** requires a valid install. # get an install components deploys Source: https://docs.nuon.co/api-ref/installs/get-an-install-components-deploys get /v1/installs/{install_id}/components/{component_id}/deploys Returns all deployments for an install component. # get an install's latest deploy Source: https://docs.nuon.co/api-ref/installs/get-an-install-deploy get /v1/installs/{install_id}/deploys/latest Returns the most recent deployment for an install. # get an install deploy Source: https://docs.nuon.co/api-ref/installs/get-an-install-deploy-1 get /v1/installs/{install_id}/deploys/{deploy_id} Return a deployment for an install by id. # get an install event Source: https://docs.nuon.co/api-ref/installs/get-an-install-event get /v1/installs/{install_id}/events/{event_id} Get a single install event. # Get an installer install Source: https://docs.nuon.co/api-ref/installs/get-an-installer-install get /v1/installer/{installer_slug}/install/{install_id} # get an installs components Source: https://docs.nuon.co/api-ref/installs/get-an-installs-components get /v1/installs/{install_id}/components Returns all components for an install. # get an installs current inputs Source: https://docs.nuon.co/api-ref/installs/get-an-installs-current-inputs get /v1/installs/{install_id}/inputs/current Returns input values for an install. # get an installs inputs Source: https://docs.nuon.co/api-ref/installs/get-an-installs-inputs get /v1/installs/{install_id}/inputs Returns input values for an install. # Get an install's runner group Source: https://docs.nuon.co/api-ref/installs/get-an-installs-runner-group get /v1/installs/{install_id}/runner-group Return the runner group, including runners and settings for the provided install. # get an installs sandbox runs Source: https://docs.nuon.co/api-ref/installs/get-an-installs-sandbox-runs get /v1/installs/{install_id}/sandbox-runs Returns all sandbox runs for an install. # get events for an install Source: https://docs.nuon.co/api-ref/installs/get-events-for-an-install get /v1/installs/{install_id}/events # Get Install Events Return an event stream for an install. # Get install deploy plan Source: https://docs.nuon.co/api-ref/installs/get-install-deploy-plan get /v1/installs/{install_id}/deploys/{deploy_id}/plan # get install readme rendered with Source: https://docs.nuon.co/api-ref/installs/get-install-readme-rendered-with get /v1/installs/{install_id}/readme Returns the `app.readme` markdown with the values interpolated from the install inputs and component outputs. # Get install sandbox run logs Source: https://docs.nuon.co/api-ref/installs/get-install-sandbox-run-logs get /v1/installs/{install_id}/sandbox-run/{run_id}/logs # get the latest deploy for an install component Source: https://docs.nuon.co/api-ref/installs/get-the-latest-deploy-for-an-install-component get /v1/installs/{install_id}/components/{component_id}/deploys/latest Returns the most recent deployment for an install component. # reprovision an install Source: https://docs.nuon.co/api-ref/installs/reprovision-an-install post /v1/installs/{install_id}/reprovision Reprovision an install sandbox. # teardown an install component Source: https://docs.nuon.co/api-ref/installs/teardown-an-install-component post /v1/installs/{install_id}/components/{component_id}/teardown Teardown and remove an install component's resources. # teardown an install's components Source: https://docs.nuon.co/api-ref/installs/teardown-an-installs-components post /v1/installs/{install_id}/components/teardown-all Teardown all components on an install. # update an install Source: https://docs.nuon.co/api-ref/installs/update-an-install patch /v1/installs/{install_id} Update an install's settings. # Updates install input config for app Source: https://docs.nuon.co/api-ref/installs/update-an-install-input patch /v1/installs/{install_id}/inputs Update input values for an install. This endpoint accepts a partial subset of inputs and merges them with the install's existing inputs, so callers only need to send the inputs they want to change. Inputs sourced from the `install_stack` (customer source) are managed by the install stack and are rejected if supplied. # Updates install input config for app Source: https://docs.nuon.co/api-ref/installs/updates-install-input-config-for-app patch /v1/installs/{install_id}/inputs Update input values for an install. This endpoint accepts a partial subset of inputs and merges them with the install's existing inputs, so callers only need to send the inputs they want to change. Inputs sourced from the `install_stack` (customer source) are managed by the install stack and are rejected if supplied. # Add a user to the current org Source: https://docs.nuon.co/api-ref/orgs/add-a-user-to-the-current-org post /v1/orgs/current/user Add a user to an organization. # create a new org Source: https://docs.nuon.co/api-ref/orgs/create-a-new-org post /v1/orgs Create a new organization. # Delete an org Source: https://docs.nuon.co/api-ref/orgs/delete-an-org delete /v1/orgs/current Delete an organization. # Get an org Source: https://docs.nuon.co/api-ref/orgs/get-an-org get /v1/orgs/current Return an organization by id. # Get an orgs health checks Source: https://docs.nuon.co/api-ref/orgs/get-an-orgs-health-checks get /v1/orgs/current/health-checks # Get an org's runner group Source: https://docs.nuon.co/api-ref/orgs/get-an-orgs-runner-group get /v1/orgs/current/runner-group Get the current org's runner group, which includes the runners and their settings. # Invite a user to the current org Source: https://docs.nuon.co/api-ref/orgs/invite-a-user-to-the-current-org post /v1/orgs/current/invites Invite a user (by email) to an org. This user will receive an email, and when they next log into the application will be added to the org. # Return current user's orgs Source: https://docs.nuon.co/api-ref/orgs/return-current-users-orgs get /v1/orgs Returns all organizations for the authenticated user. # Return org invites Source: https://docs.nuon.co/api-ref/orgs/return-org-invites get /v1/orgs/current/invites Returns a list of all invites to the org. # Update current org Source: https://docs.nuon.co/api-ref/orgs/update-current-org patch /v1/orgs/current Update an organization's settings. # Create a release Source: https://docs.nuon.co/api-ref/releases/create-a-release post /v1/components/{component_id}/releases # Get a release Source: https://docs.nuon.co/api-ref/releases/get-a-release get /v1/releases/{release_id} # Get a release 1 Source: https://docs.nuon.co/api-ref/releases/get-a-release-1 get /v1/releases/{release_id}/steps # Get all releases for a component Source: https://docs.nuon.co/api-ref/releases/get-all-releases-for-a-component get /v1/components/{component_id}/releases # Get all releases for an app Source: https://docs.nuon.co/api-ref/releases/get-all-releases-for-an-app get /v1/apps/{app_id}/releases # List your org's roles Source: https://docs.nuon.co/api-ref/roles/list-roles get /v1/roles List your org's roles. Each role carries its display metadata (`title`, `description`) and the assignment surfaces it may be offered on via the `applies_to` field (`team`, `service_account`, `api_token`, `oidc_trust_policy`). A role with no `applies_to` entries exists and may be displayed, but cannot be newly assigned. Pass `?context=` to filter to the roles assignable on a single surface. # Cancel runner job Source: https://docs.nuon.co/api-ref/runners/cancel-runner-job delete /v1/runner-jobs/{runner_job_id}/cancel # get a log stream Source: https://docs.nuon.co/api-ref/runners/get-a-log-stream get /v1/log-streams/{log_stream_id} Return a log stream. # get runner job Source: https://docs.nuon.co/api-ref/runners/get-runner-job get /v1/runner-jobs/{runner_job_id} Return a runner job. # Get runner job execution Source: https://docs.nuon.co/api-ref/runners/get-runner-job-execution get /v1/runner-jobs/{runner_job_id}/executions/{runner_job_execution_id} # get runner job plan Source: https://docs.nuon.co/api-ref/runners/get-runner-job-plan get /v1/runner-jobs/{runner_job_id}/plan Return a plan for a runner job. # get runner jobs Source: https://docs.nuon.co/api-ref/runners/get-runner-jobs get /v1/runners/{runner_id}/jobs Return runner jobs. # read a log stream's logs Source: https://docs.nuon.co/api-ref/runners/read-a-log-streams-logs get /v1/log-streams/{log_stream_id}/logs Read OTEL formatted logs for a log stream. # Get a runner Source: https://docs.nuon.co/api-ref/runnersrunner/get-a-runner get /v1/runners/{runner_id} # Get runner job executions Source: https://docs.nuon.co/api-ref/runnersrunner/get-runner-job-executions get /v1/runner-jobs/{runner_job_id}/executions # get runner settings Source: https://docs.nuon.co/api-ref/runnersrunner/get-runner-settings get /v1/runners/{runner_id}/settings Return runner settings for the provided runner. # Get a sandbox Source: https://docs.nuon.co/api-ref/sandboxes/get-a-sandbox get /v1/sandboxes/{sandbox_id} # Get all sandboxes Source: https://docs.nuon.co/api-ref/sandboxes/get-all-sandboxes get /v1/sandboxes # Get sandbox releases Source: https://docs.nuon.co/api-ref/sandboxes/get-sandbox-releases get /v1/sandboxes/{sandbox_id}/releases # Create a service account for the current org Source: https://docs.nuon.co/api-ref/service-accounts/create-a-service-account post /v1/service-accounts Create a service account for the current org. Service accounts can be used to generate API tokens for automation and CI/CD workflows. Defaults to the `org_admin` role if `role` is not specified. Allowed roles are `org_admin`, `installer`, and `runner`. # Create a token for a service account in the current org Source: https://docs.nuon.co/api-ref/service-accounts/create-a-service-account-token post /v1/service-accounts/{account_id}/tokens Create an API token for a service account in the current org. Defaults to a duration of one year (`8760h`) if `duration` is not specified. If `invalidate` is set, all existing tokens for the service account are invalidated before the new token is created. # Delete a service account for the current org Source: https://docs.nuon.co/api-ref/service-accounts/delete-a-service-account delete /v1/service-accounts/{account_id} Delete a service account from the current org. This removes the service account's roles in this org and invalidates all of its existing API tokens. # List service accounts for the current org Source: https://docs.nuon.co/api-ref/service-accounts/list-service-accounts get /v1/service-accounts List the service accounts that belong to the current organization, along with their roles. Supports offset-based pagination. # Update a service account for the current org Source: https://docs.nuon.co/api-ref/service-accounts/update-a-service-account patch /v1/service-accounts/{account_id} Update a service account's human-friendly name. The account's email and ID are immutable; only the display name changes. # Update the role of a service account for the current org Source: https://docs.nuon.co/api-ref/service-accounts/update-a-service-account-role patch /v1/service-accounts/{account_id}/role Update the role assigned to a service account in the current org. The service account's existing roles in this org are removed and replaced with the requested role. Allowed roles are `org_admin`, `installer`, and `runner`. # create a vcs connection for Github Source: https://docs.nuon.co/api-ref/vcs/create-a-vcs-connection-for-github post /v1/vcs/connections Create a VCS connection for version control integration. # Get all vcs connected repos for an org Source: https://docs.nuon.co/api-ref/vcs/get-all-vcs-connected-repos-for-an-org get /v1/vcs/connected-repos # get vcs connection for an org Source: https://docs.nuon.co/api-ref/vcs/get-vcs-connection-for-an-org get /v1/vcs/connections Returns all VCS connections for the provided organization. # public connection to create a vcs connection via a callback Source: https://docs.nuon.co/api-ref/vcs/public-connection-to-create-a-vcs-connection-via-a-callback post /v1/vcs/connection-callback Handle VCS connection callback for OAuth flow. # returns a vcs connection for an org Source: https://docs.nuon.co/api-ref/vcs/returns-a-vcs-connection-for-an-org get /v1/vcs/connections/{connection_id} Return a VCS connection by id. # Your App Deployment Diagram Source: https://docs.nuon.co/architecture/app-deployment Understand what gets deployed into a customer's cloud account when you ship your app through Nuon. When you deploy your app through Nuon, it runs inside your customer's cloud account, not yours. Nuon makes this possible by layering infrastructure and execution components inside the customer's environment, all orchestrated remotely by the Nuon [Control Plane](/architecture/platform). Each deployment is called an [Install](/guides/app-install-life-cycle). Install Architecture When an install is created, the layers build on each other from the bottom up: 1. **Cloud Account** - the customer's AWS account or Azure subscription. Everything Nuon deploys lives inside it. No cross-account access is required. 2. **[Stack](/concepts/stacks)** - an infrastructure template that provisions networking and the Runner. Nuon generates both a Terraform version and the platform's native IaC version (CloudFormation on AWS, Azure Resource Manager (Bicep) on Azure; on GCP, only Terraform is generated since Infrastructure Manager is Terraform-native). The customer deploys whichever format fits their tooling, using their own credentials. 3. **[Runner](/concepts/runners)** - picks up jobs from the Control Plane and executes all infrastructure operations locally inside the customer's environment. 4. **[Sandbox](/concepts/sandboxes)** - the base compute infrastructure (Kubernetes, VMs, serverless) provisioned by the Runner. 5. **[Components](/concepts/components)** - your application (Helm charts, Terraform modules, manifests, container images) deployed on top of the Sandbox. Everything is defined by the [App Config](/concepts/apps), a set of TOML files that declare the stack, sandbox, components, inputs, secrets, and actions. Each install is an instance of this config deployed into a customer's cloud with their specific values. For how an install progresses from creation through ongoing updates, see the [App Install Life Cycle](/guides/app-install-life-cycle). # Platform Architecture Source: https://docs.nuon.co/architecture/platform How the Nuon Control Plane and Runners work together to orchestrate BYOC deployments. For an overview of what gets deployed into a customer's cloud account, see [Your App Deployment Diagram](/architecture/app-deployment). The Nuon platform consists of a Control Plane that remotely orchestrates [Runners](/concepts/runners) deployed into customer accounts. Runners are egress-only: they poll the Control Plane for jobs rather than receiving inbound commands. This allows the Control Plane to coordinate work across all installs without any access to the accounts Runners are hosted in. **No cross-account access is required.** runner-architecture ## Install Runner The Install Runner is a stateless Go binary deployed into each customer account as part of the [Stack](/concepts/stacks). It polls the Control Plane for jobs, executes them, and streams job logs back. The customer controls the Runner's access and can disable it at any time through the Stack. See [Runners](/concepts/runners) for more detail. ## Control Plane The Control Plane orchestrates and monitors jobs across all Install Runners and provides a single view of every install. ### Control API The central interface for managing apps, installs, and jobs. The [Dashboard](/dashboard) and [CLI](/cli) are built on top of it, and you can integrate with it directly to build custom interfaces. [Nuon Cloud](https://app.nuon.co) is multi-tenant with logical isolation per org. Nuon can also be deployed as a BYOC app into a customer's own cloud account. See the [Nuon BYOC repo](https://github.com/nuonco/byoc) for details. ### Runner API A separate API that Runners use to poll for jobs and write logs and metrics. This is distinct from the Control API used by vendors and operators. ### Build Runner Before components can be deployed to installs, they are built into OCI artifacts by a Build Runner hosted in the Control Plane. All installs of an app share the same artifacts. The Build Runner is the same binary as the Install Runner, but only runs build jobs. It is the only part of the Control Plane that touches your source code. Build Runners are always single-tenant. Each is deployed to its own host and does not share resources with other Runners. # Runner Authentication Source: https://docs.nuon.co/architecture/runner-auth How runners prove their cloud identity to the Nuon Control Plane without storing credentials. AWS, GCP, and Azure each provide a way to cryptographically attest the identity of a virtual machine and the account it lives in. They do this by signing instance metadata with a private key and publishing the corresponding certificates publicly, so anyone can verify the signature without any special access or trust relationship with the cloud provider or the install's account. When a runner starts, it fetches this signed credential from the instance metadata service and presents it to Nuon. Nuon independently verifies the credential directly with the cloud provider, without relying on anything the runner claims about itself. On success, Nuon issues a token that the runner uses for all subsequent calls: polling for jobs and streaming logs. ## How It Works Every auth method follows the same three-step pattern: ``` Runner Cloud Provider Nuon | | | |--- request identity ---------->| | |<-- signed credential ----------| | | | | |--- request token ------------------------------------------>| | | | | |<-- verify credential ------| | |--- response -------------->| | | | |<-- token -------------------------------------------------- | ``` The runner sends a signed credential to Nuon, which independently verifies it by calling the cloud provider directly rather than trusting what the runner claims. It then checks that the identity matches the expected configuration for that install, before returning a Nuon auth token. ## AWS — Instance Identity Document The Instance Identity Document (IID) is a JSON document that AWS generates for every EC2 instance. It is signed by AWS using a region-specific RSA-2048 key and available from the instance metadata service (IMDSv2) without any credentials. **Document**: ```json theme={null} { "accountId": "123456789012", "instanceId": "i-0a1b2c3d4e5f67890", "instanceType": "t3.medium", "region": "us-east-1", "availabilityZone": "us-east-1a", "privateIp": "10.0.1.42", "pendingTime": "2024-01-15T10:30:00Z", "architecture": "x86_64", "imageId": "ami-0abcdef1234567890" } ``` **Signature**: ``` MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEH AaCAJIAEggGpeyJhY2NvdW50SWQiOiAiMTIzNDU2Nzg5MDEyIiwgImluc3Rh bmNlSWQiOiAiaS0wYTFiMmMzZDRlNWY2Nzg5MCIsIC4uLn0AAAAAAAAxggEY ... ``` ### Flow ``` Runner (EC2) IMDSv2 Nuon | | | |--- GET /identity --------->| | |<-- document + signature ---| | | | | |--- request token------------------------------------------>| | | | | | verify PKCS7 signature | | | (regional AWS cert) | | | | | | validate account ID | | | vs install stack | | | | |<-- token ------------------------------------------------- | ``` ### What the runner sends * The raw IID JSON document from IMDSv2 * The PKCS7 signature from IMDSv2 * The runner ID ### What Nuon verifies 1. Parses the IID and validates required fields (region, accountId) 2. Verifies the PKCS7 signature using the embedded AWS regional certificate for the region in the document 3. Looks up the runner by the provided runner ID 4. Validates that the account ID in the document matches the AWS account ID recorded in the install's stack outputs If the instance is in a different AWS account than the install expects, authentication fails. ## GCP — Identity Token GCP instances can fetch a signed identity JWT from the Compute Engine metadata service. The token is audience-scoped and signed by Google's JWKS. The runner also fetches an OAuth2 access token and constructs a presigned Compute API request so Nuon can independently read instance metadata. ### Flow ``` Runner (GCE) Metadata Service Nuon | | | |--- GET identity token -------->| | |<-- signed JWT -----------------| | |--- GET access token ---------->| | |<-- OAuth2 token ---------------| | | | | |--- request token ------------------------------------------>| | | | | | validate JWT (JWKS) | | | extract claims | | | | | |<-- Compute API call -------| | |--- instance metadata ----->| | | | | | cross-validate IDs | | | validate project + | | | service account vs | | | install stack | | | | |<-- token -------------------------------------------------- | ``` ### What the runner sends * The identity JWT from the metadata service * A presigned Compute API request (method, URL, Bearer token). The runner builds the request but does not execute it. ### What Nuon verifies 1. Validates the JWT signature using Google's JWKS 2. Extracts claims: project ID, instance ID, zone, service account 3. Independently executes the Compute API request to read instance metadata 4. Cross-validates the instance ID from the JWT against the Compute API response 5. Reads the runner ID from the instance's custom metadata key 6. Validates the project ID and service account email against the install's stack outputs ## Azure — Managed Identity Azure VMs with a user-assigned managed identity can fetch a JWT from the Azure Instance Metadata Service. The token contains the identity's tenant, subscription, and resource information and is signed by Microsoft's JWKS endpoint for the tenant. ### Flow ``` Runner (Azure VM) IMDS Nuon | | | |--- GET identity token ---->| | |<-- managed identity JWT ---| | | | | |--- request token--- -------------------------------------->| | | | | | extract tenant ID | | | fetch JWKS for tenant | | | validate JWT sig | | | extract claims | | | | | | extract runner ID | | | from xms_mirid claim | | | | | | validate tenant ID + | | | subscription ID vs | | | install stack | | | | |<-- token ------------------------------------------------- | ``` ### What the runner sends * The managed identity JWT from the Azure IMDS ### What Nuon verifies 1. Reads the tenant ID from the JWT without verifying it (needed to find the right JWKS endpoint) 2. Fetches the JWKS for that tenant 3. Validates the JWT signature and claims 4. Extracts the runner ID from the managed identity resource ID in the token 5. Validates the tenant ID and subscription ID against the install's stack outputs ## Token Issuance All three methods produce the same result: a token scoped to the runner, used for all subsequent API calls. Tokens are not stored by the runner between restarts. The runner re-authenticates on every startup. ## Security Properties * **No stored credentials.** Identity is derived from the runtime environment at startup. * **Server-side verification.** Nuon independently calls the cloud provider (Google JWKS, Azure JWKS) or verifies the credential locally (AWS PKCS7 signature with embedded regional cert). It does not trust the runner's self-reported identity. * **Install-scoped validation.** Every method validates the credential's cloud identity against the specific install's stack outputs. A runner in the wrong account, project, or tenant cannot authenticate, even with a valid credential. # CLI Source: https://docs.nuon.co/cli Install the Nuon CLI. ## Installation ### Homebrew We publish an official tap you can install with [Homebrew](https://brew.sh/). ```sh theme={null} brew install nuonco/tap/nuon ``` After installation, you may need to refresh your shell session. ```sh theme={null} source ~/.bashrc # or ~/.zshrc, etc. ``` You can also enable autocomplete for you shell. Run `nuon completion` to see what shells we support. ```sh theme={null} source <(nuon completion ) ``` The Nuon CLI changes frequently. To ensure you have the latest version, run `brew upgrade nuonco/tap/nuon` regularly. If you encounter any CLI command errors, please check you are using the latest version. ### Install Script If you are not using Homebrew, you can use our install script to automatically download and install the correct binary for your platform (macOS or Linux): ```sh theme={null} bash <(curl -sSL https://nuon-artifacts.s3.us-west-2.amazonaws.com/cli/install.sh) ``` The script automatically detects your OS and architecture, downloads the latest version, and installs it to your path. ### Manual Binary Download You can also download binaries directly for macOS or Linux: * `darwin_amd64` * `darwin_arm64` * `linux_arm` * `linux_arm64` * `linux_386` * `linux_amd64` Binaries are published to [https://nuon-artifacts.s3.us-west-2.amazonaws.com/cli](https://nuon-artifacts.s3.us-west-2.amazonaws.com/cli), and versioned by release. To download a binary, first look up the current API version, and then download the current binary for your platform. To look up the current API version: ```sh theme={null} curl https://api.nuon.co/version ``` To download the binaries for a specific version, substitute the version and platform into the following URL: `https://nuon-artifacts.s3.us-west-2.amazonaws.com/cli/$VERSION/nuon_${PLATFORM}` An example of downloading version `0.19.421` of the CLI for `darwin_arm64`: ```sh theme={null} wget -O nuon https://nuon-artifacts.s3.us-west-2.amazonaws.com/cli/0.19.421/nuon_darwin_arm64 ``` Once the binary has been downloaded remember to make it executable. ```sh theme={null} chmod +x nuon ``` ### Updates You can update the CLI using Homebrew: ```sh theme={null} brew update brew upgrade nuonco/tap/nuon ``` Or, if you installed via the install script, simply run it again to get the latest version. For manual binary installs, download the latest version from the S3 bucket as described above. ### Debug Mode In the case that something goes wrong, you can set the environment variable `NUON_DEBUG` to print verbose logs. These can be helpful to share with us, while debugging any issues. ```sh theme={null} NUON_DEBUG=true nuon apps list ``` ### Using the CLI against a BYOC Instance If you are using a Nuon BYOC instance, i.e., a self-hosted Nuon control plane running in your own infrastructure, you can use the CLI to interact with it. To do this, you can: ```sh theme={null} NUON_API_URL=https://api.your-byoc-instance.com nuon auth login # or export NUON_API_URL=https://api.your-byoc-instance.com nuon auth login #or other nuon CLI commands. ``` By default, the CLI will use api.nuon.co as the API URL. `nuon auth login` also prompts the user to either use Nuon Cloud (api.nuon.co) or Nuon BYOC where the user can enter their Nuon control place URL Nuon auth login ## Nuon Preview Features We are working on a number of TUIs for the CLI. These include contextual TUIs for object selection and full-page TUIs for workflows and actions. These features can be enabled by setting `NUON_PREVIEW=true`. Nuon CLI TUI At the time of writing, we have experimental TUIs for actions, workflows, and for creating new installs. ### Nuon CLI Extensions The `nuon` cli has support for extensions. You can browse nuon-authored extensions with `nuon ext browse` and author your own. At the time of writing, we have published a handful of extensions for public consumption. Try our api extension with `nuon install api`. Nuon Extensions # Common commands Source: https://docs.nuon.co/cli-commands The Nuon CLI commands you'll reach for most, from auth to deploy. The commands you'll use most. Run `nuon --help` (or `nuon --help`) for the full set. ## Global flags These work on every command. | Flag | What it does | | ------------------------------ | --------------------------------------------------------------------------------------------------- | | `--config `, `-C ` | Use a custom config file instead of `~/.nuon`. Can also be set with the `NUON_CONFIG_FILE` env var. | | `--output ` | Output format: `table` (default), `json`, or `agent`. Can also be set with `NUON_OUTPUT`. | | `--read-only` | Block any command that modifies state. Can also be set with `NUON_READ_ONLY=1`. | | `--debug` | Print per-request API timing to stderr. | ## Authenticate | Command | What it does | | ------------------ | -------------------------------------- | | `nuon auth login` | Sign in (browser SSO). Run this first. | | `nuon auth logout` | Sign out. | ## Organizations | Command | What it does | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nuon orgs select` | Switch your active org. A new trial account has a single org that's selected automatically when you log in, so you only need this once you belong to more than one. | | `nuon orgs current` | Show the active org. | | `nuon orgs api-tokens create --name ` | Create a durable API token for the current org (shown once). Requires org admin. Add `--role` (defaults to `org_read_only`; run `nuon roles list` or see [Access control](/concepts/access-control)) to set the token's permissions, or `--personal` to issue the token against your own account and its existing roles instead. | | `nuon orgs api-tokens list` | List the current org's API tokens. | | `nuon orgs api-tokens delete --id ` | Revoke an API token. | | `nuon orgs update-user-role --user-id --role ` | Change an org member's role (run `nuon roles list` or see [Access control](/concepts/access-control)). Requires org admin. | ## Apps & config | Command | What it does | | -------------------------------- | --------------------------------------------------------------------------------------------- | | `nuon apps create --name ` | Create an app in the control plane. | | `nuon apps sync` | Sync the config in the current directory to Nuon. The app name comes from the directory name. | | `nuon apps validate` | Validate the config in the current directory. | | `nuon apps list` | List your apps. | | `nuon apps select` | Set your active app. | ## App branches These subcommands take the app **ID** (`app...`) — get it from `nuon apps list`. Running `nuon apps select` with no flags and picking from the list stores the ID, which lets you omit `--app-id` from `trigger` and `runs`. | Command | What it does | | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nuon sync --branch ` | Sync the current directory, then trigger a run on that [app branch](/guides/app-branches). Add `--preview` for a plan-only run, or use `--app-branch` to pick the branch interactively. These flags are on `nuon sync`, not `nuon apps sync`. | | `nuon apps branches list --app-id ` | List an app's branches. Alias: `nuon apps br`. | | `nuon apps branches get --app-id --branch-id ` | Show a branch and its deployment groups. | | `nuon apps branches create --app-id --name ` | Create a branch without syncing a config file. | | `nuon apps branches trigger --branch-id ` | Trigger a branch run against the selected app. `--preview` plans every group but applies nothing, `--force` rebuilds every component. | | `nuon apps branches runs --branch-id ` | List the selected app's branch runs and open one in the workflow TUI. | | `nuon apps branches delete --app-id --branch-id --confirm` | Delete a branch. | ## Installs | Command | What it does | | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `nuon installs create` | Create an install of the selected app. | | `nuon installs list` | List installs. Add `--labels k=v` (repeatable) to filter; all labels must match. | | `nuon installs get` | Show an install's details. | | `nuon installs labels set --install-id k=v ...` | Set labels on an install. Deployment group [label selectors](/guides/app-branches#selecting-installs-for-a-group) match on these. | | `nuon installs labels list --install-id ` | List an install's labels. | | `nuon installs labels unset --install-id ...` | Remove labels from an install. | | `nuon installs components deploy-all` | Deploy all components to an install. | | `nuon installs outputs` | View an install's outputs. | ## Operate | Command | What it does | | --------------------------------- | --------------------------------------------------------- | | `nuon runbooks --install-id ` | View and run [runbooks](/guides/runbooks) for an install. | | `nuon actions` | Manage and run app [actions](/guides/actions). | | `nuon components` | Manage app components. | ## Agents / MCP Operate Nuon from an LLM client. See [Agents](/guides/agents/overview). | Command | What it does | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nuon agents context` | Print auth, selected org/app/install, and the MCP HTTP URL. | | `nuon agents mcp` | Read-only stdio MCP proxy. Its URL is derived from the configured API hostname (`api.` becomes `mcp./mcp`). Add `--allow-writes` to expose mutating tools. `--url` / `--name` override the upstream server and the name in the client's MCP list. | | `nuon agents mcp setup --platform ` | Write stdio MCP client config (`claude-code`, `cursor`, or `amp`). `--url` / `--name` / `--allow-writes` are passed through to `nuon agents mcp`. | ## Help | Command | What it does | | ------------------------- | ------------------------------- | | `nuon --help` | Help for any command. | | `nuon docs` | Open the docs. | | `nuon completion ` | Generate shell autocompletion. | | `nuon version` | Show the installed CLI version. | # Access Control Source: https://docs.nuon.co/concepts/access-control How to control what team members and service accounts can do in your Nuon org. Every identity in your Nuon org — whether a team member or a [service account](/concepts/service-accounts) — must be assigned a **role**. The role determines what that identity can read and change in the org. Roles are org-scoped: a role assigned in one org grants no access in any other org. ## Available roles | Role | Value | Grants | | ------------- | --------------- | -------------------------------------------------------------------------------------- | | **Admin** | `org_admin` | Full access to the org and everything in it, including team and credential management. | | **Read-only** | `org_read_only` | Read-only access to everything in the org; cannot make changes. | ## Assigning roles The same role set applies everywhere an identity is created. You choose a role when you: * [Invite a team member](/guides/team-management) — or change an existing member's role * Create a [service account](/concepts/service-accounts) * Create an [API token](/concepts/api-tokens) * Create an [OIDC trust policy](/concepts/oidc-federation) [API tokens](/concepts/api-tokens) (unless created as personal tokens) and [OIDC trust policies](/concepts/oidc-federation) are each backed by a dedicated [service account](/concepts/service-accounts) that carries the role — so a token or an exchanged token acts as that service account, with exactly the access its role grants. A personal API token instead acts as the team member who created it, with their role. An identity holds one role at a time; assigning a new role replaces the old one. ## Permission errors When an identity attempts something its role does not allow, Nuon returns a message describing the access the action requires and the role you currently hold — for example, "this action requires write access to installs in this organization." Ask an org admin to assign a role that grants the needed access. ## Reserved roles The **Runner** role is reserved for the machine accounts Nuon provisions for your runners. It is assigned automatically and is never user-selectable. The **Stack** role is reserved for the machine accounts Nuon provisions for install stacks managed with the Terraform module. It is assigned automatically, is never user-selectable, and — unlike the org-wide roles — grants access scoped to a single install. See [Stack service accounts](/concepts/service-accounts#stack-service-accounts) for details. # Actions Source: https://docs.nuon.co/concepts/actions Automate operational tasks and workflows in installs. Actions allow you to create automated workflows that can be run in installs. Actions are useful for debugging, running scripts, and implementing health checks. ## What are Actions? Actions are reusable workflows that can be configured to run on your installs. Each action consists of: * A trigger that determines when the action runs * One or more steps that define what the action does Actions can be used for: * Running database migrations * Executing maintenance scripts * Collecting diagnostic information * Automating operational tasks * Running custom health checks ## How do you configure an Action? Create an `actions` directory at the root of the app, and create a TOML file for each action. e.g., `alb_healthcheck.toml` `deployment_restart.toml` `kubectl_logs.toml`. Actions along with components, the sandbox and app metadata are sent to the Nuon control plane with the CLI command `nuon apps sync`. If you change and add additional actions, you need to run `nuon apps sync` again to upload the changes. But unlike components, actions do not need to be built. Actions list If actions need to connect to a VCS, use either a public repo (using a `public_repo` block) or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). For example, to pull logs from all Kubernetes pods in a namespace, you would write an action like this: ```toml actions/kubectl_logs.toml theme={null} # action name = "kubectl_logs" timeout = "30s" [[triggers]] type = "manual" [[steps]] name = "kubectl logs" inline_contents = """ #!/usr/bin/env sh kubectl logs -n $NAMESPACE --all-containers=true -l app.kubernetes.io/name=ctl-api-api """ [steps.env_vars] NAMESPACE = "default" ``` For example, if you wanted to implement a healthcheck for an AWS ALB, you would write something like this: ```toml actions/http_healthcheck.toml theme={null} # action name = "http_healthcheck" timeout = "0m15s" [[triggers]] type = "cron" cron_schedule = "*/5 * * * *" [[triggers]] type = "manual" [[steps]] name = "healthcheck" command = "./healthcheck" [steps.public_repo] repo = "nuonco/actions" branch = "main" directory = "common" [steps.env_vars] ENDPOINT = "https://your-app.{{.nuon.install.sandbox.outputs.public_domain.name}}" METHOD = "HEAD" EXPECTED_STATUS_CODE = "200" ``` We maintain a [collection of commonly-used actions](https://github.com/nuonco/actions) in an open-source repo for you to get started with. ## Container Actions By default an action's steps run directly on the install [runner](/concepts/runners) VM, so they are limited to the tooling that VM carries. An action can instead declare a container `image`, in which case every step runs inside that image and ships with whatever tools it needs: ```toml actions/db_migrate.toml theme={null} # action name = "db_migrate" timeout = "10m" image = "ghcr.io/acme/migrate-tools:v1.4.0" ``` The image can be a public reference, or the `image.ref` output of a [container image component](/guides/container-image-components) when the tools image is private. See [Container actions](/guides/container-actions) for how to configure one. ## Running Actions Actions can be triggered in several ways: * Manually via the dashboard or CLI * On a schedule * In response to events Action workflow To run an action manually with the CLI: ```sh theme={null} nuon actions create-run -w -i ``` ## Action Triggers Actions can run manually, on a cron schedule, or in response to install lifecycle events. The supported triggers that are not tied to a specific component are: * `manual` * `cron` * `pre-provision` * `post-provision` * `post-provision-sandbox` * `pre-reprovision` * `post-reprovision` * `pre-deprovision` * `post-deprovision` * `pre-deploy-all-components` * `post-deploy-all-components` * `pre-teardown-all-components` * `post-teardown-all-components` * `pre-deprovision-sandbox` * `post-deprovision-sandbox` * `pre-reprovision-sandbox` * `post-reprovision-sandbox` * `pre-update-inputs` * `post-update-inputs` * `pre-secrets-sync` * `post-secrets-sync` * `role-enabled` * `role-disabled` Each workflow trigger is called at the beginning or end of the workflow. In some cases, such as `pre-provision` or `pre-reprovision` that include a stack-run, the trigger will be called right after the runner is healthy. `post-provision-sandbox` runs immediately after the initial sandbox apply succeeds, before secrets sync, DNS provisioning, and component deployment. `post-provision` runs after the complete install provision workflow, including component deployment. ### Role change triggers The `role-enabled` and `role-disabled` triggers fire when a customer enables or disables an [operation role](/concepts/operation-roles) in their install stack. Use these to run validation, auditing, or setup tasks whenever elevated permissions are granted or revoked. ### Component triggers The following triggers require a `component_name` field to be set, as they are tied to a specific component: * `pre-deploy-component` * `post-deploy-component` * `pre-teardown-component` * `post-teardown-component` * `pre-enable-component` * `post-enable-component` * `pre-disable-component` * `post-disable-component` The enable and disable triggers run when an input update changes a toggleable component's enabled state. `pre-component-deploy` and `post-component-deploy` have been renamed to `pre-deploy-component` and `post-deploy-component` for consistency with other triggers. `pre-sandbox-run` and `post-sandbox-run` have been deprecated, in favor of `pre|post-reprovision`, `pre|post-provision`, and `pre|post-deprovision` Action Triggers are documented in the [Changelog 009](/updates/009-action-triggers-secrets-configs#action-triggers). ## Action History You can view the history of action runs using the dashboard: Action workflows & steps You can view the history of action runs using the CLI: ```sh theme={null} nuon actions list-runs -w -i ``` Or get details about a specific run: ```sh theme={null} nuon actions get-run -r -i ``` ## Action Permissions Actions are run with the same permissions as the [Runner](/concepts/runners) in each install. # API Source: https://docs.nuon.co/concepts/api Create and manage installs programmatically via the Nuon Control Plane API. Use the [Nuon API](https://api.nuon.co/docs/index.html) to embed install management in your own product, automate provisioning at scale, or integrate Nuon into an existing workflow engine. ## End-to-end examples For a step-by-step walkthrough of creating, provisioning, updating, and deprovisioning an install over the API — with both curl and Go SDK examples — see the [Integrate into your Product guide](/guides/control-plane-integration). # API Tokens Source: https://docs.nuon.co/concepts/api-tokens Durable tokens for authenticating to the Nuon Control Plane API. An **API token** authenticates requests to the [Nuon Control Plane API](/concepts/api). Use one to call the API, drive the CLI in CI/CD, or integrate Nuon into your own product. ## Token types A token is issued against one of two identities: * **Service account token** (the default) — the token is backed by its own [service account](/concepts/service-accounts), which carries the role you choose. The token acts as that service account and only grants access to the current org. This service account is created and managed for you. If you want a service account you manage explicitly and can mint multiple tokens for, create one directly instead. * **Personal token** — the token is issued against your own account and uses your existing roles. It has the same access as you (including your other orgs) and stops working if your account loses access. Personal tokens have no role property. ## Token properties | Property | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name** | A human-readable label to identify the token. Required. | | **Role** | Service account tokens only. The permissions the token grants. Defaults to `org_read_only`. See [Access control](/concepts/access-control) for the available roles. | | **Duration** | How long the token is valid. Defaults to `8760h` (one year). | Creating and managing API tokens requires **org admin** access. ## Managing tokens You can manage tokens using both the Dashboard and the CLI. In the dashboard, go to **Settings → API tokens → Create token**. In the CLI, use the following commands. ### Creating a token Create a token granting access to your current org: ```sh theme={null} nuon orgs api-tokens create --name my-token ``` By default, tokens will be read-only and expire after one year. Override these if you need admin access or a different expiration: ```sh theme={null} nuon orgs api-tokens create --name ci-token --role org_admin --duration 720h ``` Add `--personal` to create a personal token issued against your own account instead (cannot be combined with `--role`): ```sh theme={null} nuon orgs api-tokens create --name my-token --personal ``` Tokens are shown **only once** at creation time. Store the value somewhere safe (a secret manager or CI secret); you cannot retrieve it again later. ### Listing tokens View all active tokens in the org. ```sh theme={null} nuon orgs api-tokens list ``` ### Revoking tokens Delete a token to revoke its access immediately. ```sh theme={null} nuon orgs api-tokens delete --id ``` ## Using a token To use a token, include it in API requests as a Bearer credential in the `Authorization` header: ```sh theme={null} curl https://api.nuon.co/v1/current-user \ -H "Authorization: Bearer " ``` You can also use a token with the CLI by setting it as the value of the `NUON_API_TOKEN` environment variable or the `api_token` config value. ```sh env theme={null} export NUON_API_TOKEN= ``` ```sh ~/.nuon theme={null} api_token: ``` # App Branches Source: https://docs.nuon.co/concepts/app-branches App branches connect a git branch to your app, so a push rolls the change out across your fleet of customer installs through ordered deployment groups with a plan and an approval at every step. ## What is an App Branch? An app branch connects one git branch to your [app](/concepts/apps). When you push to that branch, Nuon fetches the config at that commit, builds whatever changed, and then rolls the new version out across your customer [installs](/guides/app-install-life-cycle), in an order you define, with a plan and an approval gate in front of every step. Before app branches, updating a fleet meant running `nuon apps sync` from your CLI or wiring up a GitHub Action with an API token, then driving each install yourself. An app branch makes the git push the trigger, and makes the rollout a single reviewable workflow instead of a sequence of manual steps. If you are new to Nuon, read [apps](/concepts/apps) and the [app and install life cycle](/guides/app-install-life-cycle) first. ## The end-to-end flow ```mermaid theme={null} flowchart TD push["git push to the tracked branch"] --> fetch["fetch commit
clone the repo at that SHA"] fetch --> config["fetch app config
parse and sync the app config"] config --> build["building components and sandbox
only what changed since the last successful run"] build --> plan1["plan install group: staging
diff per install"] plan1 -->|approve| deploy1["deploy install group: staging"] deploy1 --> plan2["plan install group: production
diff per install"] plan2 -->|approve| deploy2["deploy install group: production"] ``` Each box is a step in a single workflow, visible in the dashboard and in the CLI workflow TUI. The step names above are the real ones you will see. Two of them behave specially: * **`plan install group: `** is an approval step. It computes the config diff for every install in the group and then waits. Nothing is applied to those installs until a human approves it in the dashboard, or an API caller advances it. * **`plan install group: `** and **`deploy install group: `** are skippable, so you can let a rollout move past a group without deploying to it. **`building components and sandbox`** builds your components on every run. It also builds the sandbox into an artifact when [sandbox builds](/guides/sandbox-builds) are enabled for your org — without that feature it builds components only. Groups run in the order you declare, and the approval in front of each group is what makes the rollout staged: group two does not start until you approve it. A branch's deployment plan in the Nuon dashboard: a stage group selecting installs labelled env=stage flowing into a production group selecting env=prod, with each group's installs listed below ## Run types Every run of an app branch is an *app branch run*. There are three kinds, and they differ in what they are allowed to touch. | Run type | Triggered by | What it does | | ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `git-run` | A push to the tracked branch | Builds, then plans and deploys each deployment group in order. | | `git-preview-run` | Opening or updating a pull request against the tracked branch | Plan only. Builds and reports the config diff, then stops. Never touches an install. | | `manual-run` | `nuon apps branches trigger`, the API, the dashboard, or your CI | Same steps as a `git-run`. Add `--preview` and it still plans every group; approvals auto-approve and nothing is applied. | ```mermaid theme={null} flowchart LR subgraph git["Git events"] p["push"] --> gr["git-run"] pr["pull_request
opened or synchronize"] --> gpr["git-preview-run"] end subgraph manual["CLI, API, or dashboard"] t["trigger"] --> mr["manual-run"] end gr --> wf["app branch run workflow"] gpr --> wf mr --> wf ``` Merging a pull request starts a `git-run` through the push it makes to the tracked branch. A `git-preview-run` stops after `building components and sandbox`. The per-group plan and deploy steps are not created at all, so a pull request cannot touch an install. A manual plan-only run works differently. `--preview` suppresses the *apply* and the waiting, not the workflow: the run creates every group's `plan install group` and `deploy install group` step, each approval gate auto-approves with "Auto-approved in plan-only mode", and the run finishes on its own with nothing applied. You get the same per-install diffs you would in a real rollout, readable from the completed run. ## Deployment groups A deployment group is a named subset of your installs that receives an update as a unit. Groups are the unit of ordering and the unit of approval. The dashboard calls these **deployment groups**. The TOML key is `install_groups`, and the API routes use `install-groups`. They are the same thing. You declare groups on the branch config, and each group picks its installs one of two ways: * **By labels** — a `label_selector` of key/value pairs. Any install carrying all of those labels is in the group, and membership updates itself as you label and unlabel installs. * **Explicitly** — a static list of `install_ids`, or of `install_names` that Nuon resolves to IDs when you sync. A group uses one or the other; a label selector cannot be combined with an explicit list in the same group. ```toml branch.toml theme={null} name = "main" [connected_repo] repo = "acmeco/my-app-config" directory = "." branch = "main" [[install_groups]] name = "staging" order = 1 [install_groups.label_selector] env = "staging" [[install_groups]] name = "production" order = 2 [install_groups.label_selector] env = "prod" ``` `order` controls which group deploys first: lower runs first. Declare your groups in the order you want them to run; an unset `order` falls back to the position in the file. ### Rolling out gradually A canary is an ordinary first group with a narrow selector: label one or two installs `wave = "early"`, give that group the lowest `order`, and it plans and waits for approval ahead of everyone else. Approve it, watch it, and then approve the next group when you are satisfied. There is no separate canary feature to configure. ## Pull request previews When you open a pull request against a tracked branch, or push another commit to one, Nuon runs a plan-only preview and reports it on the pull request itself: * a comment titled `## Nuon Preview — ` (the branch's `name`, so usually `main`), containing a table of config changes broken down by section (Section / Added / Changed / Removed) with the individual entries expandable underneath. * a mode-specific commit status such as `nuon/// preview (plan-only)`, which links to the preview run and moves from pending to success or failure. If the pull request does not change the app config, the run stops early and the comment reads ``No changes to `nuon.toml` detected in this PR. Preview skipped.`` Posting to the pull request needs an **org-level GitHub connection**, not a particular kind of branch config: your org must have the Nuon GitHub App connected for the repository's owner. That is satisfied by a `[connected_repo]` branch, and equally by a `[public_repo]` branch whose owner your org has connected. A public `acmeco/*` repo gets previews as long as the org has connected `acmeco`. See [connecting a repository](/guides/vcs). Previews start posting as soon as the GitHub App connection for the repository's owner is in place. If they have not appeared yet, check that connection rather than the branch config. ## Plans and diffs Every change a branch run applies to an install is planned and reviewed first. Each `plan install group` step computes a diff per install in that group. Branch rollouts always require an approval on each group's plan step. There is no configuration that auto-approves a deployment group. ## Install version history Every config change to an install is recorded as an *app config version*: a snapshot of what that install was expected to be running at a point in time. The history is available per install in the dashboard and over the API, along with the diff between any two versions. Because any past version can be applied again, the history doubles as a recovery path: if a change turns out badly, you re-apply an earlier version rather than hunting for what the old values were. The [rollback guide](/guides/rollback-install-config) walks through the exact calls. ## Notifications and automation Branch runs emit the same signals as the rest of the platform, so you can watch them without polling: * **[Slack](/guides/slack)** — branch run notifications follow your org's existing Slack preferences. * **[Webhooks](/guides/webhooks)** — branch runs emit workflow and workflow step lifecycle events (`com.nuon.workflow.lifecycle.v1`) with `data.workflow.owner_type` of `app_branches`, plus an event when an app config is synced. External systems can react to a plan being ready, or to a group finishing, and can drive the next approval through the API. ## Rules and limits Know these constraints before you design your branch layout: * All branches of one app must point at the **same repository**. The branch and directory within it may differ. * An install can belong to **one app branch** at a time. * Two branches of the same app cannot use an **identical label selector**. * Every push to the tracked branch starts a run. There are no path filters. Scope what a branch watches with the `directory` field on the repo block. * `install_names` are resolved to install IDs when you sync, and an unknown name fails the sync. Renaming an install detaches it from its group until the next sync, which is the main reason to prefer `label_selector`. ## Next steps Write a branch config, label your installs, trigger runs, and set up pull request previews. Add an app branch to the `eks-simple` example app end to end. Every field on `branch.toml`. How plans, approvals, and diffs work for an individual install. # Inputs Source: https://docs.nuon.co/concepts/app-inputs Inputs allow you to define customer provided values that can be used to configure your app. Inputs are variable parameters used for configuring your app. Inputs can either be user facing or vendor facing. By default all inputs are vendor facing. ## Defining Inputs Inputs are configured in a `inputs.toml` file inside the root of your app directory. Add inputs to your App using the following configuration block ```toml inputs.toml theme={null} # inputs [[group]] name = "network" description = "Configure the install's network settings." display_name = "Network" name = "vpc_id" display_name = "VPC ID" group = "network" description = "ID of the VPC to install the app into." default = "" required = true sensitive = false ``` ### Customer facing inputs A customer facing input is a parameters that can be tweaked by customer via stack inputs (AWS CloudFormation, Azure Resource Manager (Bicep), GCP Terraform) during install creation or any time after that. To mark an input as customer facing mark the parameter `user_configurable = true` in the app input config. Example: ```toml inputs.toml theme={null} [[input]] name = "sub_domain" display_name = "Sub Domain" description = "The sub domain for the Whoami service" group = "dns" default = "whoami" sensitive = false type = "" user_configurable = true ``` Customer facing inputs cannot be modified via the vendor in any way, to modity the input value, customer must go to the install stack and update the input parameter. This triggers a callback to the install which then updates/deploys any/all components which depends on that particular input(s). ## Configuration Using Inputs Inputs can be used to configure both sandboxes and components. All inputs are made available via [variable](/guides/using-variables) configuration. Inputs are defined in a `inputs.toml` file inside the root of your app directory. All inputs are accessed using `{{.nuon.install.inputs.}}`. ### Configuring Sandboxes You can configure Sandboxes by using inputs as `var`s. For example, to use the `root_domain` input, as a variable to configure the sandbox: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-karpenter-sandbox" branch = "main" [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" ``` ### Configuring Components Depending upon the component type, you can use install inputs to configure components using Helm values, Terraform variables or environment variables. To use Inputs in a Terraform module. ```toml components.database.toml theme={null} # terraform type = "terraform_module" name = "database" terraform_version = "1.11.3" [public_repo] directory = "terraform" repo = "your-org/your-repo" branch = "main" [vars] DATABASE_PASSWORD = "{{.nuon.install.inputs.database_password}}" DATABASE_HOST = "{{.nuon.install.inputs.database_host}}" ``` To use inputs in a Helm chart Component. ```toml components/helm-database.toml theme={null} # helm type = "helm_chart" name = "helm-database" chart_name = "database" [public_repo] directory = "helm" repo = "your-org/your-repo" branch = "main" [values] DATABASE_PASSWORD = "{{.nuon.install.inputs.database_password}}" DATABASE_HOST = "{{.nuon.install.inputs.database_host}}" ``` ## App Input Groups You can organize inputs into groups, which are useful for structuring how they are visualized in the install. By default, all inputs are created in the `default` group. To create a group, and add an input to it, simply declare the group in your config or terraform and set the `group` field on any input belonging to it. ```toml inputs.toml theme={null} # inputs [[group]] name = "db" description = "Configuration for the database " display_name = "Database configuration" [[input]] name = "db_password" description = "Password to use for the database." default = "" sensitive = true display_name = "Password" group = "db" ``` ## Inputs For Customer Resources Inputs are commonly used to allow a customer to provide access to existing resources. For example, an input can be used for: * accepting a database url, or database ID to connect to * accepting a VPC ID to install an app into * accepting an existing S3 bucket name to use for storage * accepting an existing Kubernetes cluster ID to connect to * accepting a token to access a third party cloud provider or account ## Inputs when Installing an App When installing an app, the customer is presented with a modal form to fill out the inputs defined in the app's `inputs.toml` file. Inputs ## Updating Inputs If you add a required input *after* an install is created, the inputs must be added to the install before it can process any new sandbox updates or component provision/deprovision jobs. Updating inputs We recommend avoiding adding backwards incompatible input changes in the same app. # Secrets Source: https://docs.nuon.co/concepts/app-secrets Define and manage sensitive values — credentials, API keys, tokens — used to configure app components and actions. Secrets allow you to configure components with sensitive values and keys. Secret metadata, but not secret values, is defined in `secrets.toml` and incorporated into the install instructions. The customer supplies values while setting up the install with their own cloud credentials. Values are stored in AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager, depending on the install's cloud provider. Because the customer deploys the Nuon-generated Stack using their cloud credentials, the customer, not the vendor, supplies the secret values. Neither Nuon nor the vendor sees these values because they are not stored in the Nuon Control Plane. Secrets can be used to configure components and actions using [variables](/guides/using-variables). ## How do you configure a secret? Within your app directory, create a file named `secrets.toml`. This file will contain the configuration for your secrets. Alternatively, you can create a directory named `secrets` and place individual Secret files inside it, such as `github_app_key.toml`, `vendor_license_key`. ```toml secrets.toml theme={null} # secrets [[secret]] name = "github_app_key" display_name = "GitHub App Key" description = "Base64 encoded Github App Key" required = true format = "base64" [[kubernetes_sync_targets]] namespaces = ["control-plane"] name = "github" key = "app-key" [[secret]] name = "vendor_license_key" display_name = "Vendor license key" description = "Vendor license key" required = true [[kubernetes_sync_targets]] namespaces = ["app"] name = "vendor-license" key = "key" [[secret]] name = "rds_secret" display_name = "database password" description = "database password" required = true auto_generate = true [[kubernetes_sync_targets]] namespaces = ["workers", "control-plane"] name = "storage" key = "db-password" ``` Use the key-value pair `kubernetes_sync = true` to indicate that the secret should be synced to Kubernetes as a Secret object. The `kubernetes_secret_namespace` and `kubernetes_secret_name` fields specify where the secret will be created in Kubernetes. This workflow step is run after the provisioning of a Kubernetes sandbox and uses the key of `value` when creating the Kubernetes secret. ## Configuring components with secrets Reference secret identifiers from Stack outputs, and then use them in your component configuration. The following examples use AWS Secrets Manager and CloudFormation; Azure and GCP Stacks provide equivalent provider-specific outputs. ### Terraform components with secrets ```toml components/open_webui.toml theme={null} # terraform name = "open_webui" type = "terraform_module" terraform_version = "1.13.5" ... [vars] ... openai_secret_arn = "{{ .nuon.install_stack.outputs.openai_api_key_arn }}" ``` ### Helm components with secrets ```toml components/my-component theme={null} # helm name = "helm" type = "helm_chart" chart_name = "chart-name" [connected_repo] directory = "helm" repo = "org/repo" branch = "main" [[values_file]] contents = "./values/values.yaml" ``` ```yaml values.yaml theme={null} service: port: 80 targetPort: 3000 secrets: botTokenSecret: bot-user-oauth-token ``` Then reference the secret with the `value` key in the Helm chart template. ```yaml deployment.tpl theme={null} apiVersion: apps/v1 kind: Deployment --- containers: - name: slack-app image: '{{ .Values.image.repository }}:{{ .Values.image.tag }}' ports: - containerPort: { { .Values.deployment.containerPort } } env: - name: SLACK_BOT_TOKEN valueFrom: secretKeyRef: name: { { .Values.secrets.botTokenSecret } } key: value ``` ## Configuring actions with secrets Reference the secrets from AWS Secrets Manager as outputs from the CloudFormation stack, and then use them in your actions config. In this example, the secret is assigned to an action environment variable and then referenced in a script. Note the script is stored in the `src` directory of the app. ```toml actions/rds_secrets.toml theme={null} [[triggers]] type = "post-deploy-component" component_name = "rds_cluster" [[steps]] name = "Copy RDS Secret for deployment" command = "./rds_secrets/import.sh" [steps.env_vars] SECRET_ARN = "{{ .nuon.install_stack.outputs.rds_secret_arn }}" ``` ```sh src/rds_secrets/import.sh theme={null} #!/usr/bin/env bash secret_arn="$SECRET_ARN" echo "[rds-secrets import] reading db access secrets from AWS" secret=`aws --region $region secretsmanager get-secret-value --secret-id=$secret_arn` ``` ## Changing Secrets Outside of the App If you change an app secret directly in your cloud secret manager or by updating the Stack, Nuon will not automatically detect the change. In order for Nuon to be aware of the change, you will have to either reprovision the install or review the dependency graph in the dashboard and manually redeploy the components or actions that depend on the secret. `telemetry-export-config` is runner configuration, not an app-defined secret. The runner automatically detects changes to that secret. See [Export Runner Audit Logs](/guides/export-runner-audit-logs). If your secrets are configured to sync with Kubernetes, go to the install dashboard and manually select sync secrets in the Manage drop-down. If you have a use case for working with secret values not covered here, Please [contact us](https://nuon.co/contact-us). # App Variables Source: https://docs.nuon.co/concepts/app-variables App Variables allow you to store values through the API to be used across installs of an app. App variables are values that you can store in the API to be used across all installs of an app. App variables are meant for sensitive values that you want to manage through the API, but do not want included in the app as an input or secret. For example, an API key where you don't want it visible in the app's inputs TOML file as part of a git-backed repo. Nuon's API stores app variables in the Nuon data plane which is a PostgreSQL database with no encryption. ## Defining App Variables You can create app variables using the CLI. ```sh theme={null} nuon apps variables create --name=external_api_key --value=b25yYW5nZXRpZ2h0bHlhdHRlbXB0cGxlbnR5cmVhZGRvdWJsZXdlc3RleWVodW50d2g= ``` Once created you should be able to view the default using the CLI. ```sh theme={null} nuon apps variables list ``` If you no longer need the default, you can delete it using the CLI ``` nuon variables delete --id=app_variable_id ``` ## Using App Variables App variables are included in the install state, and are available in job plans. Using our example secret named `external_api_key`, you could reference its value in your app with the template expression `{{ .nuon.app.variables.external_api_key }}`. If you add or update an app variable after an install has been created, you will need to update that install's state before the value will be included in plans. Running a deploy or sandbox provision will trigger a state update. # Nuon Apps Source: https://docs.nuon.co/concepts/apps Package your existing application code and infrastructure to create Bring Your Own Cloud installable versions of your product. Apps are versions of your application code and infrastructure that can be deployed into a customer cloud account. Nuon is designed so you can lift and shift your *existing* application and infrastructure into new deployment options. Most Nuon users will create multiple apps to offer different "flavors" of BYOC to their customers. e.g., AWS, Azure versions, dev, prod environment versions, create a VPC, re-use a VPC versions, etc. ## What Is An App? When you first set up Nuon, you will map your existing product into one or more apps. An App is a fully configured version of your product that can be installed into your customer's cloud account. ## How do you configure an App? Each app is represented as multiple configuration TOML files. New to app config? [App Initialization](/guides/app-init) scaffolds a new app you can edit and sync. Each app consists of the following parts: * `Sandbox` - The base infrastructure layer e.g., AWS EKS, Azure AKS * `Runner` - The Nuon service that manages the install * `Inputs` - Customer-provided input values specific to each install e.g., app release, domain name * `Components` - Your application e.g., Helm, manifests, load balancers, certificates, Terraform Components can pull from your own Git repositories. To use **private** repositories, connect your GitHub account or organization to your Nuon org first — public repositories need no connection: ```sh theme={null} nuon orgs connect-github ``` Confirm the connection: ```sh theme={null} nuon orgs list-vcs-connections ``` You can also connect from the dashboard: click your organization name in the top left, then **Add** next to GitHub Connections. See [Connect GitHub](/guides/vcs) for the full walkthrough. ## Sandbox The sandbox sets up your application's base **compute** environment, such as a Kubernetes (EKS, AKS, GKE) or ECS cluster. It gives your app a consistent, isolated place to run across every install. In a shared-responsibility model, the sandbox can also build on infrastructure a customer already provides, such as an existing cluster. Read more about the [Sandbox](/concepts/sandboxes) concept, or follow the [Configuring Sandboxes](/guides/configuring-sandboxes) guide to set one up. ## Networking Networking is provisioned by the install [Stack](/concepts/stacks): the Terraform or CloudFormation/Bicep that the customer deploys first. The Stack creates: * The network the app runs in (a VPC on AWS, a VNet on Azure) * The [Runner](/concepts/runners) that deploys and operates your app * The [permissions](/concepts/operation-roles) the Runner needs The sandbox then runs inside this network. The Stack can create a fresh network or reuse resources the customer already has. See [Stacks](/concepts/stacks) for the available patterns. ## Inputs Inputs allow you to define different inputs that customers can provide to an app. This enables you to create "configuration" knobs that your customers can manage to control your application. Inputs are commonly used to: * Allow users to select regions and provide access to an account * Customize app & infrastructure versions, such as pinning a Kubernetes cluster version * Re-use existing infrastructure, such as providing a `vpc_id` that has been granted access Read more about [Inputs](/concepts/app-inputs), and see [Configuring Inputs & Secrets](/guides/configuring-inputs-and-secrets) for a step-by-step walkthrough. ## Components Components are used to connect your existing application and infrastructure code to your app. Components allow you to: * Import existing images, or build Dockerfiles and sync these artifacts into customer accounts * Deploy Helm charts * Deploy Kubernetes manifests * Deploy Terraform code Common components include certificates, load balancers, Kubernetes operators or Helm to deploy an app or database, etc. Apps allow you to create [dependencies between components](/guides/component-dependencies) and expose a rich configuration language to share outputs between components for configuration. You can use [variables](/guides/using-variables) to configure components with the outputs of other components. Read more about the [Components](/concepts/components) concept, or get hands-on with [Managing Components](/guides/managing-components). Jump straight to your component type: ## READMEs When running an app in an [Install](/concepts/installs), you often need to document important info. e.g., the app's access URL, the install state, links to documentation. It can also be nice to have some level of programmability, such being able to render a button that will trigger an [action](/concepts/actions). Additionally, this documentation must be rendered per-Install because certain values -- such as URLs -- will be different for each Install. App READMEs were introduced to support this. Each app has a `README.md` text field where you can define a Markdown document which will be rendered in the dashboard for each Install of that App. You can use all valid Markdown, including HTML. You can also use variables, so that the document will be custom-rendered for each Install. See the [Using READMEs](/guides/using-readmes) guide for more details. ## App Management The [CLI](/cli) exposes many operational commands to manage apps. The dashboard can also provide limited app management functionality. See the [Managing Apps](/guides/managing-apps) guide for common workflows. To see all available commands, run: `nuon apps --help`. Commands for creating, deleting, and viewing apps. **Create a local app directory** - Create a directory and name it after your app. This directory will contain your App's configuration files. ```sh theme={null} nuon mkdir your-app cd your-app ``` **Create an app** - `.toml` config files are used to represent a configuration for a single app. For consistency, the app name must be the same name as the directory you created above. ```sh theme={null} nuon apps create --name=your-app ``` **List all apps** - List all apps using the CLI or dashboard. ```sh theme={null} nuon apps list ``` Apps List **Pinning an app** - pin your app You can pin an app, which will set its ID as a default argument to all app commands. This is helpful to avoid having to pass `-a` to each command. ```sh theme={null} nuon apps select ``` Each example below assumes you have run `nuon apps select` first. To show the currently pinned app: ```sh theme={null} nuon apps current ``` **Get an app** - Get basic info about app ```sh theme={null} nuon apps get ``` **Rename** - Rename an app ```sh theme={null} nuon apps rename --name=new-name ``` ### Configuration You can view various configs for an app and sync configuration files using the [CLI](/cli). **Sync** - Sync config files to the Nuon control and data plane Sync is a critical step as you iterate and update you app configuration. Syncing performs syntax validation, uploads your configuration to the Nuon control plane, and triggers builds for any components. e.g., Terraform module, Helm chart, Kubernetes manifest, container image. If any component TOML files have changed, or any related files such as Helm `values.yaml` files, a new build will be triggered during sync. If component source files have changed, you must push those changes to your git repo, then manually build the component using the dashboard or CLI. Sync all config files in the current directory: ```sh theme={null} nuon apps sync ``` Apps sync **Configs** - List all config versions for an app ```sh theme={null} nuon apps configs ``` **Runner-config** - Print the current runner config ```sh theme={null} nuon apps runner-config ``` The output of the following 2 sub-commands are JSON so to improve readability, use `jq` or `python` ```sh theme={null} nuon apps | jq nuon apps | python -m json.tool ``` **Sandbox-config** - Print the current sandbox config ```sh theme={null} nuon apps sandbox-config ``` **Input-config** - Print the current input config ```sh theme={null} nuon apps input-config ``` ## App Metadata You can add the following metadata to your app, which is used in installers: * `display_name` * `description` This metadata can be added via a `metadata.toml` config file in the app root directory: ```toml theme={null} display_name = "Your app" description = "your app" readme = "./README.md" ``` The `readme` field points to a `README.md` file that will be rendered in the Dashboard for each app and its installs. App README # CLI Source: https://docs.nuon.co/concepts/cli Manage the full install lifecycle from your terminal. ## Using the CLI to create and manage installs The CLI allows you to embed install management scripts, or install provisioning into your workflow. [CLI Reference documentation can be found here.](/cli) Every install command takes `--install-id`/`-i` to target a specific install, or you can pin a default with `nuon installs select` and omit the flag on subsequent commands. ### Create an install Before creating an install, run `nuon auth login` to authenticate and `nuon apps select` to make sure you are on the right app. ```bash theme={null} nuon installs create ``` To see every flag: ```bash theme={null} nuon installs create --help ``` The most common flags are: * `--name` — the name for this install * `--region` — the cloud region to provision into * `--inputs` — `key=value` pairs for the app's [inputs](/concepts/app-inputs) A simple invocation: ```bash theme={null} nuon installs create --name acme-corp --region us-east-1 ``` Passing inputs inline: ```bash theme={null} nuon installs create \ --name acme-corp \ --region us-east-1 \ --inputs healthcheck_interval_seconds=10 ``` After the command runs, a list of your available apps appears. Select one and you'll be redirected to the install page in the browser. ### Find and select an install List, fetch, and pin installs: ```bash theme={null} nuon installs list nuon installs get -i nuon installs select # pins an install as the default for -i nuon installs deselect ``` Pinning is helpful when running several commands against the same install — each subsequent command skips `-i`. ### Inspect and update inputs ```bash theme={null} nuon installs current-inputs -i nuon installs update-input -i --inputs key=value ``` Updating inputs on a provisioned install triggers the workflows needed to propagate the change to any components that depend on them. ### Config-file-driven installs You can describe installs declaratively in TOML files and sync them to Nuon. This is the pattern used in CI and for managing many installs at once. ```bash theme={null} nuon installs sync -a -d nuon installs generate-config -i nuon installs toggle-sync -i --enable ``` For the install config file schema, see the [install config reference](/configuration-files#install-config). ### Deploy and redeploy components ```bash theme={null} nuon installs deploy-components -i nuon installs teardown-component -i --component-id nuon installs teardown-components -i ``` ### Manage workflows and approvals Install lifecycle events run as [workflows](/concepts/workflows). The CLI has an optional TUI that you can start with: ```bash theme={null} nuon installs workflows ``` Or you can jump right into a specific install with: ```bash theme={null} nuon installs workflows watch -i ``` Workflow TUI Or run the steps individually: ```bash theme={null} nuon installs workflows list -i nuon installs workflows steps approve -i --workflow-id --step-id nuon installs workflows steps reject -i --workflow-id --step-id nuon installs workflows steps retry -i --workflow-id --step-id ``` ### Reprovision, deprovision, forget ```bash theme={null} nuon installs reprovision -i nuon installs deprovision -i nuon installs forget -i ``` `forget` removes an install from the control plane without running deprovision workflows. Reserve it for installs that are stuck in an unrecoverable state. ## Extend the CLI The CLI is extensible — you can publish your own `nuon ` commands that ship alongside install management. For how to build, distribute, and auth custom commands, see the [CLI Extensions guide](/guides/cli-extensions). ## Command reference For the complete command list and every flag, run: ```bash theme={null} nuon installs --help ``` # Components Source: https://docs.nuon.co/concepts/components Connect and configure your app using your existing container images, Helm charts, Kubernetes Manifests and Terraform code. Nuon supports several different component types that allow you to connect your existing container images, Helm charts, Kubernetes manifests and Terraform code to your app. ## What is a Component? A component represents a part of your App. Nuon supports the following component types: * [Helm Charts](/guides/helm-chart-components) - Any Helm chart located in a repository * [Terraform](/guides/terraform-components) - Any Terraform module * [Kubernetes Manifest](/guides/kubernetes-manifest-components) - Any raw kubernetes manifest * [Container Images](/guides/container-image-components) - Any prebuilt OCI image ## How do you configure a component? A component is configured in an individual `.toml` file inside a directory called `components` in the root of your app directory. e.g., a Helm chart component named `grafana` would be defined in a file named `grafana.toml` inside a directory named `components`. Additional component configuration files can be added to the `components` directory or another directory in the app directory, and referenced in the Component's config file. e.g., you can have a `values.yaml` file for a Helm chart component to deploy observability platform from Grafana Labs located in `components/values/grafana` You can configure components to use either a public repo (using a `public_repo` block) or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). ```toml components/grafana.toml theme={null} # helm name = "grafana" type = "helm_chart" chart_name = "grafana" namespace = "grafana" storage_driver = "configmap" dependencies = ["prometheus"] [public_repo] repo = "grafana/helm-charts" directory = "charts/grafana" branch = "main" [[values_file]] contents = "./values/grafana/values.yaml" ``` By default, every component must have a `git` repository to store its source files, but you can include them in the app directory and reference the same app repo in the component TOML file. This example shows a component that uses the same repo as the app to store its AWS Terraform module to create the certificate in AWS: ```toml components/certificate.toml theme={null} [public_repo] repo = "nuonco/example-app-configs" directory = "grafana/src/components/certificate" branch = "main" ``` If you are iterating and making changes to the source files of a component, running `nuon apps sync` will not trigger a new build. You need to first push your changes to your repo then create a new build manually using dashboard or CLI after syncing the app. During development, it is recommended to create a branch and use that branch in the component config file. Component Build ## Component Management The [CLI](/cli) exposes many operational commands to manage components. To see all available commands, run: `nuon components --help`. Make sure to select an App using `nuon apps select` before using the `nuon components` commands, or pass the `-a` flag to each `nuon component` command. ### Component Management There no longer is a `nuon components select` command to pin a component. Instead, pass the `-c` flag to each command to specify the component by its id or name. Commands for working with components: **List Components** - List components for an app. ```sh theme={null} nuon components list ``` **Get a component** ```sh theme={null} nuon components get -c ``` **Delete a component** - delete a component. It is not recommended to delete components using the CLI. Instead, manage them by removing a component TOML file from the app directory and then running `nuon apps sync`. ```sh theme={null} nuon components delete -c component_id ``` **Print a component config** - Print the latest configuration for a component. ```sh theme={null} nuon components latest-config -c ``` ### Component Builds All components must be built before creating an install or updating an install. `nuon apps sync` for the first time or if a component TOML or related Helm `values.yaml` has changed will also trigger a build. The release commands support an `-auto-build` flag which will trigger a build. Any of the following CLI commands can use component\_id or component\_name with the `-c` flag to specify which component to build. **Create or re-create a Build** - Create a build using the latest component configuration in the dashboard or with the CLI. Component Build ```sh theme={null} nuon builds create -c component_name ``` **List Builds** - List builds for a component with the CLI or dashboard. ```sh theme={null} nuon builds list -c component_name ``` If you do not pass the `-c` flag, all Builds for all components in the currently selected app will be listed. Component Builds **Get a Build** - Print out build details. ```sh theme={null} nuon builds get -b -c component_name ``` **Logs** - Show logs for a build by opening the dashboard. ```sh theme={null} nuon builds logs -b -c component_name ``` Component Build ### Build and Deploy Timeouts Each component has configurable timeouts for build and deploy operations. If an operation exceeds its timeout, it will be marked as failed. You can override these in your component TOML file: ```toml theme={null} build_timeout = "30m" deploy_timeout = "1h" ``` The maximum allowed value for both is `1h`. The defaults vary by component type: | Component Type | Build Default | Deploy Default | | ------------------- | ------------- | -------------- | | Helm Chart | 5m | 30m | | Terraform Module | 5m | 60m | | Container Image | 15m | 5m | | Kubernetes Manifest | 5m | 15m | See the [configuration reference](/config-ref/index) for full details on each component type. # Auto Retries Source: https://docs.nuon.co/concepts/components/auto-retries Configure components to automatically re-plan and re-apply on transient failures by setting max_auto_retries. When a [component](/concepts/components) fails to deploy due to a transient error, you don't want it to get stuck and fail. You also don't want an actually-broken component to retry forever. `max_auto_retries` gives each component a bounded retry budget. The runner will re-plan and re-apply the component up to that many times before surfacing the failure to the [workflow](/concepts/workflows). If the component succeeds on a retry, the workflow continues normally. ## How it works Set `max_auto_retries` on any component TOML: * On a deploy failure, the runner re-plans and re-applies the component up to `N` more times. After `N` failed attempts, the workflow surfaces the failure as it would today. Each retry is a fresh plan + apply, so it picks up any state that has settled since the previous attempt (e.g. a slow IAM role becoming consistent, an upstream registry recovering). ## Configuring `max_auto_retries` `max_auto_retries` is supported on every component type: `helm_chart`, `terraform_module`, `kubernetes_manifest`, `container_image`, and `pulumi`. ### Helm chart ```toml components/penpot.toml theme={null} # helm name = "penpot" type = "helm_chart" chart_name = "penpot" namespace = "penpot" storage_driver = "configmap" max_auto_retries = 5 [public_repo] repo = "penpot/penpot-helm" directory = "charts/penpot" branch = "main" [[values_file]] contents = "./values/penpot.yaml" ``` ### Terraform module ```toml components/certificate.toml theme={null} # terraform name = "certificate" type = "terraform_module" terraform_version = "1.11.3" max_auto_retries = 5 [public_repo] repo = "nuonco/example-app-configs" directory = "penpot/src/components/certificate" branch = "main" [vars] install_id = "{{ .nuon.install.id }}" region = "{{ .nuon.install_stack.outputs.region }}" zone_id = "{{ .nuon.install.sandbox.outputs.nuon_dns.public_domain.zone_id }}" domain_name = "{{ .nuon.install.sandbox.outputs.nuon_dns.public_domain.name }}" ``` Both examples are taken from the [penpot app config](https://github.com/nuonco/example-app-configs/tree/main/penpot/components). ## Reference Looking for something else? Full per-component-type schemas are found here: * [Helm chart](/config-ref/helm) * [Terraform module](/config-ref/terraform) * [Kubernetes manifest](/config-ref/kubernetes-manifest) * [Container image](/config-ref/container-image) # Dashboard Source: https://docs.nuon.co/concepts/dashboard What you can do for installs in the Nuon dashboard. ## What the web app lets you do Once you've connected your app, you can manage installs from the [Dashboard](https://app.nuon.co). Create install modal * **Create an install:** Fill in the install's inputs and generate the stack link to share with the customer. * **Share the stack link:** The link deploys a [Stack](/concepts/stacks) (CloudFormation on AWS, Azure Resource Manager (Bicep) on Azure, Terraform on GCP) into the customer's cloud account, which creates the runner. * **Monitor workflows:** Monitor install progress from sandbox creation to component deploys, everything that makes up your app. * **Review and approve changes:** See diffs and plans for each step, then approve, reject, or retry. * **Redeploy components:** Rebuild any component that has failed and needs updating for the install to succeed. * **Update inputs after provision:** Change inputs on a live install; the dashboard triggers the workflows needed to propagate the change. * **Run actions:** Trigger on-demand scripts defined in the app — health checks, remediations, break-glass operations. * **Reprovision or deprovision:** As a heavier troubleshooting step, your customer can delete the resources created in their account, and you can reprovision in the dashboard. # Glossary Source: https://docs.nuon.co/concepts/glossary Basic terminology of the Nuon Platform. This page defines terms you will see throughout our product and documentation. ## User Terms | Term | Definition | | ---------------- | ----------------------------------------------------------------------------- | | Vendor | A BYOC software provider. | | Customer | A BYOC software consumer. | | Customer Account | The cloud account where a Vendor deploys a BYOC App. Owned by their Customer. | ## System Components | Term | Definition | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Nuon Platform | The software that we provide to Vendors and Customers to support providing and consuming BYOC applications. | | Nuon Control Plane | The part of the Nuon platform that orchestrates jobs for runners. | | Nuon Runner | The part of the Nuon platform that executes jobs. The runners being orchestrated by the Nuon Control Plane can collectively be considered the Data Plane. | | Organization | A business unit that operates Apps and Installs. A Vendor will be given one or more Orgs to use. | | App | A BYOC application, composed of a sandbox, inputs, components, and actions. Provided by a Vendor to a Customer. | | Build | An OCI artifact packaging a Component. Stored in a private OCI registry alongside the Nuon Control Plane. | | Inputs | A Vendor-defined input for an App, that can be set by a Customer when creating an Install. Input values can be used by the Sandbox, Components, and Action Workflows of the App. | | Sandbox | The “root” component of an App. Defines the base infrastructure required by the App Components. | | Component | Part of an App. Nuon supports Terraform modules, Helm charts, Docker files, and container images. Can be deployed and updated independently of the rest of the app. | | Action Workflow | A series of commands, defined by a Vendor, that can be run in an Install. | | Build Runner | A Nuon Runner that builds docker images and packages OCI artifacts for Install runners to pull. Deployed alongside the Nuon Control Plane, but technically part of the Data Plane. Single-tenant. | | Install Runner | The Nuon Runner deployed in the Customer Account for an Install. It executes tasks to sync artifacts, provision, maintain, and deprovision the sandbox and components, and run actions in the Install. Single-tenant. | | Install | A single-tennant deployment of a BYOC app. Typically deployed into a cloud account owned by a Customer. | ## Processes | Term | Definition | | ------------------- | -------------------------------------------------------------------------------------------------------- | | Provision | The process of creating an Install in a Customer’s cloud account. | | Reprovision | The process of re-creating an Install in a Customer’s account. Basically a factory reset of the Install. | | Deprovision | The process of removing an Install from a Customer’s account. | | Sync | The process of copying a Build from the Control Plane’s registry to an Install’s registry. | | Sandbox Run | The process of provisioning the Sandbox in an Install. | | Component Deploy | The process of provisioning a Component in an Install. | | Action Workflow Run | The process of executing an Action Workflow in an Install. | | Release | A process that orchestrates Deploys of a Component in one or more Installs. | ## Continuous Delivery | Term | Definition | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | App Branch | A link between one git branch and an App. A push to that branch triggers an App Branch Run. See [App Branches](/concepts/app-branches). | | App Branch Run | A single execution of an App Branch's workflow: fetch the config at a commit, build what changed, then plan and deploy each Deployment Group in order. Triggered by a push (`git-run`), a pull request (`git-preview-run`), or by hand from the CLI, API, or dashboard (`manual-run`). | | Deployment Group | A named subset of an App's Installs that receives an update as a unit, and the unit of ordering and approval within an App Branch Run. Selected by label, install ID, or install name. The dashboard calls these deployment groups; the TOML key is `install_groups` and the API routes use `install-groups`. | | Label Selector | A set of key/value pairs on a Deployment Group. Any Install carrying all of them belongs to the group, so membership re-evaluates on every run. Set an Install's labels with `nuon installs labels set`. | | Plan | The diff an App Branch Run computes for each Install in a Deployment Group, showing what would change before anything is applied. Its workflow step is an approval gate. | | App Config Version | A snapshot of the config an Install was expected to be running at a point in time. Nuon records one per change, keeps the diff between versions, and can apply any earlier version again. | | PR Preview | A plan-only App Branch Run triggered by opening or updating a pull request against a tracked branch. Nuon posts the resulting config diff as a pull request comment and sets a commit status named `nuon/// preview (plan-only)` that links to the preview run. Requires the Nuon GitHub App connected in your Org for the repository's owner. | # OIDC Federation Source: https://docs.nuon.co/concepts/oidc-federation Exchange OIDC tokens from any trusted workload for short-lived Nuon API tokens — no stored secrets. **OIDC federation** lets a workload exchange an OIDC token it already has for a short-lived [Nuon API token](/concepts/api-tokens) — with no long-lived secret stored anywhere. You configure a **trust policy** that tells Nuon which issuer to trust and which token claims to require; any workload presenting a matching token gets a short-lived token in return. This works with **any OIDC-compatible identity**, and from any client, since the exchange is a plain API call. Unlike an [API token](/concepts/api-tokens) or a token minted for a [service account](/concepts/service-accounts), there is no credential to store, distribute, or rotate. Managing OIDC federation requires **org admin** access. ## How it works 1. A workload obtains its ambient OIDC token from its platform (for example, the ID token GitHub Actions issues to a job). 2. It calls the Nuon token-exchange endpoint with that token and your org ID. 3. Nuon verifies the token's signature against the issuer's JWKS and checks its issuer, audience, and claims against your trust policies. 4. On a match, Nuon mints a short-lived API token bound to the policy's service account and returns it. The workload uses it like any other Nuon API token. The exchange endpoint requires no Nuon credentials — trust is established entirely by the OIDC token and your trust policy. ## Trust policy properties | Property | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Name** | A human-readable label to identify the policy. Required. | | **Issuer URL** | The exact `iss` of the OIDC provider to trust, e.g. `https://token.actions.githubusercontent.com`. Nuon fetches this issuer's JWKS to verify signatures. Required. | | **Audience** | The expected `aud` claim on the presented token. Required. | | **Claim conditions** | Claims the token must satisfy. A `sub` condition is required. | | **Role** | The permissions granted to exchanged tokens. Defaults to `org_read_only`. See [Access control](/concepts/access-control) for the available roles. | | **Token duration** | Lifetime of the exchanged token, in seconds. Defaults to `3600`; maximum `86400`. | | **Enabled** | Whether the policy accepts exchanges. | Each trust policy is backed by a dedicated [service account](/concepts/service-accounts) that carries its role. Creating and managing trust policies requires **org admin** access. ## Claim matching Every claim condition must match the presented token for a policy to apply — conditions are combined with AND. A `sub` condition is always required, so a policy can never trust *every* token from an issuer. Values support glob patterns. The `*` wildcard does not cross `:` segment boundaries, which makes it safe to match structured subjects like GitHub's `repo:acme/app:ref:refs/heads/main`: | Pattern | Matches | | ----------------------------------- | ------------------------------------ | | `repo:acme/app:ref:refs/heads/main` | Exactly that subject | | `repo:acme/app:*` | Any subject for the `acme/app` repo | | `repo:acme/*:ref:refs/heads/main` | The `main` ref of any repo in `acme` | ## Managing trust policies You can manage trust policies in the Dashboard under **Settings → OIDC federation**, or with the CLI. ### Creating a policy ```sh theme={null} nuon orgs oidc-trust-policies create \ --name github-actions \ --issuer https://token.actions.githubusercontent.com \ --audience https://api.nuon.co \ --role org_read_only \ --claim sub=repo:acme/app:ref:refs/heads/main ``` Pass `--claim` multiple times to require additional claims. Use `--ttl` to set a custom token duration in seconds. ### Listing and inspecting ```sh theme={null} nuon orgs oidc-trust-policies list nuon orgs oidc-trust-policies get --id ``` ### Updating and deleting ```sh theme={null} nuon orgs oidc-trust-policies update --id --role org_admin nuon orgs oidc-trust-policies delete --id ``` ## Exchanging a token ### Direct API exchange The exchange is a single unauthenticated API call — use it from any language or runtime. Send the workload's OIDC token and your org ID: ```sh theme={null} curl -X POST $NUON_API_URL/v1/oidc/token \ -H "Content-Type: application/json" \ -d '{"org_id": "", "token": ""}' ``` A successful response returns a short-lived token: ```json theme={null} { "authenticated": true, "token": "", "expires_at": "2026-01-01T00:00:00Z", "role": "org_read_only" } ``` Use the returned `token` as a Bearer credential on subsequent requests, exactly like an [API token](/concepts/api-tokens): ```sh theme={null} curl $NUON_API_URL/v1/current-user \ -H "Authorization: Bearer " ``` ### Using the CLI The [Nuon CLI](/cli) can perform the exchange for you. In GitHub Actions it detects the ambient OIDC token automatically — set your org ID and run any command: ```sh theme={null} export NUON_ORG_ID= nuon apps list ``` In other environments, provide the OIDC token yourself via `NUON_OIDC_TOKEN` (a raw JWT) or `NUON_OIDC_TOKEN_FILE` (a path to one), and the CLI will exchange it the same way. You can also exchange explicitly and capture the token, for example to export it for other tools: ```sh theme={null} export NUON_API_TOKEN=$(nuon auth exchange-token) ``` ## Example issuers Any OIDC provider that exposes a discovery document and JWKS can be used as a trust policy issuer, including: * **GitHub Actions** — `https://token.actions.githubusercontent.com` (see the [GitHub Actions guide](/guides/github-actions)) * **GitLab CI/CD** — your GitLab instance URL * **Buildkite** — `https://agent.buildkite.com` * **Cloud workloads** and **Kubernetes** service accounts with OIDC-issued tokens Set the policy's issuer to the provider's `iss` and its claim conditions to whatever uniquely identifies the workload you want to trust. ## Revocation Deleting a trust policy revokes its outstanding tokens immediately — the backing service account is removed, so any token minted through the policy stops working at once. Disabling a policy only stops new exchanges; tokens already issued remain valid until they expire. # Operation Roles Source: https://docs.nuon.co/concepts/operation-roles Assign different IAM roles to different operations for least-privilege access across sandbox, components, and actions. ## The Install Lifecycle An install in a customer's cloud account is not a single event. It passes through several distinct phases over its lifetime, each with different infrastructure activity and different permission requirements: * **Provision:** The sandbox is created. Foundational infrastructure is stood up via Terraform. This phase needs broad write access to create resources. * **Deploy:** Application components (Lambda functions, Helm charts, Kubernetes manifests, and Terraform modules) are deployed into the provisioned sandbox. Each component deploy writes only to its own slice of infrastructure. * **Maintenance and updates:** Components are redeployed with new builds, inputs are changed, drift is corrected, and reprovision runs update the sandbox itself. These writes are narrower than initial provisioning but still require resource-modification permissions. * **Debug sessions:** Actions are triggered manually or on a schedule to run scripts, collect diagnostics, or execute operational runbooks. Most debug actions need only read and exec access, but some (break-glass scenarios) temporarily require elevated permissions. * **Deprovision:** The install is torn down. Components are removed, the sandbox is destroyed, and all customer-account resources are cleaned up. This phase needs delete permissions but not create permissions. Using a single/limited set of roles across all these phases means that role must hold the union of every permission needed at any point in the lifecycle. Operation roles let you assign a different role to each phase, so each one gets only what it needs. ## What Are Operation Roles? Every operation that the Nuon runner performs in a customer's account requires a role. By default, Nuon uses three roles: * `provision_role` — used when creating the install sandbox * `deprovision_role` — used when deprovisioning the install sandbox * `maintenance_role` — used for component deploys, teardowns, and action runs Operation roles allow you to override these defaults at multiple levels of granularity: * **Per entity:** assign a specific role inline on a sandbox, component, or action config * **App-wide matrix:** define a central lookup table (`operation_roles.toml`) that maps principals and operations to roles * **At runtime:** override the role via the CLI or dashboard just before a run ## Why Least-Privilege Per Operation Matters A single maintenance role that covers both "deploy a Lambda function" and "delete a Lambda function" must hold `lambda:CreateFunction` and `lambda:DeleteFunction`. Ideally we'd want them to be separate so that the customer knows what goes in and out of the system and can control the same. With operation roles you can instead create: * A **deploy role** with only `lambda:CreateFunction` and `lambda:UpdateFunctionCode` * A **teardown role** with only `lambda:DeleteFunction` Neither role can do what the other can, limiting change radius during maintenance windows. The same principle applies across your entire app: * Sandbox provisioning may need broad Terraform permissions; deprovision needs only destroy permissions * Component deploys need write access; component teardowns need delete access * Routine action runs need minimal read and exec access; break-glass actions can use a separate elevated role ## Entity Types and Their Operations Each entity type in a Nuon app supports a specific set of operations: | Entity | Operations | | --------- | ----------------------------------------- | | Sandbox | `provision`, `reprovision`, `deprovision` | | Component | `deploy`, `teardown` | | Action | `trigger` | ## The Precedence Chain When an operation is triggered, the control plane builds the plan for the runner to work with. It walks the following chain from highest to lowest priority and uses the first match: 1. **Runtime override:** a role passed explicitly via the `--role` CLI flag or selected in the dashboard before triggering a run 2. **Break-glass role** *(actions only, deprecated):* the `break_glass_role` field on an action config 3. **Entity role:** the `operation_roles` block on a sandbox or component config, or the `role` field on an action config 4. **Matrix rule:** a matching rule in `operation_roles.toml` (the app-level principal + operation lookup table) 5. **Default role:** the standard role from `permissions.toml` for the operation type If no match is found at any level, the operation fails. **Custom roles:** Nuon previously supported three built-in role types (`provision`, `deprovision`, and `maintenance`) plus `break_glass` for emergency access. Custom roles extend this with `type = "custom"`, letting you define additional roles beyond those three and use them in operation role assignments. Any role referenced in an operation role assignment must be declared in the app config with `type = "custom"` so that it is provisioned in the customer's CloudFormation stack. ## Elevated Access and Break-Glass Operations Some operations are too sensitive for day-to-day role permissions but still need to run occasionally under controlled conditions. Operation roles support this through roles that exist in the stack but are not provisioned by default. They can be enabled and provisioned by the customer when there is a requirement for them. ### Roles Defined But Disabled by Default A custom role can be declared in the app config and provisioned into the customer's CloudFormation stack, but marked as disabled. In this state the role exists but cannot be assumed by the runner, so it has no effect during normal operations. When an elevated operation is required the role is enabled, the operation runs, and the role is disabled again by the customer. This means: * The role is already present in the install stack from day one, so no stack update or reprovisioning is needed at the moment of use * Under normal conditions the role is disabled, so it cannot be assumed accidentally or without explicit intent * Enabling and disabling the role is a deliberate, auditable action scoped to the window of use The customer retains full control. They can see the role in their install stack, enable or disable it at any time, and audit every assumption via audit logs. ### Introducing New Components with New Permission Sets As an app evolves, new components may require permissions that the existing maintenance role never had. For example, adding a component that manages RDS clusters needs `rds:CreateDBInstance` and related permissions, which would be inappropriate to add to a maintenance role shared with other components. With operation roles you can introduce the new component with its own `type = "custom"` role scoped exclusively to RDS operations. Existing installs receive the new role the next time their CloudFormation stack is updated (triggered by a reprovision), without any change to the permissions of existing roles. Each component's blast radius stays bounded to its own role. ### Example: EKS Cluster Upgrade EKS version upgrades are a concrete case where normal operational permissions are insufficient. A routine deploy role might have permission to update workloads running on the cluster, but upgrading the cluster control plane itself requires `eks:UpdateClusterVersion` and related permissions that should never be available during a standard deploy. The pattern with operation roles: 1. Define a `type = "custom"` role (e.g., `{{.nuon.install.id}}-eks-upgrade-role`) with the permissions needed for a cluster upgrade. The role is provisioned into the install stack but disabled by default. 2. When an upgrade is needed, the customer enables the role in their install stack. 3. An operator triggers the upgrade operation via the CLI or dashboard, selecting the upgrade role as a runtime override: `--role {{install-id}}-eks-upgrade-role`. 4. The runner assumes the elevated role, performs the upgrade, and exits. 5. The customer disables the role again, returning it to its default inactive state. The upgrade is fully auditable. Audit trail records show exactly which role was assumed, when, and by which operation, and the elevated permissions exist in the customer's account for only as long as necessary. You can also automate responses to role changes using [action triggers](/concepts/actions#role-change-triggers). The `role-enabled` and `role-disabled` triggers fire whenever a customer enables or disables a role in their install stack, allowing you to run validation, auditing, or setup tasks automatically. ## Role Name Resolution Role names at all levels support Go template syntax using install state variables: ```toml theme={null} role = "{{.nuon.install.id}}-deploy-role" ``` At runtime the template is rendered with the install's current state, producing a concrete role name such as `install-abc123-deploy-role`. That name is then looked up in the CloudFormation stack outputs (which contain all roles defined in `permissions.toml`). The matching ARN is assumed by the runner for the operation. ## Next Steps * [Operation Roles Guide](/guides/operation-roles): step-by-step configuration for entity roles, matrix rules, CLI overrides, and more * [Operation Roles Config Reference](/guides/operation-roles): full schema reference for `operation_roles.toml` * [Permissions Config Reference](/config-ref/permissions): defining `custom_roles` in `permissions.toml` # Concepts Source: https://docs.nuon.co/concepts/overview Get up to speed on BYOC and Nuon. Nuon is built around three pillars: connecting your app for BYOC delivery, customer installs, and enabling continuous delivery. This section covers the core concepts behind each. ## Connect Your App Everything you define as a vendor to package your application for BYOC delivery. Package your application code and infrastructure as versioned, installable BYOC products. Wire up Helm charts, Terraform modules, Kubernetes manifests, and container images. Define the base infrastructure layer provisioned in each customer's cloud account. Customer-supplied values — region, instance type, API keys — that configure each install. Manage sensitive values like credentials and API keys used in app configuration. Store and share dynamic values across all installs of an app via the API. Egress-only agents that deploy and manage installs inside customer cloud accounts. ## Customer installs Deploy into customer cloud accounts and adapt to their existing infrastructure. Instances of your app deployed and running in a customer's cloud account. Cloud-native templates that bootstrap an install in a customer's cloud account. Map IAM roles to specific operations for least-privilege access in customer accounts. ## Continuous Delivery Push updates with governance, audit trails, and operational control. Orchestrate the deployment, update, and teardown lifecycle of apps and components. Run operational scripts for health checks, migrations, and day-2 operations. Enforce compliance and security rules across builds and infrastructure deployments. Route external webhook events to app branch runs and install runbooks. Org-scoped tokens for authenticating to the Nuon Control Plane API. Named machine identities for automation, CI/CD, and runners. # Policies Source: https://docs.nuon.co/concepts/policies Enforce compliance, security, and operational standards across your infrastructure deployments. Policies allow you to enforce compliance, security, and operational standards across your infrastructure deployments. Policies are evaluated during builds and deploys, blocking or warning when violations are detected. ## What are Policies? Policies are rules written in [OPA Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) or [Kyverno](https://kyverno.io/) that validate your infrastructure before deployment. Each policy can either: * **Deny** - Block the build or deployment when a violation is detected * **Warn** - Log a warning but allow the build or deployment to continue Policies are evaluated at different phases depending on the component type: * **Build-time**: Policies run during the component build process * **Deploy-time**: Policies run after the plan is generated, before applying changes * **Sandbox runs**: Policies run during sandbox infrastructure provisioning ## Policy Types Nuon supports policies for different component types, each with its own input format: | Type | Applies To | Engine | Evaluation Phase | Input Format | | --------------------- | ------------------------------ | ------- | ---------------- | ----------------------------------------------- | | `container_image` | External container images | OPA | Build | Image metadata (SBOM, signatures, attestations) | | `helm_chart` | Helm chart components | OPA | Deploy | Kubernetes AdmissionReview | | `kubernetes_manifest` | Kubernetes manifest components | OPA | Deploy | Kubernetes AdmissionReview | | `terraform_module` | Terraform module components | OPA | Deploy | Terraform JSON plan | | `kubernetes_cluster` | Kubernetes cluster resources | Kyverno | Deploy | Kubernetes resources | | `sandbox` | Sandbox infrastructure | OPA | Sandbox run | Terraform JSON plan | ## Policy Engines ### OPA (Open Policy Agent) OPA policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), a declarative query language. Policies must be in the `nuon` package and use `deny` or `warn` rules. The input structure varies by policy type: | Policy Type | Input Structure | | ----------------------------------- | ------------------------------------------------------------- | | `terraform_module`, `sandbox` | `input.plan.resource_changes`, `input.plan.terraform_version` | | `helm_chart`, `kubernetes_manifest` | `input.review.object`, `input.review.kind` | | `container_image` | `input.image`, `input.tag`, `input.metadata` | **Example: Terraform policy** ```rego theme={null} package nuon # Deny unencrypted S3 buckets deny contains msg if { some resource in input.plan.resource_changes resource.type == "aws_s3_bucket" resource.change.actions[_] in ["create", "update"] not resource.change.after.server_side_encryption_configuration msg := sprintf("S3 bucket '%s' must have encryption enabled", [resource.address]) } # Warn about missing tags warn contains msg if { some resource in input.plan.resource_changes resource.change.actions[_] in ["create", "update"] not resource.change.after.tags.Environment msg := sprintf("Resource '%s' is missing Environment tag", [resource.address]) } ``` **Example: Kubernetes/Helm policy** ```rego theme={null} package nuon # Deny containers running as root deny contains msg if { input.review.kind.kind == "Pod" some container in input.review.object.spec.containers container.securityContext.runAsUser == 0 msg := sprintf("Container '%s' must not run as root", [container.name]) } ``` **Example: Container image policy** ```rego theme={null} package nuon # Deny unsigned images deny contains msg if { not input.metadata.signed msg := sprintf("Image %s:%s must be signed", [input.image, input.tag]) } ``` ### Kyverno Kyverno policies use YAML syntax and are designed for Kubernetes resources. Kyverno is only supported for `kubernetes_cluster` policy types: ```yaml theme={null} apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-labels spec: validationFailureAction: Enforce rules: - name: check-team-label match: any: - resources: kinds: - Pod validate: message: "All pods must have a 'team' label" pattern: metadata: labels: team: "?*" ``` ## How do you configure Policies? Policies are part of your app configuration. To create or update policies, add them to your app config and sync: ```sh theme={null} nuon apps sync ``` Policies list Please go through the [Configuring Policies guide](/guides/configuring-policies) for details. Note that unlike components, policies do not need to be built, they are evaluated directly during build and deploy workflows. ## Policy Reports ### Build-Time Evaluation Build-time policies are evaluated during the component build process: * **`container_image`**: Policies evaluate image metadata (SBOM, signatures, attestations) fetched from the registry If a `deny` rule matches during build-time evaluation, the build fails with status `policy_failed`. ### Deploy-Time Evaluation Deploy-time policies are evaluated after the plan is generated, before applying changes: * **`terraform_module`**: Policies evaluate the Terraform JSON plan * **`helm_chart`**: Policies evaluate the Kubernetes AdmissionReview objects * **`kubernetes_manifest`**: Policies evaluate the Kubernetes AdmissionReview objects If a `deny` rule matches during deploy-time evaluation, the workflow step fails and changes are not applied. ### Sandbox Evaluation Sandbox policies (`type = "sandbox"`) are evaluated during sandbox infrastructure runs. They receive the Terraform JSON plan for the sandbox infrastructure. ## Viewing Policy Results ### Dashboard Policy violations are displayed in the workflow step details. The Policy Report card shows: * **Passed**: All policy checks passed successfully * **Denies**: Policy violations that blocked the workflow (red) * **Warnings**: Policy warnings that were logged but didn't block (orange) Each violation includes the policy name and the specific message from your `deny` or `warn` rule. Workflow policy violations You can also view a list of all policy reports for an install, or filter by their status, type etc. Policy reports list ### CLI Build failures due to policy violations show the `policy_failed` status: ```sh theme={null} nuon builds create -c my-component # Output: # ✗ component build failed policy check: Image nginx:latest must be signed ``` Workflow steps display policy violation counts in the output: ```sh theme={null} nuon installs workflows steps list -w # Output shows policy column with ✗ (denies) and ⚠ (warnings): # Step Status Policy # deploy-api error ✗ 2 # deploy-db success ✓ # deploy-cache success ⚠ 1 ``` Get detailed violation messages for a specific step: ```sh theme={null} nuon installs workflows steps get -w -s ``` ## Policy Examples We maintain a [collection of example policies](https://github.com/nuonco/policies) organized by component type to help you get started. The example repository includes: * **`terraform/`** - Policies for Terraform modules (encryption, tagging, IAM security, cost controls) * **`kubernetes/`** - Policies for Helm charts and Kubernetes manifests (pod security, resource limits, networking) ## Related Resources * [External Image Policies Guide](/guides/external-image-policies) - Detailed reference for container image metadata and Rego patterns * [Policies Configuration Reference](/config-ref/policies) - Schema reference for policy configuration * [Example Policies Repository](https://github.com/nuonco/policies) - Ready-to-use policy examples # Runners Source: https://docs.nuon.co/concepts/runners Egress-only agents deployed in customer cloud accounts that execute all provisioning, deployment, and day-2 operations. Runners are deployed into each install, and are responsible for updating, monitoring and managing your running app. ## How do you configure a Runner? The Runner is hosted in a VM created in the customer's cloud account by the [Stack](/concepts/stacks). For each install, Nuon generates the Stack in both Terraform and the platform's native IaC language (e.g., AWS CloudFormation, Azure Resource Manager / Bicep), so the customer can apply whichever fits their tooling. When you create an install in the dashboard, Nuon outputs install instructions you share with your customer to create the Runner using your cloud credentials. The templates are generated by Nuon based on the app's configuration, and include the runner configuration. Install stack ## Runner Responsibilities The runner performs the following jobs, inside of an install: * provisioning or deprovisioning components * syncing container images into the local artifact store * monitoring running components by way of actions e.g., health checks * running actions, which can be used to perform Day 2 operational tasks * executing Terraform * forwarding configured telemetry to customer-controlled observability backends ## Customer-Owned Telemetry Export Customers can configure runners on AWS, Azure, and GCP to send runner audit events directly to their own OTLP-compatible backend. The destination and credentials are stored in the customer's cloud secret manager, and are not stored in the Nuon Control Plane. The install instructions identify the `telemetry-export-config` secret and configure the runner's access to it. The customer updates this secret after provisioning the Stack. See [Export Runner Audit Logs](/guides/export-runner-audit-logs) for the configuration reference and cloud-specific steps. ## Runner Permissions Each runner works by listening for jobs from the Nuon managed data plane server it belongs too. This server dispatches jobs, which the runner will pick up and then run tasks to perform. The runner will use different IAM roles for different component jobs, to minimize the permissions available on each job. These IAM roles are defined in each sandbox, and outputs are used to allow the runner to use them. Since the runner is deployed *into* the customer install, no long-lived permissions are required after the initial install. This creates a more secure operating environment, as the *only* thing that can dispatch work to the runner is the customer's data plane server it belongs to. Runner # Sandboxes Source: https://docs.nuon.co/concepts/sandboxes Sandboxes manage the base layer infrastructure for your application in each customer's cloud account. ## What is a Sandbox? Sandboxes create a consistent, isolated environment that your application can be guaranteed to work with, across your installs. They are responsible for defining the basic networking, permissioning, and infrastructure compute primitives e.g., a Kubernetes cluster - to power the app. In a shared responsiblity model, a customer may provide some infrastructure in the sandbox, such as an existing VPC or Kubernetes cluster. In this case, sandboxes enable you to leverage this customer infrastructure to meet security requirements, while also having a consistent way to deploy and manage your App in any account. Sandboxes can ensure isolation, reproducability and repeatability with each install - regardless of the account. ## How is a sandbox configured? A sandbox is configured in a `sandbox.toml` file inside the root of your app directory. To configure a sandbox, specify a repo and the correct parameters for your sandbox. Inputs and variables, are entered both inline in the `sandbox.toml` and also the `tfvars` file - to configure the sandbox, and these are passed to the Terraform code that is run by the sandbox. You can configure sandboxes to use either a public repo (using a `public_repo` block) such as Nuon's public repository of AWS and Azure sandboxes - or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" max_auto_retries = 3 [public_repo] directory = "." repo = "nuonco/aws-eks-sandbox" branch = "main" [vars] cluster_version = "1.33" cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" [[var_file]] contents = "./sandbox.tfvars" ``` ```toml sandbox.tfvars theme={null} additional_namespaces = ["grafana"] min_size = 2 max_size = 3 desired_capacity = 2 ``` `max_auto_retries` (optional, default `0`) auto-retries transient sandbox apply failures (provision, reprovision, and deprovision) up to the configured cap, so flaky infrastructure self-heals without a manual re-run. It matches the `max_auto_retries` field on [components](/concepts/components). ## How is a sandbox provisioned? By default a sandbox run clones your sandbox repo at the moment it runs, so what gets applied is whatever is at the configured ref that day. [Sandbox builds](/guides/sandbox-builds) change that: Nuon packages the sandbox into an immutable OCI artifact, pinned to an app config version, and installs provision from the artifact instead of cloning Git. Reprovisions apply what the install was built with, and nothing in your customer's account needs to reach your VCS. ## Nuon Managed Sandboxes We maintain managed sandboxes for [AWS](/platform-support/aws), [Azure](/platform-support/azure), and [GCP](/platform-support/gcp). Each platform has a turnkey sandbox (where all resources are provisioned). These sandboxes are designed to be a stable, configurable starting point and most relevant options around the network, cluster and compute are configurable. [Let us know](https://nuon.co/contact-us) if you have a use case for bringing your own VPC or Kubernetes cluster, and we can show how to adjust the sandbox configuration. We publish each sandbox as a GitHub repository and you can find detailed documentation for each sandbox there. These repositories are what you will reference in your `sandbox.toml`. [AWS EKS sandbox](https://github.com/nuonco/aws-eks-sandbox) [AWS EKS with Karpenter sandbox](https://github.com/nuonco/aws-eks-karpenter-sandbox) [AWS EKS Auto sandbox](https://github.com/nuonco/aws-eks-auto-sandbox) [AWS BYO EKS sandbox](https://github.com/nuonco/aws-byo-eks-sandbox) [AWS Min sandbox](https://github.com/nuonco/aws-min-sandbox) [Azure AKS sandbox](https://github.com/nuonco/azure-aks-sandbox) [Azure Min sandbox](https://github.com/nuonco/azure-min-sandbox) [GCP GKE sandbox](https://github.com/nuonco/gcp-gke-sandbox) [GCP Min sandbox](https://github.com/nuonco/gcp-min-sandbox) The AWS Min sandbox does not create a Kubernetes cluster, but still provisions a Route 53 Zone and IAM roles for the install. See the [Lambda function example](../get-started/app-aws-lambda) that leverages the AWS Min sandbox. ## Runner Types Update the `runner.toml` in your app to specify the type of Cloud runner to use for your app. The runner is responsible for executing the deployment of your app into the customer's account. ```toml runner.toml theme={null} # runner runner_type = "aws" helm_driver = "configmap" ``` ```toml runner.toml theme={null} # runner runner_type = "azure" helm_driver = "configmap" ``` ## Sandbox Outputs Sandbox outputs are made accessible to all components [using variables](/guides/using-variables). You can use any output from your selected sandbox by setting a config value on your component. In this example, we are using the `account.region` output from the sandbox to configure a Helm chart component: ```toml components/e2e_helm.toml theme={null} name = "e2e_helm" type = "helm_chart" chart_name = "e2e-helm" [public_repo] repo = "org/repo" directory = "chart" branch = "main" [values] account_region = "{{.nuon.install.sandbox.outputs.account.region}}" ``` See the [outputs.tf](https://github.com/nuonco/aws-eks-sandbox/blob/main/outputs.tf) for the AWS EKS sandbox for a full list of available outputs or the [outputs.tf](https://github.com/nuonco/aws-min-sandbox/blob/main/outputs.tf) for the AWS Min sandbox. Example outputs include: * `account.id` - The AWS Account ID * `account.region` - The AWS Region * `vpc.id` - The VPC ID * `cluster.cluster_name` - The EKS Cluster name * `cluster.cluster_endpoint` - The EKS Cluster endpoint * `ecr.repository_url` - The ECR repository URL * `ecr.registry_url` - The ECR registry URL * `nuon_dns.public_domain` - The public root domain for the install * `nuon_dns.internal_domain` - The internal root domain for the install * `namespaces` - A list of namespaces created in the cluster ## Configure sandboxes Each sandbox has a set of variable inputs. Most variables are either supplied by the Nuon platform during install, via inputs, or by setting default values. ### Use Default Values To configure a sandbox parameter with a default value for your app, add a `vars` block to your `sandbox.toml` file, or the mandatory `sandbox.tfvars` file in the same directory as your `sandbox.toml`. The values in this block will be used as defaults for all installs of this App.: ```toml sandbox.toml theme={null} [vars] cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.install.id }}.nuon.run" internal_root_domain = "internal.{{ .nuon.install.id }}.nuon.run" [[var_file]] contents = "./sandbox.tfvars" ``` ```toml sandbox.tfvars theme={null} additional_namespaces = ["grafana"] min_size = 2 max_size = 3 desired_capacity = 2 ``` ### Use Inputs Inputs allow you to collect customer input in the dashboard at install for values such as resource IDs, tokens and more. These inputs are accesible to all components and your sandbox, [using variables](/guides/using-variables#inputs). To configure a sandbox parameter using an input, you can define an input in your `inputs.toml` file and then reference it in your `sandbox.toml` file. In this example, we define a root domain input and use it to set the `public_root_domain` variable in the sandbox: ```toml inputs.toml theme={null} [[group]] name = "dns" description = "DNS Configrations" display_name = "Configurations for the root domain for Route53" [[input]] name = "root_domain" description = "The root domain. Services will be made available at subdomains of this root domain." default = "app.example.com" display_name = "Root Domain" group = "dns" ``` ```toml sandbox.toml theme={null} [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" ``` ## Create a Custom Sandbox While our [open source Sandboxes](#nuon-managed-sandboxes) are a great starting point, you can always fork them, or create your own sandbox to support customizing. To use a custom sandbox, simply update your app's `sandbox.toml` to point to your new sandbox code. Custom Sandboxes can also use private GitHub repos, that are connected to Nuon. ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] repo = "your-org/private-repo" directory = "sandbox" branch = "main" ```
If one of our managed sandboxes does not work for you, and you are considering creating a custom sandbox, we would love to hear about your use case. Please [contact us](https://nuon.co/contact-us). # Service Accounts Source: https://docs.nuon.co/concepts/service-accounts Named machine identities for automation, CI/CD. A **service account** is a named machine identity in your Nuon org. Use service accounts for automation, CI/CD pipelines, and any integration that calls the Nuon API on behalf of a system rather than a person. Unlike a one-off [API token](/concepts/api-tokens), a service account is a persistent identity you manage. You can rename it, change its role, and mint (or invalidate) multiple tokens for it over time. Managing service accounts requires **org admin** access. ## Roles Every service account is assigned exactly one role, which determines its permissions. Roles are org-scoped. See [Access control](/concepts/access-control) for the available roles and what each grants — **Admin** (`org_admin`) and **Read-only** (`org_read_only`). You can list the available roles at any time using the CLI: ```sh theme={null} nuon roles list ``` ## Managing service accounts Service accounts can be managed in the Dashboard on the **Service accounts** page, and with the CLI `service-accounts` commands. ### Create Create a new service account. Both a name and a role are required. ```sh theme={null} nuon service-accounts create --name ci --role org_read_only ``` ### Mint a token Create a token for a service account. The duration will default to one year. ```sh theme={null} nuon service-accounts tokens create --id ``` Optionally set a custom duration. ```sh theme={null} nuon service-accounts tokens create --id --duration 720h ``` The token value is shown **only once**. Copy it immediately into your secret manager or CI secret store — you cannot retrieve it again. ### Update a service account You can change a service account's name or role. ```sh theme={null} nuon service-accounts update-name --id --name new-name ``` ```sh theme={null} nuon service-accounts update-role --id --role org_admin ``` A service account can only have one role at a time. Updating the role will replace the service account's current role. ### List List active service accounts. ```sh theme={null} nuon service-accounts list ``` ### Delete Deleting a service account will remove its roles and invalidate all of its tokens. ```sh theme={null} nuon service-accounts delete --id ``` ## Runner service accounts Every runner is backed by its own machine identity — a service account with the special `runner` role. This is an implementation detail you can mostly ignore. You do not need to create or manage these service accounts yourself. Because they're managed for you, runner service accounts are hidden from `nuon service-accounts list` by default, but you can see them with the `--include-runners` flag. ```sh theme={null} nuon service-accounts list --include-runners ``` ## Stack service accounts When an install is provisioned with the stack Terraform module, Nuon creates a service account for it with the reserved `stack` role. This is the identity the `nuonco/stack` provider authenticates as when it reads the install's configuration and reports phone-home data. You don't create these yourself — one is created with the install. Unlike other roles, the stack role is scoped to a single install: a token minted for one install's stack service account cannot read any other install's configuration. Tokens for it are created from the **Await install stack** step's TF Module tab. See [Provision Stacks with a Terraform Module](/guides/provision-stacks-with-terraform-module) for the full flow. Like runner accounts, stack service accounts are hidden from listings by default. The dashboard's **Service accounts** page has a "Show stack accounts" checkbox to reveal them, and the CLI accepts an `--include-stacks` flag. ```sh theme={null} nuon service-accounts list --include-stacks ``` # Stacks Source: https://docs.nuon.co/concepts/stacks Customers use Stacks to install an app in their account. A Stack is the first thing deployed in a customer's cloud account when an install is created. It provisions the foundational resources needed to deploy and operate the app. For each install, Nuon generates a Stack template in both Terraform and the platform's native IaC language. Both formats produce the same resources, so you or your customer can use whichever one fits into your tooling and processes the best. * On **AWS**, the native format is a CloudFormation stack. * On **Azure**, the native format is an Azure Resource Manager (Bicep) deployment. * On **GCP**, only Terraform is generated — Google Cloud's Infrastructure Manager runs Terraform natively. ## What does a Stack create? Every Stack provisions the same three things: * The network the app runs in * The [permissions the Runner needs](/concepts/operation-roles#what-are-operation-roles) to deploy and operate your app * The Nuon-powered [Runner](/concepts/runners) itself The network can either be **created** by the Stack or **selected** from resources the customer already has. See [Stand-Alone VPC](/concepts/stacks/stand-alone-vpc), [Customer VPC](/concepts/stacks/customer-vpc), and [Customer Cluster](/concepts/stacks/customer-cluster) for each pattern. ## How is a Stack deployed? When a vendor creates an install, Nuon generates Stack templates (Terraform and the platform's native IaC, where applicable) along with links and CLI snippets the vendor can share with the customer. The customer deploys whichever format fits their tooling, using their own credentials. This is how access is granted: the customer provisions the Stack themselves. No cross-account access is required. The customer retains full ownership and visibility of all infrastructure created by the Stack. Install stack ## Customer Control The Stack gives the customer full control over the Runner's access to their cloud account. Through the Stack parameters, the customer can: * **Enable or disable the Runner** to stop it from executing jobs * **Configure IAM roles and policies** to control what the Runner can do * **Grant break glass roles** for temporary elevated access during emergencies, which the customer can revoke at any time * **Trigger automated actions** when roles are enabled or disabled — use the `role-enabled` and `role-disabled` [action triggers](/concepts/actions#role-change-triggers) to run auditing, validation, or setup tasks in response to role changes This means the customer always has a killswitch. See [Customer-Controlled Runner Shutdown](/guides/runner-kill-switch). ## Stack Inputs Stacks accept customer-provided values at deploy time. They come in three forms: * **[Secrets](/concepts/app-secrets)** — entered by the customer as CloudFormation/Bicep parameters or Terraform variables, then stored in AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager. * **[Customer-facing inputs](/concepts/app-inputs#customer-facing-inputs)** marked `user_configurable` — passed through the Stack at deploy time. * **VPC and cluster IDs** — required when the app uses the [Customer VPC](/concepts/stacks/customer-vpc) or [Customer Cluster](/concepts/stacks/customer-cluster) pattern. The customer enters them in the Stack's parameter form alongside any other inputs. # Customer Cluster Source: https://docs.nuon.co/concepts/stacks/customer-cluster The Stack deploys into a Kubernetes cluster the customer already runs. ## Deploying an Example App If you're still designing your BYOC architecture, it is helpful to deploy a sample app. For the Customer Cluster architecture, see [`byo-eks`](https://github.com/nuonco/example-app-configs/tree/main/byo-eks) in `nuonco/example-app-configs`. ## Overview [Custom nested stacks](/guides/custom-nested-stacks) wire the Runner into the cluster. Common when the app is an agent or operator that needs in-cluster access, or when the customer requires a single shared cluster for all vendor apps. ## App config Start from the [Customer VPC](/concepts/stacks/customer-vpc) pattern, then add [custom nested stacks](/guides/custom-nested-stacks) to wire the Runner into the existing cluster. Customer Cluster is natively supported via the Nuon-generated CloudFormation template. Terraform deployments are also possible but require forking the published modules at [`install-stacks`](https://github.com/nuonco/install-stacks) — see the Terraform section below. ### CloudFormation The vendor's `stack.toml` references CloudFormation nested templates plus the custom nested stacks that wire the Runner into the cluster: ```toml stack.toml theme={null} vpc_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/byo-vpc/default/stack.yaml" runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/runner/asg/stack.yaml" [[custom_nested_stacks]] name = "runner_sg_eks_access" # grant the Runner SG access to the cluster template_url = "CUSTOMER_CLOUD_DEPENDENT" index = 0 [[custom_nested_stacks]] name = "k8s_namespaces" # create namespaces for the app template_url = "CUSTOMER_CLOUD_DEPENDENT" index = 1 [[custom_nested_stacks]] name = "eks_access_entries" # add EKS access entries for Runner roles template_url = "CUSTOMER_CLOUD_DEPENDENT" index = 2 ``` The customer enters the VPC and subnet IDs (plus the cluster name) when deploying the stack. ### Terraform Custom nested stacks are CloudFormation-only. To support Terraform deployments for this pattern, fork [`install-stacks`](https://github.com/nuonco/install-stacks) and add equivalent resources (BYO-VPC inputs, cluster security-group access, namespaces, EKS access entries) directly in the Terraform module — see [Customizing Terraform Stack Templates](/guides/customizing-terraform-stack-templates). ### Sandbox The Sandbox skips cluster provisioning and consumes the customer's cluster name plus the subnet IDs from install stack outputs: ```toml sandbox.toml theme={null} [vars] cluster_name = "{{.nuon.inputs.inputs.cluster_name}}" public_subnet_ids = "{{.nuon.install_stack.outputs.public_subnets}}" private_subnet_ids = "{{.nuon.install_stack.outputs.private_subnets}}" runner_subnet_id = "{{.nuon.install_stack.outputs.runner_subnet}}" ``` # Customer VPC Source: https://docs.nuon.co/concepts/stacks/customer-vpc The Stack deploys into a VPC the customer already owns. ## Deploying an Example App If you're still designing your BYOC architecture, it is helpful to deploy a sample app. For the Customer VPC architecture, see [`byo-vpc`](https://github.com/nuonco/example-app-configs/tree/main/byo-vpc) in `nuonco/example-app-configs`. ## Overview The customer provides VPC and subnet IDs at deploy time, common when the customer prefers you use their VPC. ## App config Customer VPC is natively supported via the Nuon-generated CloudFormation template. Terraform deployments are also possible but require forking the published modules at [`install-stacks`](https://github.com/nuonco/install-stacks) to add BYO-VPC inputs — see the Terraform section below. ### CloudFormation The vendor's `stack.toml` references CloudFormation nested templates. Point `vpc_nested_template_url` at the `byo-vpc/default` template — it accepts existing VPC and subnet IDs as parameters instead of creating them: ```toml stack.toml theme={null} vpc_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/byo-vpc/default/stack.yaml" runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/runner/asg/stack.yaml" ``` The customer enters `VpcID`, `RunnerSubnetID`, `PublicSubnetIDs`, and `PrivateSubnetIDs` in the CloudFormation Quick Create form. ### Terraform The default Terraform modules at [`install-stacks`](https://github.com/nuonco/install-stacks) always create a fresh VPC — they don't accept existing VPC or subnet IDs as inputs. To support Terraform deployments of this pattern, fork the module and add BYO-VPC variables — see [Customizing Terraform Stack Templates](/guides/customizing-terraform-stack-templates). ### Sandbox Whichever format the customer deploys, the Stack reports the VPC and subnet IDs back to Nuon as install stack outputs. The vendor's `sandbox.toml` wires them into the [Sandbox](/concepts/sandboxes) so it attaches to the customer's subnets instead of creating its own: ```toml sandbox.toml theme={null} [vars] vpc_id = "{{.nuon.install_stack.outputs.vpc_id}}" public_subnet_ids = "{{.nuon.install_stack.outputs.public_subnets}}" private_subnet_ids = "{{.nuon.install_stack.outputs.private_subnets}}" runner_subnet_id = "{{.nuon.install_stack.outputs.runner_subnet}}" ``` # Stand-alone VPC Source: https://docs.nuon.co/concepts/stacks/stand-alone-vpc The Stack template creates a brand-new VPC dedicated to the install. ## Deploying an Example App If you're still designing your BYOC architecture, it is helpful to deploy a sample app. For the Stand-Alone VPC architecture, see [`eks-simple-auto`](https://github.com/nuonco/example-app-configs/tree/main/eks-simple-auto) in `nuonco/example-app-configs`. ## Overview When the customer deploys the Nuon-generated CloudFormation/Bicep or Terraform template that [you provide](/guides/install-configs), a fresh VPC is provisioned alongside the Runner and its permissions, all inside the customer's account. ## App config Each install supports two deployment formats: a Nuon-generated CloudFormation/Bicep template and the Terraform modules Nuon publishes at [`install-stacks`](https://github.com/nuonco/install-stacks) (AWS and GCP subfolders). ### CloudFormation/Bicep The vendor's `stack.toml` references nested templates. Set `type` to the cloud's native format — `aws-cloudformation` or `azure-bicep` — and point `vpc_nested_template_url` at the matching template. **AWS (CloudFormation)** — point at `vpc/eks/default` to create a new VPC: ```toml stack.toml theme={null} type = "aws-cloudformation" vpc_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/vpc/eks/default/stack.yaml" runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.4.0/runner/asg/stack.yaml" ``` The customer deploys via the CloudFormation Quick Create form (or `aws cloudformation deploy`) — no further input is needed beyond account and region. **Azure (Bicep)** — point at the `vnet.json` and `runner.json` templates in [`install-stacks/azure`](https://github.com/nuonco/install-stacks/tree/main/azure): ```toml stack.toml theme={null} type = "azure-bicep" vpc_nested_template_url = "https://raw.githubusercontent.com/nuonco/install-stacks/main/azure/vnet.json" runner_nested_template_url = "https://raw.githubusercontent.com/nuonco/install-stacks/main/azure/runner.json" ``` See [`aca-simple`](https://github.com/nuonco/example-app-configs/tree/main/aca-simple) for a complete Bicep example. ### Terraform Nuon publishes Terraform modules for both AWS and GCP at [`install-stacks`](https://github.com/nuonco/install-stacks) that provision the same resources. The customer clones (or wraps) their cloud's module and runs `terraform apply` against it. See [Customizing Terraform Stack Templates](/guides/customizing-terraform-stack-templates) if you need to fork. ### Sandbox The [Sandbox](/concepts/sandboxes) runs after the Stack and provisions the app's compute layer inside the new VPC. It picks up the VPC by reading the Stack's outputs: ```toml sandbox.toml theme={null} [vars] region = "{{.nuon.install_stack.outputs.region}}" vpc_id = "{{.nuon.install_stack.outputs.vpc_id}}" ``` # Triggers Source: https://docs.nuon.co/concepts/triggers Automate Nuon using external events with triggers. A **trigger** is an inbound webhook endpoint. External systems — a Git host, a cloud pub/sub topic, a CI provider — send events to a per-trigger **ingress URL**, and Nuon authenticates, deduplicates, and routes each event to a target: an **app branch run** or an **install runbook**. Use triggers to react to external events automatically — for example, kick off a build when a branch is pushed, or run a runbook when a cloud alert fires. ## How it works 1. You create a trigger, which gives you a unique, secret **ingress URL**. 2. An external system POSTs an event to that URL. Nuon verifies its authenticity, unwraps any envelope, and stores it as an **event**. 3. The event is evaluated against your **rules** (authored in your app config). Each matching rule produces a **dispatch**. 4. Each dispatch starts its target — an app branch run or an install runbook — passing along values mapped from the event payload. ## Concepts | Concept | Description | | ------------ | --------------------------------------------------------------------------------------------------------------------------------- | | **Trigger** | The inbound endpoint. Holds the ingress URL, authentication settings, and signing secrets. | | **Rule** | A routing rule that matches events and starts a target. Authored in your app config — see [Configure triggers](/guides/triggers). | | **Event** | A single received payload, stored with its headers, body, and routing outcome. | | **Dispatch** | One target execution produced by a matching rule, with its own status and retry state. | ## Authentication Triggers verify that incoming events are genuine. Choose an authentication type when you create a trigger: | Auth type | Description | | --------------- | ------------------------------------------------------------------------------------------ | | `none` | No verification. Use only for testing or when the ingress URL is the sole secret. | | `hmac` | Verify an HMAC signature computed over the request body with a shared secret. The default. | | `api_key` | Verify a static key sent in a header. | | `basic` | Verify HTTP Basic credentials. | | `bearer_jwt` | Verify a bearer JWT (optionally against an expected audience, subject, or email). | | `sns_signature` | Verify an AWS SNS message signature. | ### Presets Presets configure authentication and payload parsing for a known provider so you don't have to set the details by hand. Supported presets: `github`, `slack-events`, `datadog`, `aws-eventbridge`, `aws-sns`, `google-pubsub`, and `azure-event-grid`. ### Envelopes Some providers wrap the real payload in a transport envelope. Set an envelope so Nuon unwraps it before evaluating rules: `none` (default), `pubsub_push`, `cloudevents`, or `sns`. ## Secrets Each trigger has one or more signing secrets used to verify incoming events. Secrets support rotation: you can add a new secret, reveal it once, and revoke old ones without downtime. ## Status A trigger is either `active` (accepting events) or `suspended` (rejecting them). Disable a trigger to stop processing events without deleting it. ## Managing triggers Manage triggers in the Dashboard on the **Triggers** page, or with the `nuon triggers` CLI commands. ```sh theme={null} nuon triggers create my-trigger --preset github nuon triggers list nuon triggers reveal-ingress-url ``` To define how events are routed, author rules in your app config and inspect received events and dispatches — see [Configure triggers](/guides/triggers). # Workflows Source: https://docs.nuon.co/concepts/workflows Workflows are scheduled and manual lifecycles of deploying, updating or tearing down apps, components, and actions. ## What is a Workflow? A workflow represents the complete lifecycle of deploying, updating or tearing down an app and its components. Workflows also manage the execution of actions. If new to Nuon, familiarize yourself with the [app and install life cycle](../guides/app-install-life-cycle) first. Workflows List ## Workflow Types ### Install An install workflow covers all of the steps required to install an app from scratch. This includes creating the install stack, creating the virtual machine and starting the Nuon runner, provisioning the sandbox, syncing secrets, deploying the components of the app with Terraform, Helm or Kubernetes manifests e.g., databases, applications, certificates, application load balancers, and executing actions which are bash scripts to perform additional configurations like initializing a database or performing a health check. Install Workflow ### Component A component workflow deploys a component. If there are dependent components, the user can instruct Nuon to deploy those components as well. Component Workflow ### Action An action workflow executes the script associated with an action. e.g., initialize a database, run a health check on a component, make a one-off configuration change. Action Workflow ## Workflow States **Success (green checkmark)**: A step or install component completed successfully **Error (red X)**: One or more steps failed **Cancelled (yellow X)**: An install or step was cancelled by the user **In-progress (brown circle pulsing or clockwise spinning blue circle)**: A component or action is underway Workflow States ## Workflow Approvals Components using Terraform, Helm and Kubernetes manifests run a plan step which requires the user to approve or reject the step before proceeding to the apply or deploying of the sandbox and components. Workflow Approvals At the install-level and at the individual workflow-level, the user can opt to Approve All plan steps. Component Workflow ## Workflow Diffs If a component using Terraform, Helm or a Kubernetes manifest has been changed by the user, re-synced to an app and therefore re-built, the plan step will visually show the diff, aka the before and after proposed change, and require the user to approve or reject/deny the change. Learn more about [Understanding diffs](../updates/012-approval-diffs-plan-only). Workflow Diffs ## Drift Detection [Drift Detection](../updates/023-drift-workflow-improvements) allows you to automatically identify discrepancies between your defined infrastructure in Nuon and what’s actually running. You can schedule periodic drift checks or manual drift scans from the dashboard. Drilling into the relevant Workflow will show the drift. Drift Detection # Action Source: https://docs.nuon.co/config-ref/action JSON Schema reference for action configuration # Action ## Properties | Property | Description | Values | Example | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------- | | **`name`**
string | name of the action The action name is displayed in the Actions tab of the Nuon dashboard | **✅ Required** | `"http_healthcheck"`, `"database_migration"` | | **`timeout`**
string | timeout for action execution Maximum time the action can run. Maximum allowed is 30 minutes. Must be a valid Go duration string (e.g., 30s, 5m, 30m) | **✅ Required** | `"15s"`, `"5m"`, `"30m"` | | **`triggers`**
[array](#triggers) | triggers that execute this action Actions can be triggered manually, on a cron schedule, or by install lifecycle events (provision, deploy, teardown, etc). Define multiple triggers if needed | **✅ Required** | - | | **`steps`**
[array](#steps) | steps to execute in this action Ordered list of steps to execute. Each step requires a command and can optionally load scripts from repositories | **✅ Required** | - | | **`labels`**
object | key/value labels for the action Arbitrary key/value metadata used to group and categorize actions | **Optional** | - | | **`dependencies`**
array | component dependencies referenced in this action Automatically extracted from template references in steps (e.g., \{\{.component.component\_name}}) | **Optional** | `"database"`, `"api-server"` | | **`break_glass_role`**
string | IAM role for break-glass access to this action When set, allows the action to use a break glass role for elevated permissions during critical operations. Break glass roles are defined in CloudForma... | **Optional** | `"bucket-operations-break-glass"`, `"database-migration-break-glass"` | | **`role`**
string | IAM role name for action execution Name of the IAM role to use when executing this action. The role must be defined in the CloudFormation stack deployed to the customer's AWS account. If not specif... | **Optional** | `"{{.nuon.install.id}}-maintenance"` | | **`enable_kube_config`**
boolean | whether to fetch and inject kubeconfig for this action When set to false, the action runner will not fetch the install's kubeconfig or set the KUBECONFIG env var. Defaults to true. Set to false for... | **Optional** | `"true"`, `"false"` | | **`image`**
string | container image the action's steps run inside Requires the image-backed-actions org feature, which is off by default. Optional container image supplying the tools the action needs. Each step's inli... | **Optional** | `"ghcr.io/acme/kubernetes-tools:v1"`, `"{{.nuon.components.runbook-tools.outputs.image.ref}}"` | | **`kubernetes_context`**
string | kubernetes context this action targets Name of a top-level kubernetes\_context binding to target when this action runs. When set, the action's runner receives the cluster connection details from tha... | **Optional** | `"compute"`, `"data-cluster"` | ### `triggers` | Property | Description | Values | Example | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- | | **`type`**
string | type of trigger Supported trigger types: manual, cron, pre-provision, post-provision, post-provision-sandbox, pre-reprovision, post-reprovision, pre-deprovision, post-deprovision, pre-deploy-all-co... | **✅ Required** | `"manual"`, `"cron"`, `"post-provision"` | | **`index`**
integer | index for manual trigger Used to differentiate multiple manual triggers in the same action | **Optional** | `"0"`, `"1"` | | **`cron_schedule`**
string | cron schedule expression for scheduled triggers Standard cron format (minute hour day month weekday). For example, '\*/5 \* \* \* \*' runs every 5 minutes | **Optional** | `"*/5 * * * *"`, `"0 */4 * * *"` | | **`component_name`**
string | component name for component-specific triggers Required for pre-deploy-component, post-deploy-component, pre-teardown-component, post-teardown-component, pre-enable-component, post-enable-component... | **Optional** | `"database"`, `"api-server"` | ### `steps` | Property | Description | Values | Example | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------- | | **`name`**
string | name of the step Displayed in action logs and the Nuon dashboard | **✅ Required** | `"healthcheck"`, `"database_migration"` | | **`env_vars`**
object | environment variables to pass to the step Map of environment variables that will be available to the command. Supports Go templating for values | **Optional** | - | | **`public_repo`**
PublicRepoConfig | public repository containing the step script Clone a public GitHub repository to load scripts from. Requires 'repo', 'branch', and optionally 'directory' fields | **Optional** | - | | **`connected_repo`**
ConnectedRepoConfig | connected repository containing the step script Use a Nuon-connected repository to load scripts from. Requires 'repo', 'branch', and optionally 'directory' fields | **Optional** | - | | **`command`**
string | command to execute Required field. Supports Go templating (e.g., \{\{.nuon.install.id}}). The command is executed in the runner environment | **Optional** | `"./healthcheck"`, `"bash -c 'curl https://example.com'"` | | **`inline_contents`**
string | inline script contents Embed script contents directly in the config file. Supports Go templating and external URLs: HTTP(S) ([https://example.com/script.sh](https://example.com/script.sh)), git repositories (git::[https://github.co](https://github.co)... | **Optional** | `"./src/healthcheck.sh"`, `"#!/usr/bin/env sh\nkubectl get pods -n default"` | # Branch Source: https://docs.nuon.co/config-ref/branch JSON Schema reference for branch configuration # Branch ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------- | ------- | | **`name`**
string | name of the app branch | **✅ Required** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected GitHub repo the branch tracks | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public git repo the branch tracks | **Optional** | - | | **`install_groups`**
[array](#install_groups) | ordered deployment groups for this branch | **Optional** | - | | **`preview`**
[AppBranchPreviewConfig](#preview) | default preview run settings for this branch | **Optional** | - | | **`post_deploy_runbooks`**
array | names of runbooks to run on each install, in order, after its deploy succeeds; resolved to IDs at sync time | **Optional** | - | | **`ignore_changes_regex`**
string | RE2 regex matched against every changed file path; a run whose entire changed file set matches is not attempted | **Optional** | - | | **`send_statuses_on_ignore`**
boolean | whether to send a successful commit status when a run is ignored by ignore\_changes\_regex | **Optional** | - | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `install_groups` | Property | Description | Values | Example | | --------------------------------------------------- | ----------------------------------------------------------------------------- | -------------- | ------- | | **`name`**
string | name of the install group | **✅ Required** | - | | **`order`**
integer | deployment order (lower runs first) | **Optional** | - | | **`install_ids`**
array | static list of install IDs | **Optional** | - | | **`install_names`**
array | static list of install names, resolved to IDs at sync time | **Optional** | - | | **`label_selector`**
object | label key-value pairs to dynamically match installs | **Optional** | - | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve this group's plan when all policy checks pass. Defaults to false | **Optional** | - | ### `preview` | Property | Description | Values | Example | | -------------------------------- | --------------------------------------------------------------------- | ------------ | ------- | | **`mode`**
string | preview run mode: plan-only, apply, or build-only | **Optional** | - | | **`install_id`**
string | default install ID for preview runs | **Optional** | - | | **`install_name`**
string | default install name for preview runs, resolved to an ID at sync time | **Optional** | - | | **`label_selector`**
object | label key-value pairs to select the default preview install | **Optional** | - | | **`set_statuses`**
boolean | whether to set GitHub commit statuses for preview runs | **Optional** | - | | **`comment`**
boolean | whether to comment on the pull request with preview results | **Optional** | - | # Break Glass Source: https://docs.nuon.co/config-ref/break-glass JSON Schema reference for break-glass configuration # Break Glass ## Properties | Property | Description | Values | Example | | ------------------------------ | ------------------------------------ | ------------ | ------- | | **`role`**
[array](#role) | Roles to be used for breaking glass. | **Optional** | - | ### `role` | Property | Description | Values | Example | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **`name`**
string | name of the role Name used for the role in the target cloud platform. Supports Go templating using standard template variables (e.g., \{\{.nuon.install.id}}) | **✅ Required** | `"app-{{.nuon.install.id}}-role"`, `"admin-role"` | | **`description`**
string | description of the role Human-readable description that explains the role's purpose. Rendered in the installer to customers. Supports templating | **✅ Required** | `"Provides S3 bucket access for the application"`, `"Database migration role with elevated permissions"` | | **`policies`**
array | policy definitions for the role List of policies to attach to the role. Each policy defines cloud-specific permissions (AWS IAM policies, GCP IAM permissions, or GCP predefined roles) | **✅ Required** | - | | **`type`**
string | role type in permission directory Used when defining permissions in a directory. Indicates when the role is active (provision, maintenance, or deprovision). Supports templating | **Optional** | `"provision"`, `"maintenance"`, `"deprovision"` | | **`cloud_platform`**
string | target cloud platform Cloud platform this role targets. Determines which downstream renderer processes the role (e.g., AWS CloudFormation vs GCP IAM). Defaults to aws if omitted | **Optional**
`"aws"`, `"azure"`, `"gcp"` | `"aws"`, `"gcp"` | | **`display_name`**
string | display name of the role Human-readable display name shown in the installer UI. Supports templating | **Optional** | `"Application S3 Access"`, `"Database Admin"` | | **`permissions_boundary`**
string | \[AWS] permissions boundary policy \[AWS only] Optional ARN of a permissions boundary policy. Limits the maximum permissions the role can have. Supports templating and external file sources: HTTP(S) ... | **Optional** | `"./provision_boundary.json"`, `"./maintenance_boundary.json"` | | **`enabled_in_stack`**
boolean | whether the role is enabled by default in the install stack Controls the default value of the Enable parameter for this role in the install stack (CloudFormation parameter or Terraform variable, de... | **Optional** | - | # Container Image Source: https://docs.nuon.co/config-ref/container-image JSON Schema reference for container-image configuration # Container Image ## Properties | Property | Description | Values | Example | | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------ | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"container_image"`, `"external_image"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`aws_ecr`**
[AWSECRConfig](#aws_ecr) | AWS ECR image configuration Configuration for pulling images from AWS Elastic Container Registry. Use when deploying images from private ECR repositories | **Optional** | - | | **`gcp_gar`**
[GCPGARConfig](#gcp_gar) | GCP Artifact Registry image configuration Configuration for pulling images from Google Artifact Registry. Use when deploying images from private GAR repositories | **Optional** | - | | **`azure_acr`**
[AzureACRConfig](#azure_acr) | Azure Container Registry image configuration Configuration for pulling images from Azure Container Registry. Use when deploying images from private ACR repositories | **Optional** | - | | **`public`**
[PublicImageConfig](#public) | public registry image configuration Configuration for pulling images from public container registries (Docker Hub, Quay.io, GCR, etc) | **Optional** | - | | **`verification`**
[Verification](#verification) | container image signature verification policy Require the resolved image digest to satisfy at least one configured Sigstore keyless identity or Cosign public key before Nuon copies it | **Optional** | - | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 15m. Max: 1h | **Optional**
Default: `"15m"` | `"30m"`, `"1h"` | | **`deploy_timeout`**
string | deploy operation timeout Duration string for deploy operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | `"30m"`, `"1h"` | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `aws_ecr` | Property | Description | Values | Example | | | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- | - | ------- | | **`iam_role_arn`**
string | IAM role ARN for ECR access ARN of the IAM role with permissions to pull images from the ECR repository | **✅ Required** | `"arn:aws:iam::123456789012:role/ecr-pull-role"` | | | | **`region`**
string | AWS region for the ECR repository AWS region where the ECR repository is located | **✅ Required** | `"us-east-1"`, `"us-west-2"`, `"eu-west-1"` | | | | **`image_url`**
string | ECR image URL Full URL to the ECR image (without tag). Format: \.dkr.ecr.\.amazonaws.com/\/\ | **✅ Required** | `"123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp/api"`, `"123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp/worker"` | | | | **`tag`**
string | image tag Tag or version of the container image to deploy. Either tag or update\_policy must be set. Supports templating (e.g., \{\{.nuon.install.id}}) | **Optional** | `"v1.0.0"`, `"latest"`, `"{{.nuon.install.id}}"` | | | | **`update_policy`**
string | semver constraint for tag resolution Semver constraint for picking a tag at build time. When set, at each build the runner lists tags from the registry, filters to those that parse as semver and sa... | **Optional** | `"~1.25.0"`, `"^2.0.0"`, `"\u003e=1.0.0,\u003c2.0.0"`, `"1.x"`, \`"^1.0 | | ^2.0"\` | ### `gcp_gar` | Property | Description | Values | Example | | | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------- | - | ------- | | **`gcp_project_id`**
string | GCP project ID Google Cloud project ID where the Artifact Registry repository is located | **✅ Required** | `"my-gcp-project"` | | | | **`region`**
string | GCP region for the GAR repository Google Cloud region where the Artifact Registry repository is located | **✅ Required** | `"us-central1"`, `"us-east1"`, `"europe-west1"` | | | | **`image_url`**
string | GAR image URL Full URL to the GAR image (without tag). Format: \-docker.pkg.dev/\/\/\ | **✅ Required** | `"us-central1-docker.pkg.dev/my-project/my-repo/my-image"` | | | | **`tag`**
string | image tag Tag or version of the container image to deploy. Either tag or update\_policy must be set. Supports templating (e.g., \{\{.nuon.install.id}}) | **Optional** | `"v1.0.0"`, `"latest"`, `"{{.nuon.install.id}}"` | | | | **`service_account_email`**
string | GCP service account for impersonation Optional service account email to impersonate when pulling from GAR. If not set, uses application default credentials | **Optional** | `"my-sa@my-project.iam.gserviceaccount.com"` | | | | **`workload_identity_provider`**
string | - | **Optional** | - | | | | **`update_policy`**
string | semver constraint for tag resolution Semver constraint for picking a tag at build time. When set, at each build the runner lists tags from the registry, filters to those that parse as semver and sa... | **Optional** | `"~1.25.0"`, `"^2.0.0"`, `"\u003e=1.0.0,\u003c2.0.0"`, `"1.x"`, \`"^1.0 | | ^2.0"\` | ### `azure_acr` | Property | Description | Values | Example | | | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------- | - | ------- | | **`image_url`**
string | ACR image URL Full URL to the ACR image (without tag). Format: \.azurecr.io/\/\ | **✅ Required** | `"myregistry.azurecr.io/myapp/api"` | | | | **`registry_url`**
string | ACR login server Azure Container Registry login server. Format: \.azurecr.io | **✅ Required** | `"myregistry.azurecr.io"` | | | | **`tag`**
string | image tag Tag or version of the container image to deploy. Either tag or update\_policy must be set. Supports templating (e.g., \{\{.nuon.install.id}}) | **Optional** | `"v1.0.0"`, `"latest"`, `"{{.nuon.install.id}}"` | | | | **`tenant_id`**
string | Azure tenant ID owning the registry Azure AD tenant that owns the registry. Required to reach a registry in a tenant Nuon holds no identity in, and must be set together with client\_id and one of cl... | **Optional** | `"00000000-0000-0000-0000-000000000000"` | | | | **`client_id`**
string | Azure client ID of the app registration granted AcrPull Application (client) ID of an app registration in tenant\_id that has been granted AcrPull on the registry. Must be set together with tenant\_i... | **Optional** | `"00000000-0000-0000-0000-000000000000"` | | | | **`client_secret_name`**
string | name of the app secret holding the client secret Name of an app secret (nuon apps variables create) whose value is the app registration's client secret. This is the secret's name, not the secret it... | **Optional** | `"azure-acr-client-secret"` | | | | **`client_certificate_name`**
string | name of the app secret holding the client certificate Name of an app secret (nuon apps variables create) whose value is the app registration's base64-encoded PEM certificate. This is the secret's n... | **Optional** | `"azure-acr-client-cert"` | | | | **`update_policy`**
string | semver constraint for tag resolution Semver constraint for picking a tag at build time. When set, at each build the runner lists tags from the registry, filters to those that parse as semver and sa... | **Optional** | `"~1.25.0"`, `"^2.0.0"`, `"\u003e=1.0.0,\u003c2.0.0"`, `"1.x"`, \`"^1.0 | | ^2.0"\` | ### `public` | Property | Description | Values | Example | | | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------- | - | ------- | | **`image_url`**
string | container image URL Full URL to the container image from a public registry (Docker Hub, Quay.io, etc). Format: \[registry/]\/\ | **✅ Required** | `"nginx:latest"`, `"docker.io/library/postgres"`, `"quay.io/myorg/myapp"`, `"gcr.io/myproject/myapp"` | | | | **`tag`**
string | image tag Tag or version of the container image to deploy. Either tag or update\_policy must be set. Supports templating (e.g., \{\{.nuon.install.id}}) | **Optional** | `"v1.0.0"`, `"latest"`, `"{{.nuon.install.id}}"` | | | | **`update_policy`**
string | semver constraint for tag resolution Semver constraint for picking a tag at build time. When set, at each build the runner lists tags from the registry, filters to those that parse as semver and sa... | **Optional** | `"~1.25.0"`, `"^2.0.0"`, `"\u003e=1.0.0,\u003c2.0.0"`, `"1.x"`, \`"^1.0 | | ^2.0"\` | ### `verification` | Property | Description | Values | Example | | ------------------------------------ | ----------- | ------------ | ------- | | **`require_signature`**
boolean | - | **Optional** | - | | **`authorities`**
array | - | **Optional** | - | # Helm Source: https://docs.nuon.co/config-ref/helm JSON Schema reference for helm configuration # Helm ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------- | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"helm_chart"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`chart_name`**
string | Helm chart name Name of the Helm chart to deploy. Must match the chart name in the repository or Helm repo | **✅ Required** | `"karpenter-nodepools"`, `"prometheus"`, `"cert-manager"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`values`**
object | inline Helm values Map of Helm values as key-value pairs. These are passed to helm install/upgrade as --set arguments. Supports Nuon templating | **Optional** | - | | **`values_file`**
[array](#values_file) | Helm values files Array of external Helm values files to load. Each entry can specify a path to a local file or inline contents. Supports Nuon templating and external file sources | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public repository with the Helm chart Configuration for a public Git repository containing the Helm chart source. Mutually exclusive with connected\_repo and helm\_repo | **Optional** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected repository with the Helm chart Configuration for a Nuon-connected private repository containing the Helm chart source. Mutually exclusive with public\_repo and helm\_repo | **Optional** | - | | **`helm_repo`**
[HelmRepoConfig](#helm_repo) | Helm chart repository Configuration for pulling a chart from a Helm repository (e.g., a public chart registry). Mutually exclusive with public\_repo and connected\_repo | **Optional** | - | | **`namespace`**
string | Kubernetes namespace to deploy into Kubernetes namespace where the Helm release will be installed. Defaults to \{\{.nuon.install.id}}. Supports Nuon templating | **Optional** | `"clickhouse"`, `"monitoring"`, `"{{.nuon.install.id}}"` | | **`storage_driver`**
string | Helm storage driver Backend storage driver for Helm release metadata. Defaults to configmap | **Optional**
`"configmap"`, `"secret"` | `"configmap"` | | **`take_ownership`**
boolean | adopt existing Helm releases If true, Nuon will adopt an existing Helm release with the same name and namespace that was not originally managed by Nuon. Useful when migrating existing deployments t... | **Optional** | - | | **`skip_crds`**
boolean | skip Helm chart CRDs If true, Helm will not install CRDs from the chart's crds directory. CRDs rendered from templates are not skipped | **Optional**
Default: `"false"` | `"true"` | | **`drift_schedule`**
string | drift detection schedule Cron expression for periodic drift detection. If not set, drift detection is disabled | **Optional** | `"0 2 * * *"` | | **`health`**
[ComponentHealthConfig](#health) | component health configuration Live health checking configuration for this component. Health checking is enabled by default; use this block to tune the stabilization window or make deploys block on... | **Optional** | - | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | `"30m"`, `"1h"` | | **`deploy_timeout`**
string | deploy operation timeout Duration string for deploy operations (e.g., "30m", "1h"). Default: 30m. Max: 1h | **Optional**
Default: `"30m"` | `"30m"`, `"1h"` | | **`max_auto_retries`**
integer | maximum automatic retry attempts on deploy failure Maximum number of automatic retry attempts for failed deployments. Set to 0 to disable auto-retry. Default: 0 (disabled) | **Optional**
Default: `"0"` | `"3"`, `"5"` | | **`skip_noops`**
boolean | Skip the deploy step when the plan has no changes (a no-op). Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve the deploy when all policy checks pass. Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`value`**
[array](#value) | deprecated: use values map instead Deprecated: Array of name/value pairs for Helm values. Use the values map instead | **Optional** | - | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `values_file` | Property | Description | Values | Example | | -------------------------- | --------------------------------- | ------------ | -------------------------------------------------------------- | | **`source`**
string | \[DEPRECATED: use 'path' instead] | **Optional** | - | | **`contents`**
string | Contents of the values file. | **Optional** | `"./values/whoami.yaml"` | | **`path`**
string | Path to the values file. | **Optional** | `"./values/clickhouse-operator.yaml"`, `"./values/coder.yaml"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `helm_repo` | Property | Description | Values | Example | | -------------------------- | ----------------------------------- | -------------- | ------------------------------------------------------ | | **`repo_url`**
string | URL of the helm chart repository | **✅ Required** | `"https://prometheus-community.github.io/helm-charts"` | | **`chart`**
string | name of the chart in the repository | **✅ Required** | `"kube-prometheus-stack"` | | **`version`**
string | version of the chart to use | **Optional** | `"79.4.1"` | ### `health` | Property | Description | Values | Example | | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | --------------- | | **`enabled`**
boolean | enable live health checking Whether live health checking and the health verdict apply to this component. Default: true | **Optional**
Default: `"true"` | `"false"` | | **`stabilization_window`**
string | health stabilization window How long the component must hold healthy after a deploy applies before the deploy step is considered done. Duration string (e.g., "3m", "10m"). Default: 3m. Max: 1h | **Optional**
Default: `"3m"` | `"3m"`, `"10m"` | | **`block_deploy`**
boolean | fail the deploy when health does not stabilize When true, the deploy step fails if health does not stabilize inside the window. When false, the step still completes and only records what health did... | **Optional**
Default: `"false"` | `"true"` | | **`probes`**
array | synthetic health probes Probes the runner executes from inside the install to assert the component is actually serving. Each probe reports as its own health resource, and a failing probe makes the ... | **Optional** | - | | **`required_checks`**
array | - | **Optional** | - | ### `value` | Property | Description | Values | Example | | ----------------------- | --------------- | ------------ | ------------------------------- | | **`name`**
string | helm value name | **Optional** | `"replicaCount"`, `"image.tag"` | | **`value`**
string | helm value | **Optional** | `"3"`, `"{{.nuon.install.id}}"` | # Configuration Reference Source: https://docs.nuon.co/config-ref/index Complete reference for Nuon configuration file schemas # Configuration Reference This section provides detailed JSON Schema references for all Nuon configuration types. ## Available Configurations # Input Source: https://docs.nuon.co/config-ref/input JSON Schema reference for input configuration # Input ## Properties | Property | Description | Values | Example | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------- | | **`display_name`**
string | display name of the input Human-readable name shown in the installer UI to customers | **✅ Required** | `"API Token"`, `"Database URL"` | | **`description`**
string | input description Detailed explanation of what this input is for, rendered in the installer to guide users | **✅ Required** | `"The API token for authenticating with the external service"`, `"Connection string for the PostgreSQL database"` | | **`group`**
string | input group name Name of the input group this field belongs to. Must match a defined group in the inputs section | **✅ Required** | `"database"`, `"integrations"` | | **`name`**
string | input name Used to reference the input via variable templating (e.g., \{\{.nuon.inputs.input\_name}}) | **Optional** | `"api_token"`, `"database_url"` | | **`default`**
object | default value for the input Default value used if customer does not provide one. Type must match the input type | **Optional** | `"production"`, `"5432"` | | **`required`**
boolean | whether input is required If true, customer must provide a value during installation. If false, can be skipped | **Optional** | - | | **`sensitive`**
boolean | whether input is sensitive If true, the value will be masked/hidden in the UI and logs after the install is created. Use for passwords, tokens, and API keys | **Optional** | - | | **`type`**
string | input type Data type for the input. Supported types: string, number, list, json, bool, yaml, hcl | **Optional** | `"string"`, `"number"`, `"json"`, `"bool"`, `"yaml"`, `"hcl"` | | **`internal`**
boolean | Deprecated: this field has no effect and will be ignored. | **Optional** | - | | **`user_configurable`**
boolean | whether input is user configurable If true, input can be modified by end users after installation | **Optional** | - | # Input Group Source: https://docs.nuon.co/config-ref/input-group JSON Schema reference for input-group configuration # Input Group ## Properties | Property | Description | Values | Example | | ------------------------------ | -------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------- | | **`name`**
string | Group name, which must be referenced by each input. | **✅ Required** | `"database"`, `"compute"` | | **`description`**
string | Human readable description which is rendered in the installer. | **✅ Required** | `"Database configuration options"`, `"How many nodes to provision to support environments"` | | **`display_name`**
string | Human readable name which is rendered in the installer. | **Optional** | `"Database Settings"`, `"Kubernetes Nodes"` | # Inputs Source: https://docs.nuon.co/config-ref/inputs JSON Schema reference for inputs configuration # Inputs ## Properties | Property | Description | Values | Example | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | | **`input`**
[array](#input) | list of inputs Array of input definitions that customers can configure during installation | **Optional** | - | | **`group`**
[array](#group) | list of input groups Array of input group definitions that organize related inputs in the installer UI | **Optional** | - | | **`sources`**
array | external input source files Array of paths to external files containing additional input definitions. Each file is loaded and merged into the inputs configuration. Supports YAML, JSON, and TOML for... | **Optional** | - | ### `input` | Property | Description | Values | Example | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------- | | **`display_name`**
string | display name of the input Human-readable name shown in the installer UI to customers | **✅ Required** | `"API Token"`, `"Database URL"` | | **`description`**
string | input description Detailed explanation of what this input is for, rendered in the installer to guide users | **✅ Required** | `"The API token for authenticating with the external service"`, `"Connection string for the PostgreSQL database"` | | **`group`**
string | input group name Name of the input group this field belongs to. Must match a defined group in the inputs section | **✅ Required** | `"database"`, `"integrations"` | | **`name`**
string | input name Used to reference the input via variable templating (e.g., \{\{.nuon.inputs.input\_name}}) | **Optional** | `"api_token"`, `"database_url"` | | **`default`**
object | default value for the input Default value used if customer does not provide one. Type must match the input type | **Optional** | `"production"`, `"5432"` | | **`required`**
boolean | whether input is required If true, customer must provide a value during installation. If false, can be skipped | **Optional** | - | | **`sensitive`**
boolean | whether input is sensitive If true, the value will be masked/hidden in the UI and logs after the install is created. Use for passwords, tokens, and API keys | **Optional** | - | | **`type`**
string | input type Data type for the input. Supported types: string, number, list, json, bool, yaml, hcl | **Optional** | `"string"`, `"number"`, `"json"`, `"bool"`, `"yaml"`, `"hcl"` | | **`internal`**
boolean | Deprecated: this field has no effect and will be ignored. | **Optional** | - | | **`user_configurable`**
boolean | whether input is user configurable If true, input can be modified by end users after installation | **Optional** | - | ### `group` | Property | Description | Values | Example | | ------------------------------ | -------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------- | | **`name`**
string | Group name, which must be referenced by each input. | **✅ Required** | `"database"`, `"compute"` | | **`description`**
string | Human readable description which is rendered in the installer. | **✅ Required** | `"Database configuration options"`, `"How many nodes to provision to support environments"` | | **`display_name`**
string | Human readable name which is rendered in the installer. | **Optional** | `"Database Settings"`, `"Kubernetes Nodes"` | # Install Source: https://docs.nuon.co/config-ref/install JSON Schema reference for install configuration # Install ## Properties | Property | Description | Values | Example | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------- | | **`name`**
string | name of the install Unique identifier for this install configuration | **✅ Required** | `"production"`, `"staging"`, `"customer-acme"` | | **`approval_option`**
string | approval option for the install Controls how deployments are approved. Options: 'approve-all' (automatic approval) or 'prompt' (requires confirmation) | **Optional** | `"approve-all"`, `"prompt"` | | **`labels`**
object | key/value labels for the install Tag installs with arbitrary metadata like environment, region, or version. Values can use the .nuon templating syntax to render from install state, and re-render as... | **Optional** | `{"env":"production","region":"{{ .nuon.cloud_account.aws.region }}"}` | | **`aws_account`**
[AWSAccount](#aws_account) | AWS account configuration AWS-specific settings for this install, including region and other account details | **Optional** | - | | **`gcp_account`**
[GCPAccount](#gcp_account) | GCP account configuration GCP-specific settings for this install, including project ID and region | **Optional** | - | | **`azure_account`**
[AzureAccount](#azure_account) | Azure account configuration Azure-specific settings for this install, including the deployment location | **Optional** | - | | **`inputs`**
array | input values Array of input groups with key-value pairs for customer inputs provided during installation | **Optional** | - | | **`stack_overrides`**
[InstallStackOverrides](#stack_overrides) | Stack template overrides Per-install overrides for the app-level stack template configuration. Overrides take precedence over app-level defaults. | **Optional** | - | | **`component_toggles`**
object | - | **Optional** | - | | **`components`**
object | - | **Optional** | - | ### `aws_account` | Property | Description | Values | Example | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ------------------------------------------- | | **`region`**
string | AWS region AWS region where the infrastructure will be deployed | **✅ Required** | `"us-east-1"`, `"us-west-2"`, `"eu-west-1"` | | **`account_id`**
string | AWS account ID AWS account this install targets. Required when phone home authentication is enabled for the organization. Immutable once the install exists. | **Optional** | `"123456789012"` | ### `gcp_account` | Property | Description | Values | Example | | ---------------------------- | -------------------------------------------------------------------- | ------------ | --------------------------------- | | **`project_id`**
string | GCP project ID GCP project where the infrastructure will be deployed | **Optional** | `"my-gcp-project"` | | **`region`**
string | GCP region GCP region where the infrastructure will be deployed | **Optional** | `"us-central1"`, `"europe-west1"` | ### `azure_account` | Property | Description | Values | Example | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- | | **`location`**
string | Azure location Azure location/region where the infrastructure will be deployed | **✅ Required** | `"eastus"`, `"westus2"`, `"westeurope"` | | **`subscription_id`**
string | Azure subscription ID Azure subscription this install targets. Required when phone home authentication is enabled for the organization. Immutable once the install exists. | **Optional** | `"00000000-0000-0000-0000-000000000000"` | ### `stack_overrides` | Property | Description | Values | Example | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------- | | **`vpc_nested_template_url`**
string | VPC nested template URL override Per-install override for the VPC nested CloudFormation template URL. Overrides the app-level default from stack.toml. | **Optional** | `"https://nuon-artifacts.s3.us-west-2.amazonaws.com/templates/custom-vpc.yaml"` | | **`runner_nested_template_url`**
string | Runner nested template URL override Per-install override for the runner nested CloudFormation template URL. Overrides the app-level default from stack.toml. | **Optional** | `"https://nuon-artifacts.s3.us-west-2.amazonaws.com/templates/custom-runner.yaml"` | | **`custom_nested_stacks`**
object | Custom nested stack overrides Per-install overrides for custom install stacks. Entries with the same name as app-level stacks replace them; new names are appended. Supports AWS CloudFormation, Azur... | **Optional** | - | # Installer Source: https://docs.nuon.co/config-ref/installer JSON Schema reference for installer configuration # Installer ## Properties | Property | Description | Values | Example | | | --------------------------------------- | ----------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------- | ----------------------------------------- | | **`name`**
string | installer name Human-readable name for the installer | **Optional** | `"My SaaS Installer"` | | | **`description`**
string | installer description Detailed description of what this installer does | **Optional** | `"Complete installer for My SaaS application"` | | | **`slug`**
string | URL-safe slug URL-friendly identifier for the installer | **Optional** | `"my-saas-installer"` | | | **`apps`**
array | list of app names Array of app names to include in this installer | **Optional** | `"api"`, `"web-ui"` | | | **`documentation_url`**
string | documentation URL Link to the application documentation | **Optional** | `"https://docs.example.com"` | | | **`community_url`**
string | community URL Link to community resources or forum | **Optional** | `"https://community.example.com"` | | | **`homepage_url`**
string | homepage URL Link to the application homepage | **Optional** | `"https://example.com"` | | | **`github_url`**
string | GitHub repository URL Link to the GitHub repository | **Optional** | `"https://github.com/example/repo"` | | | **`logo_url`**
string | logo URL URL to the application logo image | **Optional** | `"https://example.com/logo.png"` | | | **`favicon_url`**
string | favicon URL URL to the favicon image | **Optional** | `"https://example.com/favicon.ico"` | | | **`og_image_url`**
string | OpenGraph image URL URL to the image displayed when sharing the installer on social media | **Optional** | `"https://example.com/og-image.png"` | | | **`demo_url`**
string | demo URL Link to a live demo of the application | **Optional** | `"https://demo.example.com"` | | | **`post_install_markdown`**
string | post-install markdown Markdown content displayed to users after successful installation | **Optional** | `"## Next Steps\n\nYour application is now installed!"` | | | **`copyright_markdown`**
string | copyright markdown Markdown content for copyright information | **Optional** | `"© 2025 Acme Inc. All rights reserved."` | | | **`footer_markdown`**
string | footer markdown Markdown content displayed in the installer footer | **Optional** | \`"[Documentation](https://docs.example.com) | [Support](https://support.example.com)"\` | # Installs Config Source: https://docs.nuon.co/config-ref/installs-config JSON Schema reference for installs-config configuration # Installs Config ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | ---------------------------------------------------------- | ------------ | ------- | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected GitHub repo containing install config TOML files | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public git repo containing install config TOML files | **Optional** | - | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | # Job Source: https://docs.nuon.co/config-ref/job JSON Schema reference for job configuration # Job ## Properties | Property | Description | Values | Example | | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------ | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"job"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`image_url`**
string | job container image URL Docker image URL to execute. Can be a public Docker registry or private registry | **✅ Required** | `"ubuntu:22.04"`, `"ghcr.io/myorg/myjob:v1.0.0"` | | **`tag`**
string | image tag Docker image tag to use | **✅ Required** | `"latest"`, `"v1.0.0"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`cmd`**
array | command to execute Command to run in the job container | **Optional** | `"python"`, `"bash"` | | **`env_vars`**
object | environment variables Map of environment variables to pass to the job container | **Optional** | - | | **`args`**
array | command arguments Arguments to pass to the command | **Optional** | `"-c 'echo hello'"`, `"script.py"` | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | `"30m"`, `"1h"` | | **`deploy_timeout`**
string | deploy operation timeout Duration string for job execution (e.g., "30m", "1h"). Default: 15m. Max: 1h | **Optional**
Default: `"15m"` | `"30m"`, `"1h"` | | **`env_var`**
[array](#env_var) | - | **Optional** | - | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `env_var` | Property | Description | Values | Example | | ----------------------- | -------------------------- | ------------ | ---------------------------------------------------------- | | **`name`**
string | environment variable name | **Optional** | `"NAMESPACE"`, `"INGRESS_NAME"`, `"LOG_LEVEL"` | | **`value`**
string | environment variable value | **Optional** | `"kube-system"`, `"{{.nuon.install.id}}-public"`, `"info"` | # Kubernetes Context Source: https://docs.nuon.co/config-ref/kubernetes-context JSON Schema reference for kubernetes-context configuration # Kubernetes Context ## Properties | Property | Description | Values | Example | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------- | | **`name`**
string | context name Unique name for this kubernetes context within the app. Components reference this name via the top-level kubernetes\_context field | **✅ Required** | `"data-cluster"`, `"shared-prod"` | | **`component`**
string | source peer component Name of the peer component that produces cluster connection details. Must be a terraform\_module or pulumi component, and must expose a `cluster` output object | **✅ Required** | `"data-eks"`, `"shared-aks"` | # Kubernetes Contexts Source: https://docs.nuon.co/config-ref/kubernetes-contexts JSON Schema reference for kubernetes-contexts configuration # Kubernetes Contexts ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | | **`kubernetes_context`**
[array](#kubernetes_context) | list of kubernetes contexts Array of named kubernetes context bindings. Each context references a peer component that produces cluster connection details. Components opt into a context via the top-... | **Optional** | - | ### `kubernetes_context` | Property | Description | Values | Example | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------- | | **`name`**
string | context name Unique name for this kubernetes context within the app. Components reference this name via the top-level kubernetes\_context field | **✅ Required** | `"data-cluster"`, `"shared-prod"` | | **`component`**
string | source peer component Name of the peer component that produces cluster connection details. Must be a terraform\_module or pulumi component, and must expose a `cluster` output object | **✅ Required** | `"data-eks"`, `"shared-aks"` | # Kubernetes Manifest Source: https://docs.nuon.co/config-ref/kubernetes-manifest JSON Schema reference for kubernetes-manifest configuration # Kubernetes Manifest ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------- | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"kubernetes_manifest"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`namespace`**
string | Kubernetes namespace Kubernetes namespace where the manifest will be deployed. Supports template variables | **✅ Required** | `"default"`, `"clickhouse"`, `"{{.nuon.install.id}}"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`manifest`**
string | Kubernetes manifest Path to a YAML manifest file or inline manifest content for Kubernetes resources. Supports templating with variables like \{\{.nuon.install.id}}. Mutually exclusive with kusto... | **Optional** | `"./manifests/deployment.yaml"` | | **`kustomize`**
[KustomizeConfig](#kustomize) | Kustomize configuration Configuration for building manifests from a kustomize overlay. Mutually exclusive with manifest. Requires either public\_repo or connected\_repo | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public repository with kustomize source Configuration for a public Git repository containing kustomize overlays. Only valid when using kustomize, not inline manifests | **Optional** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected repository with kustomize source Configuration for a Nuon-connected private repository containing kustomize overlays. Only valid when using kustomize, not inline manifests | **Optional** | - | | **`drift_schedule`**
string | drift detection schedule Cron expression for periodic drift detection. If not set, drift detection is disabled | **Optional** | `"0 2 * * *"` | | **`health`**
[ComponentHealthConfig](#health) | component health configuration Live health checking configuration for this component. Health checking is enabled by default; use this block to tune the stabilization window or make deploys block on... | **Optional** | - | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | `"30m"`, `"1h"` | | **`deploy_timeout`**
string | deploy operation timeout Duration string for deploy operations (e.g., "30m", "1h"). Default: 15m. Max: 1h | **Optional**
Default: `"15m"` | `"30m"`, `"1h"` | | **`max_auto_retries`**
integer | maximum automatic retry attempts on deploy failure Maximum number of automatic retry attempts for failed deployments. Set to 0 to disable auto-retry. Default: 0 (disabled) | **Optional**
Default: `"0"` | `"3"`, `"5"` | | **`skip_noops`**
boolean | Skip the deploy step when the plan has no changes (a no-op). Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve the deploy when all policy checks pass. Defaults to false | **Optional**
Default: `"false"` | `"true"` | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `kustomize` | Property | Description | Values | Example | | --------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------- | ------------------------------ | | **`path`**
string | kustomization directory path Path to the kustomization directory, relative to the source root. | **✅ Required** | `"overlays/production"`, `"."` | | **`patches`**
array | additional patch files Additional patch files to apply after kustomize build. | **Optional** | `"patches/namespace.yaml"` | | **`enable_helm`**
boolean | enable Helm chart inflation Enable Helm chart inflation during kustomize build. | **Optional** | - | | **`load_restrictor`**
string | file load restrictor Controls how kustomize loads files. Options: none, rootOnly. Default: rootOnly. | **Optional** | `"rootOnly"`, `"none"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `health` | Property | Description | Values | Example | | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | --------------- | | **`enabled`**
boolean | enable live health checking Whether live health checking and the health verdict apply to this component. Default: true | **Optional**
Default: `"true"` | `"false"` | | **`stabilization_window`**
string | health stabilization window How long the component must hold healthy after a deploy applies before the deploy step is considered done. Duration string (e.g., "3m", "10m"). Default: 3m. Max: 1h | **Optional**
Default: `"3m"` | `"3m"`, `"10m"` | | **`block_deploy`**
boolean | fail the deploy when health does not stabilize When true, the deploy step fails if health does not stabilize inside the window. When false, the step still completes and only records what health did... | **Optional**
Default: `"false"` | `"true"` | | **`probes`**
array | synthetic health probes Probes the runner executes from inside the install to assert the component is actually serving. Each probe reports as its own health resource, and a failing probe makes the ... | **Optional** | - | | **`required_checks`**
array | - | **Optional** | - | # Metadata Source: https://docs.nuon.co/config-ref/metadata JSON Schema reference for metadata configuration # Metadata ## Properties | Property | Description | Values | Example | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------ | | **`version`**
string | config file version Version of the configuration file format | **✅ Required** | `"1.0.0"`, `"2.0.0"` | | **`description`**
string | app description Detailed description of the application, displayed in the installer UI | **Optional** | `"A powerful SaaS platform for managing deployments"` | | **`display_name`**
string | app display name Human-readable name for the application, shown in the installer | **Optional** | `"My SaaS App"`, `"Enterprise Platform"` | | **`slack_webhook_url`**
string | Slack webhook URL Slack webhook URL to receive deployment notifications and updates | **Optional** | `"https://hooks.slack.com/services/YOUR/WEBHOOK/URL"` | | **`readme`**
string | README content Markdown content displayed as README documentation for the application | **Optional** | `"./README.md"` | | **`label_colors`**
object | label key color codes Map of label key names to hex color codes for customizing label display in the dashboard | **Optional** | `"{\"env\": \"#FF5733\", \"region\": \"#33FF57\"}"` | | **`default_labels`**
object | default labels for all installs Labels applied to every install of the app. Values may use the templating syntax (\{\{ .nuon.\* }}). These labels cannot be edited or removed on individual installs... | **Optional** | `"{\"tier\": \"prod\", \"region\": \"{{ .nuon.cloud_account.aws.region }}\"}"` | # Permission Source: https://docs.nuon.co/config-ref/permission JSON Schema reference for permission configuration # Permission ## Properties | Property | Description | Values | Example | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **`name`**
string | name of the role Name used for the role in the target cloud platform. Supports Go templating using standard template variables (e.g., \{\{.nuon.install.id}}) | **✅ Required** | `"app-{{.nuon.install.id}}-role"`, `"admin-role"` | | **`description`**
string | description of the role Human-readable description that explains the role's purpose. Rendered in the installer to customers. Supports templating | **✅ Required** | `"Provides S3 bucket access for the application"`, `"Database migration role with elevated permissions"` | | **`policies`**
[array](#policies) | policy definitions for the role List of policies to attach to the role. Each policy defines cloud-specific permissions (AWS IAM policies, GCP IAM permissions, or GCP predefined roles) | **✅ Required** | - | | **`type`**
string | role type in permission directory Used when defining permissions in a directory. Indicates when the role is active (provision, maintenance, or deprovision). Supports templating | **Optional** | `"provision"`, `"maintenance"`, `"deprovision"` | | **`cloud_platform`**
string | target cloud platform Cloud platform this role targets. Determines which downstream renderer processes the role (e.g., AWS CloudFormation vs GCP IAM). Defaults to aws if omitted | **Optional**
`"aws"`, `"azure"`, `"gcp"` | `"aws"`, `"gcp"` | | **`display_name`**
string | display name of the role Human-readable display name shown in the installer UI. Supports templating | **Optional** | `"Application S3 Access"`, `"Database Admin"` | | **`permissions_boundary`**
string | \[AWS] permissions boundary policy \[AWS only] Optional ARN of a permissions boundary policy. Limits the maximum permissions the role can have. Supports templating and external file sources: HTTP(S) ... | **Optional** | `"./provision_boundary.json"`, `"./maintenance_boundary.json"` | | **`enabled_in_stack`**
boolean | whether the role is enabled by default in the install stack Controls the default value of the Enable parameter for this role in the install stack (CloudFormation parameter or Terraform variable, de... | **Optional** | - | ### `policies` | Property | Description | Values | Example | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | -------------------------------------------------------------------------------------------------------------- | | **`managed_policy_name`**
string | \[AWS] managed policy name \[AWS only] Name or ARN of an AWS managed policy to attach to the IAM role. Mutually exclusive with contents | **Optional** | `"AmazonS3FullAccess"`, `"ReadOnlyAccess"` | | **`name`**
string | policy name Name for the policy. Used across all cloud platforms when creating the permission grant. Optional for a bare AWS managed\_policy\_name attachment, which needs no separate name. Supports N... | **Optional** | `"app-{{.nuon.install.id}}-policy"`, `"s3-access-policy"` | | **`contents`**
string | \[AWS] inline policy document \[AWS only] JSON policy document defining inline IAM permissions. Mutually exclusive with managed\_policy\_name. Supports Nuon templating and external file sources: HTTP(S... | **Optional** | `"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"*\"}]}"` | | **`gcp_permissions`**
array | \[GCP] individual permissions \[GCP only] List of individual GCP IAM permission strings to include in a custom role bound to the service account. Use this for fine-grained permission control. Mutuall... | **Optional** | `"compute.instances.get"`, `"storage.objects.list"` | | **`gcp_predefined_role`**
string | \[GCP] predefined role \[GCP only] Name of a GCP predefined role to bind to the service account. This is the GCP equivalent of AWS managed policies — a Google-managed bundle of permissions. Mutually ... | **Optional** | `"roles/editor"`, `"roles/owner"` | | **`azure_actions`**
array | \[Azure] individual RBAC actions \[Azure only] List of Azure RBAC action strings to include in a custom role definition bound to the operation's managed identity. Use this for fine-grained permission... | **Optional** | `"Microsoft.Compute/*"`, `"Microsoft.Resources/subscriptions/resourceGroups/*"` | | **`azure_built_in_roles`**
array | \[Azure] built-in roles \[Azure only] Names of Azure built-in roles to assign to the operation's managed identity (e.g. Contributor, Reader). This is the Azure equivalent of AWS managed policies. Mut... | **Optional** | `"Contributor"`, `"Reader"` | # Permissions Source: https://docs.nuon.co/config-ref/permissions JSON Schema reference for permissions configuration # Permissions ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | | **`provision_role`**
[AppAWSIAMRole](#provision_role) | provisioning IAM role IAM role used during initial provisioning of the install with permissions to set up resources | **Optional** | - | | **`deprovision_role`**
AppAWSIAMRole | deprovisioning IAM role IAM role used for tearing down the install and cleaning up resources | **Optional** | - | | **`maintenance_role`**
AppAWSIAMRole | maintenance IAM role IAM role used for day-to-day maintenance, updates, and operational tasks | **Optional** | - | | **`custom_roles`**
array | custom IAM roles Additional IAM roles for specialized operations beyond the standard provision/maintenance/deprovision lifecycle. Each role must have type set to 'custom' | **Optional** | - | | **`roles`**
array | list of permission roles Array of role definitions in directory-based permission structure. Each role must have a type field (provision, maintenance, deprovision, or custom) | **Optional** | - | ### `provision_role` | Property | Description | Values | Example | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **`name`**
string | name of the role Name used for the role in the target cloud platform. Supports Go templating using standard template variables (e.g., \{\{.nuon.install.id}}) | **✅ Required** | `"app-{{.nuon.install.id}}-role"`, `"admin-role"` | | **`description`**
string | description of the role Human-readable description that explains the role's purpose. Rendered in the installer to customers. Supports templating | **✅ Required** | `"Provides S3 bucket access for the application"`, `"Database migration role with elevated permissions"` | | **`policies`**
array | policy definitions for the role List of policies to attach to the role. Each policy defines cloud-specific permissions (AWS IAM policies, GCP IAM permissions, or GCP predefined roles) | **✅ Required** | - | | **`type`**
string | role type in permission directory Used when defining permissions in a directory. Indicates when the role is active (provision, maintenance, or deprovision). Supports templating | **Optional** | `"provision"`, `"maintenance"`, `"deprovision"` | | **`cloud_platform`**
string | target cloud platform Cloud platform this role targets. Determines which downstream renderer processes the role (e.g., AWS CloudFormation vs GCP IAM). Defaults to aws if omitted | **Optional**
`"aws"`, `"azure"`, `"gcp"` | `"aws"`, `"gcp"` | | **`display_name`**
string | display name of the role Human-readable display name shown in the installer UI. Supports templating | **Optional** | `"Application S3 Access"`, `"Database Admin"` | | **`permissions_boundary`**
string | \[AWS] permissions boundary policy \[AWS only] Optional ARN of a permissions boundary policy. Limits the maximum permissions the role can have. Supports templating and external file sources: HTTP(S) ... | **Optional** | `"./provision_boundary.json"`, `"./maintenance_boundary.json"` | | **`enabled_in_stack`**
boolean | whether the role is enabled by default in the install stack Controls the default value of the Enable parameter for this role in the install stack (CloudFormation parameter or Terraform variable, de... | **Optional** | - | # Policies Source: https://docs.nuon.co/config-ref/policies JSON Schema reference for policies configuration # Policies ## Properties | Property | Description | Values | Example | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------ | ------- | | **`policy`**
[array](#policy) | list of policies Array of policy definitions that enforce compliance and security rules across your infrastructure | **Optional** | - | ### `policy` | Property | Description | Values | Example | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | **`type`**
string | policy type Type of policy that determines where and how it is enforced | **Optional**
`"kubernetes_cluster"`, `"terraform_module"`, `"helm_chart"`, `"kubernetes_manifest"`, `"container_image"`, `"pulumi"`, `"sandbox"` | `"kubernetes_cluster"`, `"terraform_module"` | | **`engine`**
string | policy engine The policy engine used to evaluate the policy. Must be compatible with the policy type. | **Optional**
`"kyverno"`, `"opa"` | `"kyverno"`, `"opa"` | | **`name`**
string | policy name Human-readable name for the policy. If not specified, will be derived from the source filename when parsing from a policies/ directory. | **Optional** | `"disallow-ingress-nginx-custom-snippets"`, `"set-karpenter-non-cpu-limits"` | | **`contents`**
string | policy document Policy content in the appropriate format for the policy type. Supports Nuon templating and external file sources: HTTP(S) URLs ([https://example.com/policy.json](https://example.com/policy.json)), git repositories (g... | **Optional** | `"./disallow-ingress-nginx-custom-snippets.yaml"`, `"./block-mutable-tags.rego"` | | **`components`**
array | target components List of component names this policy applies to. Use \["\*"] to apply to all components of the specified type, or list specific component names. Required for component-scoped policy ... | **Optional** | `"*"`, `"rds_cluster"` | # Policy Source: https://docs.nuon.co/config-ref/policy JSON Schema reference for policy configuration # Policy ## Properties | Property | Description | Values | Example | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | **`type`**
string | policy type Type of policy that determines where and how it is enforced | **Optional**
`"kubernetes_cluster"`, `"terraform_module"`, `"helm_chart"`, `"kubernetes_manifest"`, `"container_image"`, `"pulumi"`, `"sandbox"` | `"kubernetes_cluster"`, `"terraform_module"` | | **`engine`**
string | policy engine The policy engine used to evaluate the policy. Must be compatible with the policy type. | **Optional**
`"kyverno"`, `"opa"` | `"kyverno"`, `"opa"` | | **`name`**
string | policy name Human-readable name for the policy. If not specified, will be derived from the source filename when parsing from a policies/ directory. | **Optional** | `"disallow-ingress-nginx-custom-snippets"`, `"set-karpenter-non-cpu-limits"` | | **`contents`**
string | policy document Policy content in the appropriate format for the policy type. Supports Nuon templating and external file sources: HTTP(S) URLs ([https://example.com/policy.json](https://example.com/policy.json)), git repositories (g... | **Optional** | `"./disallow-ingress-nginx-custom-snippets.yaml"`, `"./block-mutable-tags.rego"` | | **`components`**
array | target components List of component names this policy applies to. Use \["\*"] to apply to all components of the specified type, or list specific component names. Required for component-scoped policy ... | **Optional** | `"*"`, `"rds_cluster"` | # Pulumi Source: https://docs.nuon.co/config-ref/pulumi JSON Schema reference for pulumi configuration # Pulumi ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------ | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"pulumi"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`runtime`**
string | Pulumi runtime The Pulumi runtime to use for the program (go, nodejs, python) | **✅ Required** | `"go"`, `"nodejs"`, `"python"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`pulumi_version`**
string | Pulumi version Version of the Pulumi CLI to use for deployments. If not specified, uses the latest version | **Optional** | `"3.100.0"` | | **`config`**
object | Pulumi stack config Map of Pulumi stack configuration values as key-value pairs. Supports templating. Keys use the format 'namespace:key' (e.g., 'aws:region') | **Optional** | - | | **`env_vars`**
object | environment variables Map of environment variables passed to Pulumi as key-value pairs | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public repository configuration Configuration for a public repository accessible without authentication | **Optional** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected repository configuration Configuration for a private repository connected to the Nuon platform | **Optional** | - | | **`drift_schedule`**
string | drift detection schedule Cron expression for periodic drift detection. If not set, drift detection is disabled. Supports templating | **Optional** | `"0 2 * * *"` | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | - | | **`deploy_timeout`**
string | deploy operation timeout Duration string for deploy operations (e.g., "30m", "1h"). Default: 60m. Max: 1h | **Optional**
Default: `"60m"` | - | | **`max_auto_retries`**
integer | maximum automatic retry attempts on deploy failure Maximum number of automatic retry attempts for failed deployments. Set to 0 to disable auto-retry. Default: 0 (disabled) | **Optional**
Default: `"0"` | `"3"`, `"5"` | | **`skip_noops`**
boolean | Skip the deploy step when the plan has no changes (a no-op). Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve the deploy when all policy checks pass. Defaults to false | **Optional**
Default: `"false"` | `"true"` | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | # Runbook Source: https://docs.nuon.co/config-ref/runbook JSON Schema reference for runbook configuration # Runbook ## Properties | Property | Description | Values | Example | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | --------------------------------------- | | **`name`**
string | name of the runbook The runbook name is displayed in the Runbooks tab of the Nuon dashboard and used to identify it during sync | **✅ Required** | `"v2.3-update"`, `"database-migration"` | | **`steps`**
[array](#steps) | ordered steps to execute in the runbook Sequential list of deploy and action steps. Each step executes in order. Deploy steps can include dependency deployment. Action steps can reference existing ... | **✅ Required** | - | | **`description`**
string | - | **Optional** | - | | **`readme`**
string | readme file for the runbook Markdown file with runbook documentation and instructions. Supports Go templating and external file sources: HTTP(S) URLs, git repositories, file paths, and relative paths | **Optional** | `"./release-notes.md"` | | **`labels`**
object | - | **Optional** | - | | **`input`**
[array](#input) | - | **Optional** | - | | **`dependencies`**
array | - | **Optional** | - | ### `steps` | Property | Description | Values | Example | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ---------------------------------------------------------------------------------- | | **`name`**
string | name of the step Displayed in the workflow UI and runbook detail page | **✅ Required** | `"deploy-database"`, `"run-migrations"` | | **`type`**
string | type of step One of: 'component\_deploy' (deploy a component; 'deploy' is accepted as a legacy alias), 'component\_tear\_down' (tear down a component), 'action' (run an action), 'sandbox\_reprovision',... | **✅ Required** | `"component_deploy"`, `"component_tear_down"`, `"action"`, `"sandbox_reprovision"` | | **`plan_only`**
boolean | generate a plan without applying it Only applies to 'component\_deploy' and 'sandbox\_reprovision' steps. When true, the step generates its plan but does not apply it. Plan-only sandbox reprovision s... | **Optional** | - | | **`component_name`**
string | component to deploy or tear down (for component steps) Name of the component to deploy or tear down. Required when type is 'component\_deploy' or 'component\_tear\_down' | **Optional** | `"database"`, `"api-server"` | | **`deploy_dependents`**
boolean | also deploy transitive dependents When true, deploys the component and all components that transitively depend on it (downstream), in dependency order. Only applies to component\_deploy steps | **Optional** | - | | **`tear_down_dependents`**
boolean | also tear down transitive dependents When true, tears down the component and all components that transitively depend on it (downstream), with dependents torn down first. Only applies to component\_t... | **Optional** | - | | **`deploy_dependencies`**
boolean | legacy alias for deploy\_dependents \[DEPRECATED: use 'deploy\_dependents' instead] | **Optional** | - | | **`skip_component_deploys`**
boolean | skip component deployments after sandbox reprovision Only applies to 'sandbox\_reprovision' steps. When true, only the sandbox infrastructure is reprovisioned and components are NOT redeployed on to... | **Optional** | - | | **`action_name`**
string | existing action to run (for action steps) Name of a previously defined action workflow to execute. Mutually exclusive with inline action fields (command, inline\_contents) | **Optional** | `"database-migration"` | | **`command`**
string | command to execute (for inline action steps) Shell command for an inline action. Supports Go templating | **Optional** | `"./validate.sh"` | | **`inline_contents`**
string | inline script contents (for inline action steps) Embed script contents directly or reference an external file. Supports Go templating and external URLs | **Optional** | `"./scripts/validate.sh"` | | **`env_vars`**
object | environment variables for inline action steps Map of environment variables passed to the inline action command | **Optional** | - | | **`timeout`**
string | timeout for inline action steps Maximum execution time for inline action steps. Must be a valid Go duration string | **Optional** | `"30s"`, `"5m"` | | **`role`**
string | IAM role for inline action execution IAM role name to use when executing the inline action step | **Optional** | - | | **`trigger`**
string | trigger to wait on Required for wait\_for\_event steps. The matched normalized event is available to later action steps by step name, for example \{\{.runbook\_outputs.wait\_for\_tag.event.payload.tag}... | **Optional** | - | | **`event_types`**
array | event types that can resume this step For wait\_for\_event steps, the step resumes when an event from trigger has one of these types and matches every configured filter | **Optional** | - | | **`filters`**
array | - | **Optional** | - | | **`match_all`**
boolean | explicitly match all event types For wait\_for\_event steps, permits matching all event types, including when filters contain only exclusion predicates | **Optional** | - | ### `input` | Property | Description | Values | Example | | ------------------------------ | ----------- | -------------- | ------- | | **`display_name`**
string | - | **✅ Required** | - | | **`description`**
string | - | **✅ Required** | - | | **`name`**
string | - | **Optional** | - | | **`default`**
object | - | **Optional** | - | | **`required`**
boolean | - | **Optional** | - | | **`sensitive`**
boolean | - | **Optional** | - | | **`type`**
string | - | **Optional** | - | # Runner Source: https://docs.nuon.co/config-ref/runner JSON Schema reference for runner configuration # Runner ## Properties | Property | Description | Values | Example | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------- | | **`runner_type`**
string | type of runner Specifies how the runner executes deployments | **✅ Required** | `"aws"` | | **`env_vars`**
object | environment variables Map of environment variables to pass to the runner as key-value pairs | **Optional** | `"DEBUG=true"`, `"LOG_LEVEL=info"` | | **`helm_driver`**
string | Helm driver configuration Specifies the backend driver for Helm operations (e.g., 'configmap', 'secret') | **Optional** | `"configmap"`, `"secret"` | | **`init_script_url`**
string | initialization script URL URL to a script that runs during runner initialization. Supports HTTP(S), git, file, and relative paths (./). Examples: [https://example.com/script.sh](https://example.com/script.sh), ./scripts/init.sh, g... | **Optional** | `"https://raw.githubusercontent.com/nuonco/runner/refs/heads/main/scripts/aws/init-mng-v2.sh"` | | **`phone_home_script_url`**
string | phone-home Lambda source URL URL to the Python source for the install stack's phone-home Lambda, fetched when the stack template is rendered and embedded inline. Leave unset to use the pinned defau... | **Optional** | `"https://raw.githubusercontent.com/nuonco/runner/refs/tags/aws-v0.1.4/scripts/aws/phonehome.py"` | | **`instance_type`**
string | machine/instance type for the install runner Cloud machine/instance type used for the install runner host. Cloud-specific value mapped per runner\_type (an EC2 instance type for aws, a machine type ... | **Optional** | `"t3a.medium"`, `"e2-medium"`, `"Standard_D2s_v5"` | | **`runner_api_url`**
string | custom runner API endpoint Set this to proxy all runner API calls (heartbeats, job polling, logs) through your own domain instead of Nuon's. | **Optional** | `"https://runner-api.example.com"` | | **`public_api_url`**
string | custom public API endpoint Set this to proxy phone-home calls through your own domain instead of Nuon's. | **Optional** | `"https://runner-api.example.com"` | | **`env_var`**
[array](#env_var) | deprecated: use env\_vars map instead Deprecated: Array of name/value pairs for environment variables. Use the env\_vars map instead | **Optional** | - | ### `env_var` | Property | Description | Values | Example | | ----------------------- | -------------------------- | ------------ | ---------------------------------------------------------- | | **`name`**
string | environment variable name | **Optional** | `"NAMESPACE"`, `"INGRESS_NAME"`, `"LOG_LEVEL"` | | **`value`**
string | environment variable value | **Optional** | `"kube-system"`, `"{{.nuon.install.id}}-public"`, `"info"` | # Sandbox Source: https://docs.nuon.co/config-ref/sandbox JSON Schema reference for sandbox configuration # Sandbox ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -------------------------------- | | **`type`**
string | sandbox IaC type IaC type for this sandbox: 'terraform' (default) or 'pulumi'. 'pulumi' requires the pulumi-sandbox feature flag | **Optional** | `"terraform"`, `"pulumi"` | | **`terraform_version`**
string | Terraform version Version of Terraform to use for deployments. Required when type=terraform | **Optional** | `"1.5.0"`, `"1.6.0"`, `"latest"` | | **`runtime`**
string | Pulumi runtime The Pulumi runtime to use for the program (go, nodejs, python). Required when type=pulumi | **Optional** | `"go"`, `"nodejs"`, `"python"` | | **`pulumi_version`**
string | Pulumi version Version of the Pulumi CLI to use. If not specified, uses the latest version | **Optional** | `"3.100.0"` | | **`pulumi_config`**
object | Pulumi stack config Map of Pulumi stack configuration values as key-value pairs. Keys use the format 'namespace:key' (e.g., 'gcp:project'). Supports templating | **Optional** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected repository configuration Configuration for a private repository connected to the Nuon platform | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public repository configuration Configuration for a public repository accessible without authentication | **Optional** | - | | **`drift_schedule`**
string | drift detection schedule Cron expression for periodic drift detection. If not set, drift detection is disabled | **Optional** | `"0 2 * * *"`, `"*/10 * * * *"` | | **`env_vars`**
object | environment variables Map of environment variables passed to Terraform as key-value pairs | **Optional** | - | | **`vars`**
object | Terraform variables Map of Terraform input variables as key-value pairs. Supports templating | **Optional** | - | | **`var_file`**
[array](#var_file) | Terraform variable files Array of external Terraform variable files to load. Each file contents support templating and external file sources: HTTP(S) URLs ([https://example.com/vars.tfvars](https://example.com/vars.tfvars)), git rep... | **Optional** | - | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of sandbox operations to IAM role names. Allows using different roles for different operations (provision, deprovision, reprovision). Roles must be defin... | **Optional** | - | | **`max_auto_retries`**
integer | maximum automatic retry attempts on sandbox apply failure Maximum number of automatic retry attempts for failed sandbox provision, reprovision, and deprovision applies. Set to 0 to disable auto-ret... | **Optional**
Default: `"0"` | `"3"`, `"5"` | | **`skip_noops`**
boolean | Skip the sandbox apply when the plan has no changes (a no-op). Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve the sandbox apply when all policy checks pass. Defaults to false | **Optional**
Default: `"false"` | `"true"` | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `var_file` | Property | Description | Values | Example | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------- | | **`contents`**
string | variable file contents Contents of a Terraform .tfvars file. Supports Nuon templating and external file sources: HTTP(S) URLs ([https://example.com/vars.tfvars](https://example.com/vars.tfvars)), git repositories (git::[https://githu](https://githu)... | **Optional** | `"./sandbox.tfvars"`, `"./variables/production.tfvars"` | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | # Secret Source: https://docs.nuon.co/config-ref/secret JSON Schema reference for secret configuration # Secret ## Properties | Property | Description | Values | Example | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | | **`name`**
string | secret name Identifier for the secret used to reference it via variable templating (e.g., \{\{.nuon.secrets.name}}). Supports templating | **✅ Required** | `"database_password"`, `"api_key"` | | **`description`**
string | secret description Detailed explanation of what this secret is for, displayed to users during installation | **✅ Required** | `"Master password for the database"`, `"API key for external service authentication"` | | **`display_name`**
string | display name Human-readable name shown in the installer UI. Supports templating | **Optional** | `"Database Password"`, `"API Key"` | | **`required`**
boolean | whether secret is required If true, customer must provide a value during installation. If false, can be skipped | **Optional** | - | | **`auto_generate`**
boolean | whether to auto-generate secret If true, a random secret will be generated if customer does not provide one. Cannot be used with required or default | **Optional** | - | | **`format`**
string | secret format Format of the secret value. Supported values: 'base64' for base64-encoded secrets, or empty for plain text | **Optional** | `"base64"` | | **`default`**
string | default value Default value used if customer does not provide one. Cannot be used with required or auto\_generate | **Optional** | - | | **`kubernetes_sync`**
boolean | sync to Kubernetes If true, the secret will be synced to a Kubernetes Secret resource | **Optional** | - | | **`kubernetes_secret_namespace`**
string | Kubernetes namespace Kubernetes namespace where the secret will be created. Required if kubernetes\_sync is true. Supports templating | **Optional** | `"default"`, `"{{.nuon.install.id}}-namespace"` | | **`kubernetes_secret_name`**
string | Kubernetes secret name Name of the Kubernetes Secret resource. Required if kubernetes\_sync is true. Supports templating | **Optional** | `"app-secret"`, `"{{.nuon.install.id}}-secret"` | | **`kubernetes_sync_targets`**
[array](#kubernetes_sync_targets) | Kubernetes sync targets List of Kubernetes destinations to sync this secret to. When present, Kubernetes sync is enabled. Each target writes the secret value into a given key of a named secret acro... | **Optional** | - | ### `kubernetes_sync_targets` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------------- | -------------- | ------------------- | | **`namespaces`**
array | target namespaces List of Kubernetes namespaces the secret will be created in. Supports templating | **✅ Required** | `"datadog"` | | **`name`**
string | Kubernetes secret name Name of the Kubernetes Secret resource. Supports templating | **✅ Required** | `"datadog-api-key"` | | **`key`**
string | Kubernetes secret key Key within the Kubernetes Secret to write the value to. Supports templating | **✅ Required** | `"api-key"` | # Secrets Source: https://docs.nuon.co/config-ref/secrets JSON Schema reference for secrets configuration # Secrets ## Properties | Property | Description | Values | Example | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | | **`secret`**
[array](#secret) | list of secrets Array of secret definitions that customers can provide during installation or that are auto-generated | **Optional** | - | ### `secret` | Property | Description | Values | Example | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | | **`name`**
string | secret name Identifier for the secret used to reference it via variable templating (e.g., \{\{.nuon.secrets.name}}). Supports templating | **✅ Required** | `"database_password"`, `"api_key"` | | **`description`**
string | secret description Detailed explanation of what this secret is for, displayed to users during installation | **✅ Required** | `"Master password for the database"`, `"API key for external service authentication"` | | **`display_name`**
string | display name Human-readable name shown in the installer UI. Supports templating | **Optional** | `"Database Password"`, `"API Key"` | | **`required`**
boolean | whether secret is required If true, customer must provide a value during installation. If false, can be skipped | **Optional** | - | | **`auto_generate`**
boolean | whether to auto-generate secret If true, a random secret will be generated if customer does not provide one. Cannot be used with required or default | **Optional** | - | | **`format`**
string | secret format Format of the secret value. Supported values: 'base64' for base64-encoded secrets, or empty for plain text | **Optional** | `"base64"` | | **`default`**
string | default value Default value used if customer does not provide one. Cannot be used with required or auto\_generate | **Optional** | - | | **`kubernetes_sync`**
boolean | sync to Kubernetes If true, the secret will be synced to a Kubernetes Secret resource | **Optional** | - | | **`kubernetes_secret_namespace`**
string | Kubernetes namespace Kubernetes namespace where the secret will be created. Required if kubernetes\_sync is true. Supports templating | **Optional** | `"default"`, `"{{.nuon.install.id}}-namespace"` | | **`kubernetes_secret_name`**
string | Kubernetes secret name Name of the Kubernetes Secret resource. Required if kubernetes\_sync is true. Supports templating | **Optional** | `"app-secret"`, `"{{.nuon.install.id}}-secret"` | | **`kubernetes_sync_targets`**
array | Kubernetes sync targets List of Kubernetes destinations to sync this secret to. When present, Kubernetes sync is enabled. Each target writes the secret value into a given key of a named secret acro... | **Optional** | - | # Stack Source: https://docs.nuon.co/config-ref/stack JSON Schema reference for stack configuration # Stack ## Properties | Property | Description | Values | Example | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------- | | **`name`**
string | stack name Name of the install stack when deployed in the customer account or project. Supports Go templating | **✅ Required** | `"myapp-{{.nuon.install.id}}"`, `"production-stack"` | | **`description`**
string | stack description Description of the install stack. Supports Go templating | **✅ Required** | `"Infrastructure stack for MyApp application"` | | **`type`**
string | stack type Type of infrastructure stack. Supported values: 'aws-cloudformation', 'azure-bicep' (Azure), 'gcp-terraform' (Google Cloud). | **Optional** | `"aws-cloudformation"`, `"azure-bicep"`, `"gcp-terraform"` | | **`vpc_nested_template_url`**
string | VPC nested template URL URL to the CloudFormation nested template for VPC resources | **Optional** | `"https://s3.amazonaws.com/bucket/vpc-template.yaml"` | | **`runner_nested_template_url`**
string | runner nested template URL URL to the CloudFormation nested template for the Nuon runner infrastructure | **Optional** | `"https://s3.amazonaws.com/bucket/runner-template.yaml"` | | **`deployment_scope`**
string | deployment scope Scope the generated install stack root template deploys at. Only supported for 'azure-bicep'. Supported values: 'resource\_group' (the default) confines every resource to the instal... | **Optional** | `"subscription"` | | **`custom_nested_stacks`**
object | custom nested stacks Custom install-stack resources to include. Each entry has a name, template\_url, index, and optional parameters. AWS uses CloudFormation templates, Azure uses compiled ARM JSON,... | **Optional** | - | # Terraform Source: https://docs.nuon.co/config-ref/terraform JSON Schema reference for terraform configuration # Terraform ## Properties | Property | Description | Values | Example | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------ | | **`type`**
string | component type Type of component to deploy. Determines which configuration block is required (helm\_chart, terraform\_module, container\_image, kubernetes\_manifest, or job) | **✅ Required**
`"terraform_module"` | - | | **`name`**
string | component name Unique identifier for the component within the app. Used for referencing in dependencies and templates | **✅ Required** | `"database"`, `"api-server"`, `"frontend"` | | **`terraform_version`**
string | Terraform version Version of Terraform to use for deployments | **✅ Required** | `"1.5.0"`, `"1.6.0"`, `"latest"` | | **`var_name`**
string | variable name for component output Optional name to use when storing component outputs as variables. If not specified, uses the component name | **Optional** | `"db_endpoint"`, `"api_host"` | | **`labels`**
object | Key/value labels used to organize and filter components. Metadata only; does not affect deployment | **Optional** | - | | **`dependencies`**
array | component dependencies List of other components that must be deployed before this component. Automatically extracted from template references | **Optional** | `"database"`, `"infrastructure"` | | **`operation_roles`**
[array](#operation_roles) | operation-specific IAM role assignments Map of component operations to IAM role names. Allows using different roles for different operations (provision, deprovision, update). Roles must be defined ... | **Optional** | - | | **`toggleable`**
boolean | - | **Optional** | - | | **`default_enabled`**
boolean | - | **Optional** | - | | **`kubernetes_context`**
string | kubernetes context binding Optional. Name of a kubernetes\_context defined at the app level. If set, the component targets the cluster produced by that context's source component. If omitted, the co... | **Optional** | `"data-cluster"` | | **`env_vars`**
object | environment variables Map of environment variables passed to Terraform as key-value pairs | **Optional** | - | | **`vars`**
object | Terraform variables Map of Terraform input variables as key-value pairs. Supports templating | **Optional** | - | | **`var_file`**
[array](#var_file) | Terraform variable files Array of external Terraform variable files to load. Each file contents support templating and external file sources: HTTP(S) URLs ([https://example.com/vars.tfvars](https://example.com/vars.tfvars)), git rep... | **Optional** | - | | **`public_repo`**
[PublicRepoConfig](#public_repo) | public repository configuration Configuration for a public repository accessible without authentication | **Optional** | - | | **`connected_repo`**
[ConnectedRepoConfig](#connected_repo) | connected repository configuration Configuration for a private repository connected to the Nuon platform | **Optional** | - | | **`drift_schedule`**
string | drift detection schedule Cron expression for periodic drift detection. If not set, drift detection is disabled. Supports templating | **Optional** | `"0 2 * * *"`, `"*/10 * * * *"` | | **`build_timeout`**
string | build operation timeout Duration string for build operations (e.g., "30m", "1h"). Default: 5m. Max: 1h | **Optional**
Default: `"5m"` | `"30m"`, `"1h"` | | **`deploy_timeout`**
string | deploy operation timeout Duration string for deploy operations (e.g., "30m", "1h"). Default: 60m. Max: 1h | **Optional**
Default: `"60m"` | `"30m"`, `"1h"` | | **`max_auto_retries`**
integer | maximum automatic retry attempts on deploy failure Maximum number of automatic retry attempts for failed deployments. Set to 0 to disable auto-retry. Default: 0 (disabled) | **Optional**
Default: `"0"` | `"3"`, `"5"` | | **`skip_noops`**
boolean | Skip the deploy step when the plan has no changes (a no-op). Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`auto_approve_on_policies_passing`**
boolean | Auto-approve the deploy when all policy checks pass. Defaults to false | **Optional**
Default: `"false"` | `"true"` | | **`var`**
[array](#var) | deprecated: use vars map instead Deprecated: Array of name/value pairs for Terraform variables. Use the vars map instead | **Optional** | - | | **`env_var`**
[array](#env_var) | deprecated: use env\_vars map instead Deprecated: Array of name/value pairs for environment variables. Use the env\_vars map instead | **Optional** | - | ### `operation_roles` | Property | Description | Values | Example | | --------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------ | | **`operation`**
string | operation type Type of operation: provision, deprovision, update, reprovision, or trigger | **✅ Required** | `"provision"`, `"deploy"`, `"deprovision"` | | **`role`**
string | IAM role name Name of the IAM role to use for this operation (not ARN). Role must exist in install stack outputs | **✅ Required** | `"{{.nuon.install.id}}-maintenance"`, `"{{.nuon.install.id}}-provision"` | ### `var_file` | Property | Description | Values | Example | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------- | | **`contents`**
string | variable file contents Contents of a Terraform .tfvars file. Supports Nuon templating and external file sources: HTTP(S) URLs ([https://example.com/vars.tfvars](https://example.com/vars.tfvars)), git repositories (git::[https://githu](https://githu)... | **Optional** | `"./sandbox.tfvars"`, `"./variables/production.tfvars"` | ### `public_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- | | **`repo`**
string | repository URL HTTPS URL to the public Git repository | **✅ Required** | `"https://github.com/user/repo.git"`, `"https://github.com/user/terraform-modules.git"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `connected_repo` | Property | Description | Values | Example | | --------------------------- | -------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------ | | **`repo`**
string | repository identifier Identifier of the connected repository configured in the Nuon platform | **✅ Required** | `"my-repo"`, `"production-infrastructure"` | | **`directory`**
string | directory path Path within the repository to the configuration files | **✅ Required** | `"terraform"`, `"infra/terraform"` | | **`branch`**
string | Git branch Git branch to checkout and use for deployments | **✅ Required** | `"main"`, `"develop"`, `"production"` | ### `var` | Property | Description | Values | Example | | ----------------------- | ------------------------ | ------------ | ------------------------------------------------- | | **`name`**
string | terraform variable name | **Optional** | `"cluster_name"`, `"install_id"`, `"region"` | | **`value`**
string | terraform variable value | **Optional** | `"{{.nuon.install.id}}"`, `"true"`, `"us-west-2"` | ### `env_var` | Property | Description | Values | Example | | ----------------------- | -------------------------- | ------------ | ---------------------------------------------------------- | | **`name`**
string | environment variable name | **Optional** | `"NAMESPACE"`, `"INGRESS_NAME"`, `"LOG_LEVEL"` | | **`value`**
string | environment variable value | **Optional** | `"kube-system"`, `"{{.nuon.install.id}}-public"`, `"info"` | # Configuration Files Source: https://docs.nuon.co/configuration-files Configure apps using TOML configuration files Config files allow you to define a BYOC application using TOML files. Under the hood, Nuon will handle keeping your configuration in sync. ## Create an app **Create a local App directory** - Create a directory and name it after your app. This directory will contain your app's configuration files. ```sh nuon theme={null} mkdir your-app cd your-app ``` Config files are used to sync the configuration for a single app. The app name must be the same as the directory you created above. ```sh nuon theme={null} nuon apps create --name=your-app ``` ## Sync an App To sync an app, and it's configuration run: ```sh nuon theme={null} nuon apps sync ``` ## Language Server Protocol (LSP) Nuon has a Language Server with a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Nuon.nuon-lsp) that provides autocompletion, validation, and inline documentation for Nuon configuration TOML files. The Language Server is a beta feature and included in [Nuon Labs](https://labs.nuon.co/) so please report any bugs or inconsistencies as a [GitHub issue](https://github.com/nuonco/nuon/issues). The Language Server requires the [Nuon CLI](./cli) so please download it first. Future versions of the Language Server will include the CLI. The component type is required after a `#` at the top of each Nuon configuration TOML file. ```toml components/certificate.toml theme={null} # terraform name = "certificate" type = "terraform_module" terraform_version = "1.11.3" ``` [Here are the valid component types](https://github.com/nuonco/nuon/blob/main/pkg/config/schema/types.go#L15) in the Nuon OSS repository. ## Breaking up Config Files Since configs can get complex as your application grows, we follow a structured approach to organize your configuration files. Create a `components` directory in your project root with individual files for each component. e.g., `components/helm_deploy.toml`. ```toml components/helm_deploy.toml theme={null} # helm name = "whoami" type = "helm_chart" chart_name = "whoami" namespace = "whoami" storage_driver = "configmap" [public_repo] repo = "nuonco/demo" directory = "eks-simple/src/components/whoami" branch = "main" [[values_file]] contents = "./whoami.yaml" ``` See our [`example-app-configs` repository](https://github.com/nuonco/example-app-configs) for common directory structures. ## Reference ### Input Define [inputs](/concepts/app-inputs) for an app using `inputs.toml` file. To configure a sensitive `api_key` and `vpc_id` input: ```toml inputs.toml theme={null} # inputs [[input]] name = "vpc_id" description = "vpc_id to install application into" default = "" sensitive = false display_name = "VPC ID" type = "string" [[input]] name = "api_key" description = "API key" default = "" sensitive = true display_name = "API Key" type = "string" ``` Inputs are not required, unless you are using a sandbox or components that require inputs. e.g., `root_domain` for the `aws-eks-sandbox`. ### Sandbox Define the [sandbox](/concepts/sandboxes) using the `sandbox.toml` file. To define an `aws-eks-sandbox` Sandbox: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-sandbox" branch = "main" [vars] cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" [[var_file]] contents = "./sandbox.tfvars" ``` Nuon maintains [managed sandboxes](./concepts/sandboxes#nuon-managed-sandboxes) for the platforms we support. Notice how variables can be defined in the `vars` block and with `var_file`. ### Component Config You can define components by creating `.toml` files in a `components` directory. Components must declare a `type` field and any relevant component-specific configuration. ### Component Dependencies Dependencies can be explicitly defined using the dependencies field in each component’s configuration file. You can specify which components a given component depends on by listing their names in the dependencies array. Learn more about depencencies [here](/guides/component-dependencies). In this example, an application load balancer component requires certificate and whoami helm components are deployed first. ```toml components/alb.toml theme={null} # helm name = "application_load_balancer" type = "helm_chart" chart_name = "application-load-balancer" dependencies = ["certificate", "whoami"] ``` ### Terraform Module Component To define a [terraform module component](/guides/terraform-components) use the `terraform_module` type. To configure a terraform component using a connected repo: ```toml components/terraform.toml theme={null} # terraform name = "terraform" type = "terraform_module" terraform_version = "1.11.3" [connected_repo] directory = "infra" repo = "org/repo" branch = "main" [env_vars] AWS_REGION = "{{.nuon.install.sandbox.account.region}}" [vars] account_id = "{{.nuon.install.sandbox.account.id}}" ``` ### Pulumi Component To define a [pulumi component](/guides/pulumi-components) use the `pulumi` type. To configure a pulumi component using a connected repo: ```toml components/pulumi.toml theme={null} # pulumi name = "pulumi_infra" type = "pulumi" runtime = "go" [connected_repo] directory = "components/pulumi" repo = "org/repo" branch = "main" [config] "aws:region" = "{{.nuon.install_stack.outputs.region}}" [env_vars] PULUMI_SKIP_UPDATE_CHECK = "true" ``` ### Helm Chart Component To define a [helm chart component](/guides/helm-chart-components) use the `helm_chart` type. To configure a helm chart in a connected repo: ```toml components/helm.toml theme={null} # helm name = "helm" type = "helm_chart" chart_name = "chart-name" [connected_repo] directory = "helm" repo = "org/repo" branch = "main" [values] "api.ingresses.public_domain" = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.name}}" ``` ### Kubernetes Manifest Component To define a [kubernetes manifest component](/guides/kubernetes-manifest-components) use the `kubernetes_manifest` type. ```toml manifest-db-secret.toml theme={null} # kubernetes-manifest name = "grafana_secrets" type = "kubernetes_manifest" dependencies = ["grafana_postgres"] namespace = "grafana" manifest = """ # Secret for Grafana's internal database connection apiVersion: v1 kind: Secret metadata: name: grafana-postgres-secret namespace: grafana type: Opaque stringData: GF_DATABASE_URL: "postgres://grafana:grafana@postgresql.grafana.svc.cluster.local:5432/grafana?sslmode=disable&connect_timeout=10" """ ``` ### Container Image Component To define a [container image component](/guides/container-image-components) use the `container_image` type. To configure a public container image: ```toml components/container_image.toml theme={null} # container-image name = "container_image" type = "container_image" [public] image_url = "kennethreitz/httpbin" tag = "latest" ``` To configure a container image from an ECR repo: ```toml components/container_image_ecr.toml theme={null} # container-image name = "container_image_ecr" type = "container_image" [aws_ecr] iam_role_arn = "iam_role_arn" image_url = "ecr-url" tag = "latest" region = "us-west-2" ``` ### Install Configs You can create and manage [installs](/concepts/installs) by creating `.toml` files in an `installs` directory managed separately from the apps. An install must declare a `name` field and any relevant install-specific configuration. Install config files allow you to update multiple installs simultaneously, offering a more streamlined experience than the dashboard. By defining these configurations in code, you can automate your workflow using a CI system like [GitHub Actions](/guides/github-actions). The inputs are defined as a list of input groups. This is just for logical grouping, and inputs from different groups can be defined together. Note that if an input is defined multiple times, the last defined value would be used. Input configs will be grouped together, reflecting the input groups you have defined in the app. Sensitive inputs will be excluded, to avoid saving them in plain text in version control. When syncing install config files, sensitive inputs will be ignored. They can still be managed manually via the dashboard. Currently only AWS installs are supported using install config files. **Generate an Install Config File** To manage existing installs using CI, use the CLI command `nuon installs generate-config` to export an existing install to stdout. Create a TOML config file for the install in the `installs` folder of your app's config and paste the stout. ```bash theme={null} mkdir -p installs touch installs/.toml nuon installs generate-config -i > installs/.toml ``` **Update an Install** The `nuon installs sync` command can be used with a single install config file or with a directory containing multiple install config files. ```bash theme={null} nuon installs sync -a -d ``` In the dashboard, see the newly-created workflow to track the install's upgrade process. **An Example Install Config File** ```toml installs/install.toml theme={null} # install name = "" # configure Auto Approval settings for an install approval_option = "prompt" # set this to "approve-all" to enable auto approval [aws_account] region = "us-east-1" [[inputs]] input_string = "sample" [[inputs]] input_number = "100" input_bool = "true" input_json = '{"key": "value"}' ``` All input values should be defined as strings. They will be parsed into the correct type by Nuon. ### Install Config Syncing You can connect install config files to a Git repository using an `installs.toml` file or the `[installs_config]` section in your app config. When connected, a push to that repo updates every install to match, and a config file naming an install that does not exist yet proposes creating it for approval. ```toml installs.toml theme={null} [connected_repo] repo = "org/repo" branch = "main" directory = "installs" ``` Or in your app config: ```toml nuon.toml theme={null} [installs_config.connected_repo] repo = "org/repo" branch = "main" directory = "installs" ``` For the full property list, see the [Installs Config reference](/config-ref/installs-config). See [Syncing Install Configs from Git](/guides/install-config-syncing) for the full guide. # Dashboard Source: https://docs.nuon.co/dashboard Manage your Nuon apps and installs using the web-based dashboard ## Apps With Nuon, [apps](./concepts/apps) are created and synced to the Nuon control plane with the Nuon [CLI](./cli). Once those steps are completed, apps, their components, and installs, can be administered in the web-based dashboard. Access the dashboard at [https://app.nuon.co](https://app.nuon.co) The default page is a list of apps created in your org. Dashboard - Apps List ## Installs [Installs](./concepts/installs) of apps are accessible from both the left navigation bar or within an app's detail page. Dashboard - Installs List After clicking into an app, there is a `README.md` describing the app and tabs for the components, installs and actions. Dashboard - App Detail After clicking into an install, there is the same app README.md with templated values for the install, inputs, state and tabs for the runner, stack, sandbox, components, actions and workflows. Dashboard - Install Detail The [runner](./concepts/runners) tab shows the real-time status and health of the Nuon runner which communicates from the install’s cloud account to the Nuon control plane for install and maintenance jobs. Dashboard - Install Detail The [components](./concepts/components) tab lists the building blocks of the app such as Helm charts, Kubernetes manifests, Terraform modules, and container images. The tab also shows which build of an app’s component is used and has menu items to re-build and re-deploy components. Dashboard - Install Components [Actions](./concepts/actions) are bash scripts that perform install operations e.g., initialize a database and day-2 operations like debug, perform health checks, and conduct additional operations like break glass emergency repairs. Dashboard - Install Actions [Workflows](./concepts/workflows) are orchestrated steps to perform an app or individual component installation aka provisioning, de-provisioning an install or a component, tear-down, or run actions, the bash scripts discussed earlier. Dashboard - Install Workflows Here is a manual action workflow prompting the Nuon operator for inputs before executing the action script. # Create An AWS EC2 App Source: https://docs.nuon.co/get-started/app-aws-ec2 Learn how to package and install a Cloud Development Environment (CDE) on AWS EC2. This [app](/concepts/apps) can be found in the [cde directory](https://github.com/nuonco/example-app-configs/tree/main/cde) of the example-app-configs repository. The CDE app is a straightforward EC2 VM, but it is a good example of how much Nuon can do beyond basic provisioning. It ships with optional Claude Code support — customers can bring their own Anthropic API key and get a fully configured AI coding environment in their own AWS account, with the key stored as an encrypted SSM SecureString that never leaves their cloud. It also demonstrates several platform features working together: * **Actions** — eleven actions handle everything from post-provision setup (installing Docker, VS Code Web, Claude Code) to day-2 ops (start/stop VM, add SSH keys) to automated healthchecks that run every 5 minutes * **Conditional inputs** — inputs like `install_vscode_web` and `install_claude_code` gate what gets installed; if VS Code Web is disabled, the ALB and ACM certificate are never created; if Claude Code is enabled, the Anthropic API key is injected into the customer's shell environment on first boot * **Policies** — four OPA policies enforce guardrails on the Terraform plan, including blocking independent Elastic IP deletion and warning on oversized instance types * **README templating** — the install page renders a live status view using Go templates that read action outputs, showing VM state, SSH reachability, and active connections without any external monitoring * **Cost controls** — auto-stop cron jobs are installed on the VM at provision time, controlled entirely by install inputs ## Prerequisites * [Signup for the self-service free trial](https://app.nuon.co). You will need a login and an org in Nuon's cloud. * [Set up an AWS account](https://docs.aws.amazon.com/SetUp/latest/UserGuide/setup-overview.html). This is the account you will install the app in, as if you were a customer of the software vendor. ## What You Will Create This tutorial will walk you through creating the following: * An [app](/concepts/apps) * A Terraform [component](/concepts/components) to provision the EC2 instance, Elastic IP, DNS record, and security group * [Actions](/concepts/actions) for post-provision setup and day-2 operations (start/stop VM, healthchecks) * An [install](/concepts/installs), using our [AWS minimal sandbox](/concepts/sandboxes#nuon-managed-sandboxes) We recommend you clone the [`example-app-configs` repository](https://github.com/nuonco/example-app-configs) which includes the `cde` app versus creating each config file manually. This guide is meant to explain the concepts behind the config files, so you can create your own apps in the future. ## Configure App To configure the app, you will create several TOML config files. In each section below we will provide you with configuration snippets for the app itself as well as its components. ### Create App Clone the `example-app-configs` repository, cd into the `cde` directory, and create the app in Nuon. This will create the app in `app.nuon.co`. ```sh theme={null} git clone https://github.com/nuonco/example-app-configs cd example-app-configs/cde nuon auth login nuon orgs select nuon apps create -n cde ``` App List ### Inputs Inputs are values passed into the install at creation time. There are two kinds: * **Vendor inputs** — set by the software vendor in the Nuon dashboard or CLI. These configure app-level defaults like OS choice, instance type, and which optional tools to install. * **Customer inputs** (`user_configurable = true`) — surfaced to the end customer when they apply the install stack. These are entered in the CloudFormation Quick-Create form or the customer portal. Examples include the SSH public key, git identity, and any secrets like the VS Code Web password or Anthropic API key. In the app root directory, notice the file named `inputs.toml`: ```toml inputs.toml theme={null} [[group]] name = "developer" display_name = "Developer Configuration" description = "SSH access and instance sizing for your development environment" [[input]] name = "ssh_public_key" display_name = "SSH Public Key" description = "Paste the contents of your public key file (e.g. cat ~/.ssh/id_rsa.pub)" user_configurable = true group = "developer" [[input]] name = "os" display_name = "Operating System" description = "VM operating system. ubuntu-24.04 (Ubuntu 24.04 LTS, recommended for development, uses apt) or al2023 (Amazon Linux 2023, uses dnf)" default = "ubuntu-24.04" group = "developer" [[input]] name = "instance_type" display_name = "Instance Type" description = "EC2 instance type. t3a.medium (2vCPU/4GB), t3a.large (2vCPU/8GB), t3a.xlarge (4vCPU/16GB), t3a.2xlarge (8vCPU/32GB), m7i.large (2vCPU/8GB consistent), m7i.xlarge (4vCPU/16GB consistent)" default = "t3a.xlarge" group = "developer" [[input]] name = "git_user_name" display_name = "Git User Name" description = "Your full name for git commits (e.g. Jane Smith). Sets git config user.name globally." default = "" user_configurable = true group = "developer" [[input]] name = "git_user_email" display_name = "Git Email" description = "Email address for git commits. Sets git config user.email globally." default = "" user_configurable = true group = "developer" [[group]] name = "optional_tools" display_name = "Optional Tools" description = "Tools installed via the post-provision action. Each can be re-run or updated independently." [[input]] name = "dotfiles_repo" display_name = "Dotfiles Repo URL" description = "HTTPS URL of your dotfiles git repository (e.g. https://github.com/you/dotfiles). If set, the repo is cloned to ~/.dotfiles and install.sh is run after provisioning." default = "" user_configurable = true group = "optional_tools" [[input]] name = "install_docker" display_name = "Install Docker" description = "Install Docker and enable it as a system service (true/false)" default = "false" group = "optional_tools" [[input]] name = "install_vscode_web" display_name = "Install VS Code Web" description = "Install code-server (VS Code in the browser) over HTTPS via ALB and ACM cert (true/false)" default = "false" group = "optional_tools" [[input]] name = "vscode_password" display_name = "VS Code Web Password" description = "Password to access VS Code Web. Set this now so it is ready if you enable VS Code Web later. Not used if Install VS Code Web is false." sensitive = true user_configurable = true group = "optional_tools" [[input]] name = "install_claude_code" display_name = "Install Claude Code" description = "Install the Claude Code CLI (true/false)" default = "false" group = "optional_tools" [[input]] name = "anthropic_api_key" display_name = "Anthropic API Key" description = "Your Anthropic API key for Claude Code (from console.anthropic.com). Only required if Install Claude Code is true." default = "" sensitive = true user_configurable = true group = "optional_tools" [[group]] name = "cost_controls" display_name = "Cost Controls" description = "Automatic shutdown options to prevent idle instances from accumulating charges" [[input]] name = "auto_stop_inactive_hours" display_name = "Inactive Auto-Stop (hours)" description = "Shut down the VM after this many hours with no active SSH or VS Code connections. Leave blank to disable." default = "2" group = "cost_controls" [[input]] name = "auto_stop_max_hours" display_name = "Force Auto-Stop (hours)" description = "Shut down the VM after this many hours of uptime since last start, regardless of activity. Leave blank to disable." default = "4" group = "cost_controls" ``` Two customer inputs have no default and must be provided: `ssh_public_key` (the public key used to access the instance) and `vscode_password` (set it now so it is ready if VS Code Web is enabled later). All other inputs have sensible defaults the customer can accept or override. Cost controls default to shutting the VM down after 2 hours of inactivity and 4 hours of total uptime, keeping idle cloud spend in check. App Inputs ### Sandbox Nuon provides a set of [Nuon Managed Sandboxes](/concepts/sandboxes#nuon-managed-sandboxes) that can be used to provision the infrastructure needed for your app. The `aws-min-sandbox` is a lightweight sandbox for workloads that don't require Kubernetes. It provisions only the minimum shared infrastructure: an ECR repository for container images and Route53 hosted zones for DNS. The VPC, subnets, and NAT gateway are already provided by the install stack — the sandbox and your components build on top of them. In the app root directory, notice the file named `sandbox.toml`: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.4" [public_repo] directory = "." repo = "nuonco/aws-min-sandbox" branch = "main" [vars] enable_nuon_dns = "true" public_root_domain = "{{ .nuon.install.id }}.nuon.run" internal_root_domain = "internal.{{ .nuon.install.id }}.nuon.run" ``` With `enable_nuon_dns` set to `true`, the sandbox creates a Route53 DNS zone for the install. The install id is used as the subdomain, giving each install its own unique `nuon.run` domain. Unlike some other example apps, this sandbox does not require any domain-related inputs — the domain is derived entirely from the install id. ### Components Components are the building blocks of your app. This app has a single component: a Terraform module that provisions the EC2 instance along with its Elastic IP, Route53 A record, and security group. If VS Code Web is enabled, the component also creates an ALB and ACM certificate. In the app root directory, navigate to the `components` directory. Notice the single file `1-ec2.toml`: ```toml 1-ec2.toml theme={null} # terraform name = "ec2" type = "terraform_module" terraform_version = "1.11.4" max_auto_retries = 5 [public_repo] repo = "nuonco/example-app-configs" directory = "cde/src/components/ec2" branch = "main" [vars] install_id = "{{.nuon.install.id}}" region = "{{.nuon.sandbox.outputs.account.region}}" subnet_id_0 = "{{index .nuon.sandbox.outputs.vpc.public_subnet_ids 0}}" subnet_id_1 = "{{index .nuon.sandbox.outputs.vpc.public_subnet_ids 1}}" vpc_id = "{{.nuon.sandbox.outputs.vpc.id}}" dns_zone_id = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.zone_id}}" dns_zone_name = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.name}}" os = "{{.nuon.install.inputs.os}}" instance_type = "{{.nuon.install.inputs.instance_type}}" ssh_public_key = "{{.nuon.install.inputs.ssh_public_key}}" install_vscode_web = "{{.nuon.install.inputs.install_vscode_web}}" anthropic_api_key = "{{.nuon.install.inputs.anthropic_api_key}}" ``` Notice how the component vars pull from two sources: sandbox outputs (VPC IDs, subnet IDs, DNS zone) and install inputs (OS, instance type, SSH key). This templating pattern is how Nuon passes context from the sandbox and the customer's choices into your Terraform modules. Terraform Component ### Actions [Actions](/concepts/actions) are scripts that the runner executes on demand or on a schedule. They are how you handle day-2 operations — setup, maintenance, and monitoring — without requiring the customer to SSH into the instance themselves. This app includes the following actions: * `post_provision_setup` — runs automatically after provisioning and is also re-runnable manually. Installs Docker, VS Code Web, and Claude Code based on the install inputs, configures git user name and email, and sets up the auto-stop cron jobs on the VM. * `install_dotfiles` — clones the customer's dotfiles repo to `~/.dotfiles` and runs `install.sh`. Triggered automatically after provision and re-runnable at any time from the portal. * `add_ssh_key` — appends an additional SSH public key to `~/.ssh/authorized_keys`. Manual only. * `start_dev_env` / `stop_dev_env` — start or stop the VM to control costs. Manual. * `healthcheck_ec2`, `healthcheck_ssh`, `healthcheck_code_server`, `healthcheck_alb`, `connections_status` — run on a 5-minute cron and report their results in the install README. Actions ### Policies [Policies](/concepts/policies) let the vendor enforce guardrails on Terraform plans before they are applied to the customer's account. This app ships four OPA policies scoped to the `ec2` component: two warnings and two hard denies. * `warn-large-instance-type` — warns if the instance type is larger than `medium`, prompting the vendor to confirm the cost is intentional. * `warn-ssh-open-to-world` — warns if the SSH security group rule is open to `0.0.0.0/0`, flagging a potential overly permissive configuration. * `deny-eip-deletion` — blocks deletion of the Elastic IP unless the EC2 instance is also being destroyed in the same plan, preserving DNS stability. * `deny-instance-type-change` — blocks in-place instance type changes, which would require a stop/start and could cause unexpected downtime. policies ### Security The CDE app is designed so that the customer's data never leaves their AWS account and the vendor has no network access to the instance. A few specifics worth understanding: * **SSH key auth only.** Password authentication is disabled at provision time. The public key the customer provides at install time is the only authorized key. * **Minimal inbound surface.** The security group allows only TCP:22. If VS Code Web is enabled, HTTPS:443 is also opened via an ALB — but the security group only allows ALB traffic to reach port 8080 on the instance, not direct inbound access. Post-provision setup (Docker, VS Code Web, Claude Code) is delivered by the runner via AWS SSM Run Command, an outbound-only control channel, so no additional ports need to be opened for provisioning. * **VS Code Web is TLS-only and password-protected.** If enabled, code-server runs on port 8080 inside the VPC. The ALB terminates HTTPS with an ACM-managed certificate, and only ALB traffic is allowed to reach the port — direct inbound access is blocked. * **Anthropic API key is stored as an SSM SecureString.** The key is encrypted at rest with AWS KMS in the customer's account. The EC2 instance profile is granted least-privilege access to read only its own parameter path. The vendor never sees the key. * **Runner permissions are bounded.** The runner's IAM role has a permissions boundary scoped to only the AWS services this app requires: `ec2`, `iam`, `ssm`, `elasticloadbalancing`, `acm`, and `route53`. It cannot access other resources in the customer's account. ### Sync App to Nuon You now have a complete Nuon app. This is a good place to stop and sync it to Nuon. Make sure you are in the root directory of your app, then run: ```sh theme={null} nuon apps sync ``` Select the app in the dashboard, and you should now see the updated inputs, sandbox, components, and actions. App ## Create an Install Click the Create Install button in the top right corner of the app page in the Dashboard. Give your install a name and choose an AWS Region. Notice the inputs you defined in the `inputs.toml` file are displayed here. Two fields require values from the customer: `ssh_public_key` (paste the output of `cat ~/.ssh/id_rsa.pub`) and `vscode_password` (set it now so it is ready if you enable VS Code Web later). You can accept the defaults for all other inputs. Click the Create Install button at the bottom of the page to start the workflow. The provision workflow generates two [install stack](/concepts/stacks) formats — a CloudFormation Quick-Create / CLI snippet, and Terraform `inputs.auto.tfvars` and `secrets.auto.tfvars` files for the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module — so the customer can apply whichever fits their tooling. Create Install ### Monitoring Installs As soon as you kick off the install provisioning, you should see the new install's workflow in the dashboard. Install workflow ### Apply the Install Stack in AWS In this step, you are switching personas, from the software vendor to the end customer, authorizing the install of the app in your cloud account. Apply the install stack using whichever format fits your workflow. Either way, the resulting resources are the same: a VPC, the IAM policies Nuon expects, an Autoscaling Group, and a VM running the Nuon Build Runner that will provision the install of your app. #### Option A: CloudFormation Click or copy the CloudFormation Quick-Create link to open it in your AWS account and log in. The form will include the customer inputs defined in `inputs.toml` — required fields like `ssh_public_key` and `vscode_password` are marked as required and must be filled in before the stack can be created. Scroll to the bottom, accept the IAM acknowledgement, and click **Create Stack**. #### Option B: Terraform Download the generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files from the dashboard, set up a `backend.tf` (snippet provided in the dashboard), and run: ```sh theme={null} terraform init terraform apply ``` against the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module. ### Monitor the Install Stack creation in AWS Monitor the stack creation in the AWS console (CloudFormation) or the Terraform output. This will take a few minutes to complete. You can also pull up the AWS EC2 console and see the EC2 VM appear at some point with the install id in its name. The Nuon Dashboard will not provide feedback until the runner is up and connected to Nuon. ### Monitor the Remainder of the Install Workflow If plan steps require approvals, you will need to approve them in the dashboard. You can also monitor the progress of the install in the dashboard. Workflow ### Connect to Your Development Environment When the install has provisioned and the deploys have completed, open the install page in the dashboard. The README tab shows the connection strings for your instance, populated with the actual hostname and SSH user once provisioning is complete. **SSH:** ```sh theme={null} ssh @ ``` SSH **VS Code (Remote - SSH extension):** Open the [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) extension, then `Cmd+Shift+P` → `Remote-SSH: Connect to Host` → enter the same `@`. VS Code **Zed:** ```sh theme={null} zed ssh://@ ``` Zed **VS Code Web** (if you enabled it): VS Code Web The README will include a direct HTTPS link to the ALB. Open it in a browser and enter the password you set in the `vscode_password` input. README ## Observability The install page displays a live README rendered from `README.md` in the app root. It uses Go templating to read action outputs and display the current state of the VM and its services. The five healthcheck actions — `healthcheck_ec2`, `healthcheck_ssh`, `healthcheck_code_server`, `healthcheck_alb`, and `connections_status` — run on a 5-minute cron via the runner and write their results to action outputs. The README reads those outputs through `{{.nuon.actions.workflows.*}}` template variables to show a per-service status summary, active SSH session count, and connected client IPs. No additional monitoring infrastructure is needed — the runner handles execution via SSM and the results surface directly on the install page. ## Cost Controls Two automatic shutdown mechanisms are installed as cron jobs by `post_provision_setup`, controlled by install inputs: * **Inactive auto-stop** (`auto_stop_inactive_hours`, default 2h) — monitors active SSH and VS Code Web connections every 10 minutes and shuts down the VM after N consecutive hours with no active sessions. * **Force auto-stop** (`auto_stop_max_hours`, default 4h) — shuts down the VM after N hours of total uptime since last start, regardless of activity. Both can be disabled by leaving the input blank. The `stop_dev_env` and `start_dev_env` actions provide manual control. The Elastic IP and Route53 DNS record persist through stop/start cycles, so the SSH hostname never changes. ## Deprovision the Install Nuon is mindful of your public cloud spend, so provide the following deprovisioning steps. Deprovisioning the install is a two-step process. First, deprovision the install in the Nuon dashboard, which will tear down the EC2 instance and all associated resources. Once that is completed successfully, tear down the install stack — delete the CloudFormation stack in the AWS console, or run `terraform destroy` against the install module — to remove the runner by destroying the EC2 VM, ASG, and VPC. Be sure to back up any data you want to keep before deprovisioning the install, as this will delete all resources created by the install. ## Manually Deprovision the Install If deprovisioning the install in the dashboard fails, tearing down the install stack — deleting the CloudFormation stack in AWS, or running `terraform destroy` against the install module — will delete all of the component resources and the VPC. ## Wrapping Up and Next Steps Congratulations, you just deployed a Cloud Development Environment to AWS! A few suggestions for where to go next: * Review other example apps in the [example-app-configs repository](https://github.com/nuonco/example-app-configs) to see how to deploy a more complex app. * Dig into our [app](/concepts/apps) guide to learn how to configure more complex apps. # Create an AWS EKS App Source: https://docs.nuon.co/get-started/app-aws-k8s Learn how to package and deploy a Helm app to AWS EKS. This [app](/concepts/apps) can be found in the [eks-simple directory](https://github.com/nuonco/example-app-configs/tree/main/eks-simple) of the example-app-configs repository. ## Prerequisites * [Signup for the self-service free trial](https://app.nuon.co). You will need a login and an org in Nuon's cloud. * [Set up an AWS account](https://docs.aws.amazon.com/SetUp/latest/UserGuide/setup-overview.html). This is the account you will install the app in. ## What You Will Create This tutorial will walk you through creating the following: * An [App](/concepts/apps) * A [Whoami component](/concepts/components) * An [Install](/concepts/installs), using our [AWS EKS sandbox](/concepts/sandboxes#nuon-managed-sandboxes) We recommend you clone the [`example-app-configs` repository](https://github.com/nuonco/example-app-configs) which includes the `eks-simple` app versus creating each config file manually. This guide is meant to explain the concepts behind some of the config files, so you can create your own apps in the future. ## Configure App To configure the app, you will create several TOML config files. In each section below we will provide you with configuration snippets for the app itself as well as it's components. ### Create App Clone the `example-app-configs` repository, cd into the `eks-simple` directory, and create the app in Nuon. This will create the app in `app.nuon.co` ```sh theme={null} git clone https://github.com/nuonco/example-app-configs cd example-app-configs/eks-simple nuon auth login nuon orgs select nuon apps create -n eks-simple ``` You should see the new app in the dashboard. App List ### Inputs Inputs are customer-specific configs that are entered when you install the app in the customer's cloud account. They will be displayed in the dashboard. Inputs are optional. In the app root directory, create a file named `inputs.toml` and add the following: ```toml inputs.toml theme={null} # inputs [[group]] name = "dns" description = "DNS Configrations" display_name = "Configurations for the root domain for Route53" [[input]] name = "domain" description = "domain for the whoami endpoint e.g., nuon.run" default = "nuon.run" display_name = "Domain" group = "dns" [[input]] name = "sub_domain" description = "The sub domain for the Whoami service" default = "whoami" display_name = "Sub Domain" group = "dns" ``` This Input defines a domain, which will default to nuon.run and a subdomain for the Whoami service that will be deployed in the customer's AWS account. The customer will be prompted to enter this value when they create an install of the app. The subdomain will default to `whoami`, but the customer can change it to whatever they want. ### Sandbox Nuon provides a set of [Nuon Managed Sandboxes](/concepts/sandboxes#nuon-managed-sandboxes) that can be used to provision the infrastructure needed for your app. The `aws-eks-sandbox` will provide everything you need to run an EKS, from the EKS cluster down to the VPC. Your app references these Sandboxes in the `sandbox.toml` file. In the app root directory, create a file named `sandbox.toml` and add the following: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-sandbox" branch = "main" [vars] cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.install.id }}.{{.nuon.inputs.inputs.domain}}" internal_root_domain = "internal.{{ .nuon.install.id }}.{{.nuon.inputs.inputs.domain}}" [[var_file]] contents = "./sandbox.tfvars" ``` With `enable_nuon_dns` set to `true`, the sandbox will create a Route53 DNS zone for the install, allowing you to access the services deployed in the customer's AWS account using a Nuon-managed domain. The config uses the install id as the subdomain, which will be unique for each install. The sandbox expects variables, some of which are defined in the `vars` section while others are defined in the `sandbox.tfvars` file. In the app root directory, create a file named `sandbox.tfvars` and add the following: ```hcl sandbox.tfvars theme={null} maintenance_role_eks_access_entry_policy_associations = { eks_admin = { policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy" access_scope = { type = "cluster" } } eks_view = { policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" access_scope = { type = "cluster" } } } additional_namespaces = ["whoami"] maintenance_cluster_role_rules_override = [{ "apiGroups" = ["*"] "resources" = ["*"] "verbs" = ["*"] }] min_size = 2 max_size = 3 desired_size = 2 ``` ### Components Components are the building blocks of your App and where a software vendor's application is installed on top of the Sandbox infrastructure. In this example, we will create a component to deploy Traefik's [Whoami](https://github.com/traefik/whoami) service with Helm. Whoami is a Tiny Go webserver that prints OS information and HTTP request headers. In the app root directory, create directory called `components` and within it, a file named `whoami.toml` and add the following: ```toml whoami.toml theme={null} # helm name = "whoami" type = "helm_chart" chart_name = "whoami" namespace = "whoami" storage_driver = "configmap" [public_repo] repo = "nuonco/example-app-configs" directory = "eks-simple/src/components/whoami" branch = "main" [[values_file]] contents = "./values/whoami.yaml" ``` You will also need ALB and Certificate components to expose the Whoami service to the internet. In the `components` directory, create a file named `alb.toml` and add the following: ```toml alb.toml theme={null} # helm name = "application_load_balancer" type = "helm_chart" chart_name = "application-load-balancer" dependencies = ["whoami"] [public_repo] repo = "nuonco/components" directory = "aws/alb" branch = "main" [values] domain_certificate = "{{.nuon.components.certificate.outputs.public_domain_certificate_arn}}" domain = "{{.nuon.inputs.inputs.sub_domain}}.{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.name}}" https_port = "443" service_name = "whoami" service_port = "80" install_name = "{{.nuon.install.id}}" ``` In the components directory, create a file named `certificate.toml` and add the following: ```toml certificate.toml theme={null} # terraform name = "certificate" type = "terraform_module" terraform_version = "1.11.3" [public_repo] repo = "nuonco/example-app-configs" directory = "eks-simple/src/components/certificate" branch = "main" [vars] install_id = "{{ .nuon.install.id }}" region = "{{ .nuon.install_stack.outputs.region }}" zone_id = "{{ .nuon.install.sandbox.outputs.nuon_dns.public_domain.zone_id }}" domain_name = "*.{{ .nuon.install.sandbox.outputs.nuon_dns.public_domain.name }}" ``` ### Actions [Actions](/concepts/actions) are used to perform operations on your app, such as healthchecks or running `kubectl` commands. In the app root directory, create directory called `actions` and within it, two actions. Create a file named `healthcheck.toml` and add the following to check the ALB status. See the eks-simple directory of the example repo for the `healthcheck.sh` script referenced below. ```toml healthcheck.toml theme={null} # action name = "alb_healthcheck" timeout = "30s" [[triggers]] type = "manual" [[steps]] name = "alb-healthcheck" command = "./healthcheck.sh" [steps.public_repo] repo = "nuonco/example-app-configs" directory = "eks-simple/src/actions/alb" branch = "main" [steps.env_vars] INGRESS_NAME = "{{.nuon.install.id}}-public" INGRESS_NAMESPACE = "whoami" ``` ### Other Configuration Files Look at the `eks-simple` app in the [`example-app-configs` repository](https://github.com/nuonco/example-app-configs) for the remaining and required directory structure and files. The files created above are marked in \*\*. ``` - **eks-simple** - **actions** - **healthcheck.toml** - **simple_action.toml** - **components** - **alb.toml** - **certificate.toml** - **whoami.toml** - permissions - deprovision_boundary.json - deprovision.toml - maintenance_boundary.json - maintenance.toml - provision_boundary.json - provision.toml - policies - disallow-ingress-nginx-custom-snippets.yml - set-karpenter-non-cpu-limits.yaml - src - components - whoami - templates - deployment.yaml - service.yaml - Chart.yaml - break_glass.toml - **inputs.toml** - installer.toml - metadata.toml - policies.toml - README.md - runner.toml - **sandbox.tfvars** - **sandbox.toml** - secrets.toml - stack.toml ``` ### Sync App to Nuon You now have a complete Nuon app. This is a good place to stop and sync it to Nuon. Make sure you are in the root directory of your app, then run: ```sh theme={null} nuon apps sync ``` Select the app in the dashboard, and you should now see the updated inputs, sandbox, components, and runner configuration. App ## Create an Install Click the Create Install button in the top right corner of the app page in the Dashboard. Give your install a name choosethe AWS Region. Notice the inputs you defined in the `inputs.toml` file are displayed here, allowing the customer to enter their own values. After entering the inputs, click the Create Install button at the bottom of the page to start the Workflow. The provision workflow generates two install stack formats — a CloudFormation Quick-Create / CLI snippet, and Terraform `inputs.auto.tfvars` and `secrets.auto.tfvars` files for the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module — so the customer can apply whichever fits their tooling. Create Install ### Monitoring Installs As soon as you kick off the install provisioning, you should see the new install's workflow in the dashboard. Install List ### Apply the Install Stack in AWS Apply the install stack using whichever format fits your workflow. Either way, the resulting resources are the same: a VPC, the IAM policies Nuon expects, an Autoscaling Group, and a VM running the Nuon Build Runner that will provision the install of your app. #### Option A: CloudFormation Click or copy the CloudFormation Quick-Create link to open it in your AWS account, log in, scroll to the bottom, accept the defaults, and click **Create Stack**. CloudFormation Stack #### Option B: Terraform Download the generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files from the dashboard, set up a `backend.tf` (snippet provided in the dashboard), and run: ```sh theme={null} terraform init terraform apply ``` against the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module. ### Monitor the Install Stack creation in AWS Monitor the stack creation in the AWS console (CloudFormation) or the Terraform output. This takes a few minutes to complete. You can also pull up the AWS EC2 console and see the EC2 VM appear with the install id in its name. The Nuon Dashboard will not provide feedback until the runner is up and connected to Nuon. Stack Log ### Monitor the Remainder of the Install Workflow If plan steps require approvals, you will need to approve them in the dashboard. You can also monitor the progress of the install in the dashboard. Workflow In the AWS console, you can see the EKS cluster being created. Cluster In the AWS console, you can see the Application Load Balancer being created and the Target Group being created for the Whoami service. ALB ### Inspect the Install When the install has provisioned, and the deploys have completed, click the URL link in the install's README.md visible on the install page in the dashboard. Alternatively, copy the link and open a terminal and curl the API to verify it's running. ```sh theme={null} curl https://whoami.{install_id}.nuon.run ``` You should see a response similar to the following: ```bash theme={null} Hostname: whoami-78ffb6cbf9-rhtlb IP: 127.0.0.1 IP: ::1 IP: 10.128.130.230 IP: fe80::1076:d7ff:fe3c:3bab RemoteAddr: 10.128.0.10:11270 GET / HTTP/1.1 Host: whoami.inlgekffpkqv08yy8ayt7mxuyy.nuon.run User-Agent: curl/8.7.1 Accept: */* X-Amzn-Trace-Id: Root=1-6864407f-5e2b57f16533e41347ae8c8c X-Forwarded-For: 136.49.24.124 X-Forwarded-Port: 443 ``` ## Deprovision the Install Nuon is mindful of your public cloud spend, so we provide the following deprovisioning steps. Deprovisioning the Install is a two-step process. First, you need to deprovision the install in the Nuon dashboard, which will deprovision the resources to install your App. Once that is completed successfully, tear down the install stack — delete the CloudFormation stack in the AWS console, or run `terraform destroy` against the install module — to remove the Build Runner by destroying the EC2 VM, ASG, and VPC. Be sure to back up any data you want to keep before deprovisioning the install, as this will delete all resources created by the Install. ## Manually Deprovision the Install If deprovisioning the install in the dashboard fails, there is a `error-destroy.sh` script in the [Nuon Managed Sandboxes](/concepts/sandboxes#nuon-managed-sandboxes) repository that you can use to manually deprovision the install. This script will remove all resources created by the install, including the EKS cluster, ALB, certificates, and Route53 DNS records. You still have to tear down the install stack — delete the CloudFormation stack in the AWS console, or run `terraform destroy` against the install module — after running this script to remove the runner, ASG, EC2 VM, and VPC. ## Wrapping Up and Next Steps Congratulations, you just deployed an app to AWS! A few suggestions for where to go next: * Review other example apps in the [example-app-configs repository](https://github.com/nuonco/example-app-configs) to see how to deploy a more complex app. * Dig into our [app](/concepts/apps) guide to learn how to configure more complex apps. # Create An AWS Lambda App Source: https://docs.nuon.co/get-started/app-aws-lambda Learn how to package and install an AWS Lambda app. This [app](/concepts/apps) can be found in the [aws-lambda directory](https://github.com/nuonco/example-app-configs/tree/main/aws-lambda) of the example-app-configs repository. ## Prerequisites * [Signup for the self-service free trial](https://app.nuon.co). You will need a login and an org in Nuon's cloud. * [Set up an AWS account](https://docs.aws.amazon.com/SetUp/latest/UserGuide/setup-overview.html). This is the account you will install the app in. ## What You Will Create This tutorial will walk you through creating the following: * An [app](/concepts/apps) * Several [components](/concepts/components) including a Lambda function, Docker image, DynamoDB table, Certificate and API Gateway * An [install](/concepts/installs), using our [AWS minimal sandbox](/concepts/sandboxes#nuon-managed-sandboxes) We recommend you clone the [`example-app-configs` repository](https://github.com/nuonco/example-app-configs) which includes the `aws-lambda` app versus creating each config file manually. This guide is meant to explain the concepts behind some of the config files, so you can create your own apps in the future. ## Configure App To configure the app, you will create several TOML config files. In each section below we will provide you with configuration snippets for the app itself as well as it's components. ### Create App Clone the `example-app-configs` repository, cd into the `aws-lambda` directory, and create the app in Nuon. This will create the app in `app.nuon.co` ```sh theme={null} git clone https://github.com/nuonco/example-app-configs cd example-app-configs/aws-lambda nuon auth login nuon orgs select nuon apps create -n aws-lambda ``` You should see the new app in the dashboard. App List ### Inputs Inputs are customer-specific configs that are entered when you install the App in the customer's cloud account. They will be displayed in the dashboard. Inputs are optional. In the app root directory, notice the file named `inputs.toml`: ```toml inputs.toml theme={null} # inputs [[group]] name = "dns" description = "DNS Configrations" display_name = "Configurations for the root domain for Route53" [[input]] name = "domain" description = "domain for the AWS API Gateway e.g., nuon.run or stage.nuon.run" default = "nuon.run" display_name = "Domain" group = "dns" [[input]] name = "sub_domain" description = "The sub domain for the AWS API Gateway" default = "api" display_name = "Sub Domain" group = "dns" ``` This input defines a domain, which will default to the install id as subdomain prepended to `nuon.run` and a second-level subdomain for the API Gateway service that will be deployed in the customer's AWS account. The customer will be prompted to enter these values when they create an install of the app. For this tutorial, you can accept the defaults. ### Sandbox Nuon provides a set of [Nuon Managed Sandboxes](/concepts/sandboxes#nuon-managed-sandboxes) that can be used to provision the infrastructure needed for your app. The `aws-min-sandbox` is very streamlined compared to the Kubernetes sandboxes so will provide DNS delegation needed for nuon.run. Your app references these Sandboxes in the `sandbox.toml` file. In the app root directory, notice the file named `sandbox.toml`: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.4" [public_repo] directory = "." repo = "nuonco/aws-min-sandbox" branch = "main" [vars] enable_nuon_dns = "true" public_root_domain = "{{ .nuon.install.id }}.{{.nuon.inputs.inputs.domain}}" internal_root_domain = "internal.{{ .nuon.install.id }}.{{.nuon.inputs.inputs.domain}}" [[var_file]] contents = "./sandbox.tfvars" ``` With `enable_nuon_dns` set to `true`, the sandbox will create a Route53 DNS zone for the install, allowing you to access the services deployed in the customer's AWS account using a Nuon-managed `nuon.run` domain. The config uses the install id as the subdomain, which will be unique for each install. ### Components Components are the building blocks of your app and where a software vendor's application is installed on top of the sandbox infrastructure. In this example, there are several components that make up the AWS Lambda app, including: * A Docker image with a Dockerfile and Go app stored in ECR * A DynamoDB table to store data * A Lambda function to process requests * An API Gateway to expose the Lambda function to the internet * A Certificate to secure the API Gateway In the app root directory, navigate to the `components` directory. Notice there are several files here, one for each component. Numbers are prefixed to the file names to easily show the dependency order. The Docker build component creates a container image including the Go application code and pushes it to ECR. ```toml 0-docker-image.toml theme={null} # docker-build name = "docker_image" type = "docker_build" dockerfile = "Dockerfile" [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/api" branch = "main" ``` The DynamoDB component creates a DynamoDB table using Terraform to store data from the Lambda function POST route. ```toml 1-dynamodb-table.toml theme={null} # terraform name = "dynamodb_table" type = "terraform_module" terraform_version = "1.11.4" [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/dynamodb-table" branch = "main" [vars] name = "widgets-{{.nuon.install.id}}" hash_key = "ID" install_id = "{{.nuon.install.id}}" region = "{{.nuon.install_stack.outputs.region}}" ``` The Lambda component creates a Lambda function using Terraform that references the Docker image in ECR. ```toml 2-lambda-function.toml theme={null} # terraform name = "lambda_function" type = "terraform_module" terraform_version = "1.11.4" dependencies = ["dynamodb_table"] [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/lambda-function" branch = "main" [vars] install_id = "{{.nuon.install.id}}" region = "{{.nuon.sandbox.outputs.account.region}}" function_name = "widgets-{{.nuon.install.id}}" image_uri = "{{.nuon.components.docker_image.outputs.image.repository}}:{{.nuon.components.docker_image.outputs.image.tag}}" dynamodb_table_arn = "{{.nuon.components.dynamodb_table.outputs.dynamodb_table_arn}}" ``` The certificate component creates a Certificate using Terraform for the API Gateway to use. ```toml 3-certificate.toml theme={null} # terraform name = "certificate" type = "terraform_module" terraform_version = "1.11.4" [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/certificate" branch = "main" [vars] zone_id = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.zone_id}}" domain_name = "*.{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.name}}" install_id = "{{.nuon.install.id}}" region = "{{.nuon.install_stack.outputs.region}}" # https://registry.terraform.io/modules/terraform-aws-modules/acm/aws/latest ``` The API Gateway component creates an API Gateway using Terraform that exposes the Lambda function to the internet. Notice how the certificate, Route53 zone, and Lambda function are all referenced in this component through template variables. ```toml 4-api-gateway.toml theme={null} # terraform name = "api_gateway" type = "terraform_module" terraform_version = "1.11.4" [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/api-gateway" branch = "main" [vars] install_id = "{{.nuon.install.id}}" region = "{{.nuon.install_stack.outputs.region}}" name = "{{.nuon.inputs.inputs.sub_domain}}" domain_name = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.name }}" domain_name_certificate_arn = "{{.nuon.components.certificate.outputs.public_domain_certificate_arn}} " lambda_function_arn = "{{.nuon.components.lambda_function.outputs.lambda_function.lambda_function_arn}}" zone_id = "{{.nuon.install.sandbox.outputs.nuon_dns.public_domain.zone_id}}" ``` ### Sync App to Nuon You now have a complete Nuon app. This is a good place to stop and sync it to Nuon. Make sure you are in the root directory of your app, then run: ```sh theme={null} nuon apps sync ``` Select the app in the dashboard, and you should now see the updated inputs, sandbox, components, and runner configuration. App ## Create an Install Click the Create Install button in the top right corner of the app page in the Dashboard. Give your install a name choose the AWS Region. Notice the inputs you defined in the `inputs.toml` file are displayed here, allowing the customer to enter their own values. Just accept the defaults for this tutorial. Click the Create Install button at the bottom of the page to start the Workflow. The provision workflow generates two install stack formats — a CloudFormation Quick-Create / CLI snippet, and Terraform `inputs.auto.tfvars` and `secrets.auto.tfvars` files for the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module — so the customer can apply whichever fits their tooling. Create Install ### Monitoring Installs As soon as you kick off the install provisioning, you should see the new install's workflow in the dashboard. Install List ### Apply the Install Stack in AWS In this step, you are switching personas, from the software vendor, to the end customer, authorizing the install of the app in your cloud account. Apply the install stack using whichever format fits your workflow. Either way, the resulting resources are the same: a VPC, the IAM policies Nuon expects, an Autoscaling Group, and a VM running the Nuon Build Runner that will provision the install of your app. #### Option A: CloudFormation Click or copy the CloudFormation Quick-Create link to open it in your AWS account, log in, scroll to the bottom, accept the defaults, and click **Create Stack**. CloudFormation Stack #### Option B: Terraform Download the generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files from the dashboard, set up a `backend.tf` (snippet provided in the dashboard), and run: ```sh theme={null} terraform init terraform apply ``` against the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module. ### Monitor the Install Stack creation in AWS Monitor the stack creation in the AWS console (CloudFormation) or the Terraform output. This will take a few minutes to complete. You can also pull up the AWS EC2 console and see the EC2 VM appear at some point with the install id in its name. The Nuon Dashboard will not provide feedback until the runner is up and connected to Nuon. Stack Log ### Monitor the Remainder of the Install Workflow If plan steps require approvals, you will need to approve them in the dashboard. You can also monitor the progress of the install in the dashboard. Workflow ### Inspect the Install When the install has provisioned, and the deploys have completed, click the URL link in the Install's README.md visible on the install page in the dashboard. Alternatively, copy the link and open a terminal and curl the API to verify it's running. README #### Create a Widget record in DynamoDB via a Lambda function ```sh theme={null} curl -X POST https://.nuon.run/widgets \ -H Content-Type:"application/json" \ -d '{"id":"7"}' ``` The Go app called by the Lambda function will not return anything when it successfully POSTs the value of 7 to the DynamoDB table called widgets. #### Retrieve a Widget record from DynamoDB via a Lambda function Verify the widget record was created with the GET request: ```bash theme={null} curl https://.nuon.run/widgets/7 ``` You should see the following response: ```json theme={null} { "id": "7" } ``` You can also verfify the POST and GET requests in the CloudWatch logs for the Lambda function in the AWS console. CloudWatch ## Deprovision the Install Nuon is mindful of your public cloud spend, so provide the following deprovisioning steps. Deprovisioning the install is a two-step process. First, you need to deprovision the install in the Nuon dashboard, which will deprovision the resources to install your app. Once that is completed successfully, tear down the install stack — delete the CloudFormation stack in the AWS console, or run `terraform destroy` against the install module — to remove the runner by destroying the EC2 VM, ASG, and VPC. Deprovision Be sure to back up any data you want to keep before deprovisioning the install, as this will delete all resources created by the install. ## Manually Deprovision the Install If deprovisioning the install in the dashboard fails, tearing down the install stack — deleting the CloudFormation stack in AWS, or running `terraform destroy` against the install module — will delete all of the component resources and the VPC. ## Wrapping Up and Next Steps Congratulations, you just deployed an app to AWS! A few suggestions for where to go next: * Review other example apps in the [example-app-configs repository](https://github.com/nuonco/example-app-configs) to see how to deploy a more complex app. * Dig into our [app](/concepts/apps) guide to learn how to configure more complex apps. # Roll out a change Source: https://docs.nuon.co/get-started/app-branches-walkthrough Sync the eks-simple example app's built-in branch config, then plan and deploy a change across its installs. This walkthrough builds on the [`eks-simple` directory](https://github.com/nuonco/example-app-configs/tree/main/eks-simple) of the example-app-configs. [App branches](/concepts/app-branches) turn a change to your app config into a single, reviewable rollout across your installs. `eks-simple` already ships its branch config. This walkthrough syncs it into your org and takes a change through it end to end: preview the diff, plan each deployment group, approve, deploy. ## Prerequisites * Finish [Create an AWS EKS App](/get-started/app-aws-k8s) first, or otherwise have the `eks-simple` app synced to Nuon with **at least one install** that has finished provisioning. * The [Nuon CLI](/cli), authenticated with `nuon auth login` and pointed at your org with `nuon orgs select`. * A clone of [`example-app-configs`](https://github.com/nuonco/example-app-configs). ## What You Will Create * An [app branch](/concepts/app-branches) named `main` **in your org**, created by syncing the branch config that ships in the `eks-simple` directory of the example repository. * Two [deployment groups](/concepts/app-branches#deployment-groups) named `stage` and `production`, which select their installs by label. * A labeled install, so it lands in the `stage` group. * A plan-only preview run, and then a real run you approve group by group. ## Review the Branch Config ### `branch.toml` The branch config is a single file at the root of the app config directory, alongside `metadata.toml`. `eks-simple` already includes it — open [`eks-simple/branch.toml`](https://github.com/nuonco/example-app-configs/blob/main/eks-simple/branch.toml) in your clone and read it against what follows: ```toml branch.toml theme={null} name = "main" [public_repo] directory = "eks-simple" repo = "nuonco/example-app-configs" branch = "main" [[install_groups]] name = "stage" order = 1 [install_groups.label_selector] env = "stage" [[install_groups]] name = "production" order = 2 [install_groups.label_selector] env = "prod" ``` Reading it top to bottom: * **`name = "main"`** is the branch's name in Nuon. It comes from this key, not from the filename, and it is what you pass to `nuon sync --branch`. * **`[public_repo]`** is the repository the branch tracks. `directory` is the path to the app config inside that repo, so `eks-simple` here rather than `"."`. All three fields are required. * **Two `[[install_groups]]`** — `stage` first (`order = 1`), then `production` (`order = 2`). Lower `order` deploys first, and each group's plan waits for your approval before its deploy runs. * **`[install_groups.label_selector]`** picks each group's installs by label. An install labeled `env = "stage"` is in the `stage` group; one labeled `env = "prod"` is in `production`. Nothing is hard-coded to a particular install, so adding a customer to a group is a matter of labeling it. Both groups use `label_selector` rather than `install_names` on purpose. Names are resolved to install IDs when you sync and an unknown name fails the whole sync, so a name-based group breaks in any org where that install does not exist. Label selectors re-evaluate on every run. ### Label your install The `stage` group is empty until an install carries `env = "stage"`. Label the install you provisioned earlier: ```sh theme={null} nuon installs labels set --install-id env=stage ``` Then confirm the selector matches it: ```sh theme={null} nuon installs list --labels env=stage ``` The example app also declares its CI installs as tracked config files, and sets the labels there instead: ```toml installs/eks-simple-ci-stage.toml theme={null} #:schema https://api.nuon.co/v1/general/config-schema?type=install name = "eks-simple-ci-stage" [labels] env = "stage" approval_option = "approve-all" [aws_account] region = "us-east-1" [[inputs]] domain = "nuon.run" sub_domain = "whoami" ``` Either route sets the same label. Install config files have their own sync command, separate from `nuon apps sync`: ```sh theme={null} nuon installs sync -a -d installs/ ``` Managing labels in the files means group membership is version-controlled along with everything else about the install. ### Sync the app From the `eks-simple` directory (start in the directory where you cloned `example-app-configs`): ```sh theme={null} cd example-app-configs/eks-simple nuon apps select # no flags: pick eks-simple from the list nuon apps sync ``` The sync creates the branch and its two deployment groups. Check what Nuon now has: ```sh theme={null} nuon apps list # prints each app's name and ID nuon apps branches list --app-id ``` Branch commands take the app **ID** (`app...`) — copy it from `nuon apps list`. Selecting the app with `nuon apps select` (no flags, pick from the list) stores it once, which is what lets the rest of this walkthrough drop `--app-id` from `trigger` and `runs`. You should see one branch, `main`. In the dashboard, the app now has a **Branches** view showing the branch and its **Deployment plan**: `stage`, then `production`. The eks-simple branch's deployment plan in the Nuon dashboard: a stage group selecting env=stage installs flowing into a production group selecting env=prod ## Preview a Change Before deploying anything, see what a change would do. Edit the `sub_domain` input default in `inputs.toml` (this is an excerpt — leave the rest of the file as it is): ```toml inputs.toml (excerpt) theme={null} [[input]] name = "sub_domain" description = "The sub domain for the Whoami service" default = "hello" display_name = "Sub Domain" group = "dns" ``` A changed **default** only shows up in the diff for installs that inherit it. An install that sets `sub_domain` explicitly (including `eks-simple-ci-stage`, whose config file pins it) shows an empty diff for this change. To see the diff on every install, change something unconditional instead, or remove the explicit value from your install first. Then run a plan-only preview against your local files: ```sh theme={null} nuon sync --branch main --preview ``` This syncs the config in the current directory and triggers a **plan-only** run for it. Nothing is applied to any install. The command prints the run's ID and returns; watch the run in the dashboard, or with `nuon apps branches runs --app-id --branch-id main`, which opens the workflow TUI. A manual plan-only run still walks the deployment groups: it creates each group's `plan install group` and `deploy install group` step, but each approval gate **auto-approves** ("Auto-approved in plan-only mode") and nothing is applied, so the run finishes on its own. Read the per-install diffs from the completed run. (A pull request preview behaves differently and never creates the group steps at all.) `nuon sync --branch` runs against your local working directory, including uncommitted edits. That makes it the fast way to iterate on a change before you commit it. Note the flag lives on `nuon sync`, not `nuon apps sync`. ## Deploy the Change Drop the `--preview` and the same command becomes a real rollout: ```sh theme={null} nuon sync --branch main ``` Or trigger a run from the branch's committed config, with no local sync at all: ```sh theme={null} nuon apps branches trigger --branch-id main ``` Both walk the same deployment groups, but their first two steps differ, because `nuon sync --branch` hands Nuon a config it already built from your working directory while `trigger` fetches one from git: | Step | `nuon sync --branch main` | `nuon apps branches trigger` | | ---- | ---------------------------------- | ---------------------------------- | | 1 | `fetch commit (skipped)` | `fetch commit` | | 2 | `fetch app config (skipped)` | `fetch app config` | | 3 | `building components and sandbox` | `building components and sandbox` | | 4 | `plan install group: stage` | `plan install group: stage` | | 5 | `deploy install group: stage` | `deploy install group: stage` | | 6 | `plan install group: production` | `plan install group: production` | | 7 | `deploy install group: production` | `deploy install group: production` | From step 3 on they are identical: * `building components and sandbox` rebuilds only what changed. The sandbox half of it needs [sandbox builds](/guides/sandbox-builds) enabled for your org; without it, components only. * each `plan install group` computes the diff for every install in the group and then **waits** for you. * each `deploy install group` runs once you approve the plan before it. ### Approve the plan The run pauses at `plan install group: stage`. In the diff, installs that inherit the default show `sub_domain` moving from `whoami` to `hello`; installs that pin `sub_domain` explicitly show no change for it (see the note in Preview a Change). Approve the plan and the group deploys. The run then stops at `plan install group: production`. A group with no matching installs still pauses; **Skip install group** moves the run past it. Every group's plan waits for an approval, from the dashboard or an API caller. There is no setting that auto-approves a deployment group. ### Watch runs from the CLI ```sh theme={null} nuon apps branches runs --branch-id main ``` This lists the branch's runs and opens the one you pick in the workflow TUI. ## Trigger From a Git Push So far every run has been triggered by hand, because `branch.toml` tracks `nuonco/example-app-configs`, a repository you cannot push to. To get the push-triggered behavior, point the branch at a repository of your own. Fork `example-app-configs`, or copy the `eks-simple` directory into a repository you control. Connect the Nuon GitHub App for your repository's owner (see [connecting a repository](/guides/vcs)). This is what makes pull request previews possible. It is an org-level connection keyed on the owner, so it enables previews for any branch tracking a repo under that owner, whether the branch config uses `[connected_repo]` or `[public_repo]`. Swap `[public_repo]` for `[connected_repo]`, pointing at your repository and the directory the config lives in: ```toml branch.toml theme={null} name = "main" [connected_repo] directory = "eks-simple" repo = "your-org/example-app-configs" branch = "main" [[install_groups]] name = "stage" order = 1 [install_groups.label_selector] env = "stage" [[install_groups]] name = "production" order = 2 [install_groups.label_selector] env = "prod" ``` ```sh theme={null} nuon apps sync git commit -am "point app branch at my repo" git push origin main ``` The push starts a run on its own. From here on, a commit to `main` is the trigger, with no CLI step in between. Open a pull request against `main` with a config change in it. Nuon runs a plan-only preview and posts a comment headed `## Nuon Preview — main` (the branch's `name`, not the app's) with a table of what changed, plus a commit status named `nuon///main preview (plan-only)` linked to the preview run. Push another commit and the same comment is updated in place. ## Inspect the History Every config change to an install is recorded as an app config version. Open the install in the dashboard and look at its **Versions** view, or list them over the API (create an API token in the dashboard's org settings; the install ID comes from `nuon installs list`): ```sh theme={null} curl -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ "https://api.nuon.co/v1/installs/$INSTALL_ID/app-config-versions" ``` Any earlier version can be applied again, which is the path back if a change turns out badly. The guide walks through [rolling back to a previous version](/guides/rollback-install-config). ## Next Steps You now have a git branch wired to a fleet: a change to the config produces a build, a diff per install, and an approval gate in front of each group. Where to go next: Multiple branches, label-driven groups, and the full command reference. Run types, previews, and version history in depth. Every field on `branch.toml`. Have your own systems react to branch runs. # Create Your First App Source: https://docs.nuon.co/get-started/create-your-first-app Use an example app to learn Nuon or package your app with Nuon. Your first app can take one of two forms, and both deploy into your cloud account: * **An example app (recommended)** — the fastest way to see Nuon work end to end. Use one of our pre-built [example apps](https://github.com/nuonco/example-app-configs). * **Your own app** — your app packaged in Nuon's configuration format and referencing your existing Terraform, Helm, Kubernetes, and Docker assets. New to Nuon? Read the [App & Install Life Cycle](/guides/app-install-life-cycle) guide to understand how apps are packaged, installed and managed with Nuon. ## Use an Example App Each guide below deploys a working example into your AWS account: * [eks-simple](/get-started/app-aws-k8s): An AWS EKS (Kubernetes) Traefik `whoami` application (\~35 min) * [cde](/get-started/app-aws-ec2): An AWS EC2 VM you connect to with `ssh` and VS Code Web (\~26 min) * [aws-lambda](/get-started/app-aws-lambda): A Lambda function with DynamoDB and API Gateway (\~26 min) Estimated times above include creating the initial VPC, Subnets, Nat Gateway, ASG, EC2 VM, and a healthy Nuon runner (\~11 min). For the fastest duration, approve all workflow steps when the app install begins. cde and aws-lambda are less resource and cost intensive than eks-simple which provisions a Kubernetes cluster with 3 nodes. For these and other examples, clone the [example-app-configs repository](https://github.com/nuonco/example-app-configs) and run: ```sh theme={null} brew install nuonco/tap/nuon nuon auth login cd example-app-configs/ nuon apps create --name nuon apps sync nuon installs create --name --region ``` ## Use Your Own App When you're ready to try your app with Nuon, package your existing Terraform, Helm, Kubernetes manifest, and container image assets into a Nuon app — then install it into your AWS, Azure or GCP. See [how apps are configured](/concepts/apps#how-do-you-configure-an-app) to structure your app, including connecting a GitHub repository. Use agents like Claude Code to build your app by learning from Nuon's example apps. See our [AI and Apps Building docs](/guides/agents/ai-and-apps) for more info. ## Prerequisites * [Sign up for the free trial](https://app.nuon.co). You'll get a login and an org in Nuon Cloud. * The example apps above need an AWS account to install into. [Set up an AWS account](https://docs.aws.amazon.com/SetUp/latest/UserGuide/setup-overview.html) if you don't have one. Nuon also has apps for Azure and GCP [in our example apps repo](https://github.com/nuonco/example-app-configs). # Day-2 Operations Overview Source: https://docs.nuon.co/get-started/day-2-operations BYOC primitives for scaling your deployments ## Workflows A workflow is the lifecycle of an install or a component. It plans the change, surfaces a diff, runs through any approvals you've configured. Each workflow run's logs and step state are visible in the dashboard, CLI, and TUI. [How it works → Workflows](/concepts/workflows) ## Audit Logs Install runners on AWS, Azure, and GCP can forward deployment, action workflow, and sandbox audit events directly to a customer-controlled OTLP logging backend. The destination and credentials remain in the customer's cloud account. Only Nuon audit events are forwarded, not application logs or general runner logs. [How to → Export Runner Audit Logs](/guides/export-runner-audit-logs) ## Actions An action is a custom script you run on a live install. Define it in TOML, point it at a repo, set environment variables. It runs inside the customer's account — on a cron, on a lifecycle event (`post-deploy-component`, `pre-reprovision`, etc.), on a manual trigger, or as a one-off from the dashboard. Typical uses: healthchecks, database migrations, runbooks, debugging stuck installs. [How it works → Actions](/concepts/actions) ## Policies A policy is an OPA or Kyverno rule that gates what can be deployed. Policies validate Terraform plans, Helm charts, Kubernetes manifests, container images, and sandbox configurations before they apply. Every evaluation is logged on a per-app analytics page, scoped to the install, component, action, or deploy that triggered it. Use policies to enforce compliance, security baselines, or organization conventions across every customer install at once, instead of relying on review discipline per install. [How it works → Policies](/concepts/policies) ## Operation Roles An operation role is a specific IAM role the runner assumes for one class of work — provisioning, deploys, individual actions, or break-glass access. Each role is defined by the vendor and approved by the customer through the Stack, so the runner never holds more permissions than the task at hand requires. Roles can be scoped per component or per action; every workflow run records which role was used. [How it works → Operation Roles](/concepts/operation-roles) # Docs Home Source: https://docs.nuon.co/get-started/introduction Continuous delivery into your customer's cloud.
## Get started with your app, or deploy an example app today
## Quickstart at a glance

The fastest way to try Nuon is to deploy an example app into a test cloud account (AWS, Azure, or GCP) — see the full quickstart for detail.

[Sign up for the free trial](https://app.nuon.co/). Run `brew install nuonco/tap/nuon`. Run `nuon auth login`. Run `git clone https://github.com/nuonco/example-app-configs`, then `cd example-app-configs/eks-simple`. Run `nuon apps create --name eks-simple`, then `nuon apps sync`, then `nuon installs create --name --region `.
# Quickstart Source: https://docs.nuon.co/get-started/quickstart Deploy your first app with Nuon in a few minutes by shipping an example Kubernetes app into an AWS account. You'll need two things before you start: * A Nuon Cloud account — [sign up for the free trial](https://app.nuon.co/). * An AWS account to deploy the app into. Copy and paste the flow below, or follow the walkthrough underneath for detail on each step of the Quickstart. ```sh theme={null} # 1. Install the CLI brew install nuonco/tap/nuon # 2. Authenticate with Nuon Cloud nuon auth login # 3. Clone Nuon's example apps repo git clone https://github.com/nuonco/example-app-configs # 4. Create the app and sync it with Nuon Cloud. (Kubernetes on AWS shown here) cd example-app-configs/eks-simple nuon apps create --name eks-simple nuon apps sync # 5. Install the app in your AWS account nuon installs create --name --region ``` ## Install the CLI ```sh theme={null} brew install nuonco/tap/nuon ``` If you are not using Homebrew, refer to our [CLI](/cli) documentation for other installation options. ## Authenticate to Nuon Cloud ```sh theme={null} nuon auth login ``` Follow the prompt in the browser and log in with a Google account. ## Clone the Example Apps Repo Nuon maintains a [example apps](https://github.com/nuonco/example-app-configs) repo to demonstrate how to package various apps on AWS, Azure and GCP with various architectures like Kubernetes, VM and serverless. ```sh theme={null} git clone https://github.com/nuonco/example-app-configs ``` ## Create and Sync the Example App For the Quickstart, we're using an AWS EKS (Kubernetes) app called eks-simple with a whoami Helm component, an ACM certificate, and an Application Load Balancer to access the whoami service. View the app configuration in the [example apps repo](https://github.com/nuonco/example-app-configs/tree/main/eks-simple) or with your IDE and the cloned repo. Use the Nuon CLI to create the app then sync its contents into Nuon Cloud. The sync triggers the app's components (e.g., images, Helm, Kubernetes manifests, Terraform) to be built into OCI artifacts that are stored in your org's container registry. Nuon will use these artifacts when installing your app. The app name should match the directory name. ```sh theme={null} cd example-app-configs/eks-simple nuon apps create --name eks-simple nuon apps sync ``` ## Install the Example App in AWS Now create an install to deploy the app into your AWS account: ```sh theme={null} nuon installs create --name --region ``` * `--name` (`-n`) names this install. * `--region` (`-r`) is the AWS region to provision into, for example `us-east-1`. ## Guided Walkthroughs For guided walkthroughs including this eks-simple app and EC2 and serverless apps, see [Create Your First App](/get-started/create-your-first-app). ## Deploy Your Own App When you're ready to package your own software, wrap your existing Terraform, Helm, Kubernetes manifest, and container image assets into a Nuon app — the commands are the same (`nuon apps create`, `nuon apps sync`, then `nuon installs create`). Use agents like Claude Code to build your app by learning from Nuon's example apps. See our [AI and Apps Building docs](/guides/agents/ai-and-apps) for more info. # Configure actions Source: https://docs.nuon.co/guides/actions Define and remotely execute custom action workflows in your installs. Actions let you define and remotely execute custom workflows in installs. You can automate common tasks, implement healthchecks, and even automate runbooks to handle incidents. For one-off scripts you don't want to commit to TOML, see [Run adhoc actions](/guides/adhoc-actions). To run an action's steps inside a container image you build, see [Container actions](/guides/container-actions). ## Configuring action workflows Action workflows can be configured in your app's TOML config. For example, to define an HTTP healthcheck: ```toml actions/http_healthcheck.toml theme={null} # action name = "http_healthcheck" timeout = "0m15s" [[triggers]] type = "cron" cron_schedule = "*/5 * * * *" [[triggers]] type = "manual" [[steps]] name = "healthcheck" command = "./healthcheck" [steps.public_repo] repo = "nuonco/actions" branch = "main" directory = "common" [steps.env_vars] ENDPOINT = "https://your-app.{{.nuon.install.sandbox.outputs.public_domain.name}}" METHOD = "HEAD" EXPECTED_STATUS_CODE = "200" ``` ```toml actions/.toml theme={null} # action version = "v1" description = "Your app on AWS." display_name = "Your App" [[actions]] source = "./actions/alb_healthcheck.toml" ``` Once configured and synced, you will see the workflow in the "Actions" tab for your app. Following that example, we can describe the parts of a workflow. ### Timeout Each workflow must have a timeout limiting how long it can run. The maximum allowed timeout is 30 minutes. Timeouts must be provided as valid Golang `time.Duration` strings. The example workflow will time out after 15 seconds, more than enough time to make a simple HTTP request. ### Action triggers Actions can run manually, on a cron schedule, or in response to install lifecycle events. The supported triggers that are not tied to a specific component are: * `manual` * `cron` * `pre-provision` * `post-provision` * `post-provision-sandbox` * `pre-reprovision` * `post-reprovision` * `pre-deprovision` * `post-deprovision` * `pre-deploy-all-components` * `post-deploy-all-components` * `pre-teardown-all-components` * `post-teardown-all-components` * `pre-deprovision-sandbox` * `post-deprovision-sandbox` * `pre-reprovision-sandbox` * `post-reprovision-sandbox` * `pre-update-inputs` * `post-update-inputs` * `pre-secrets-sync` * `post-secrets-sync` * `role-enabled` * `role-disabled` Each workflow trigger is called at the beginning or end of the workflow. In some cases, such as `pre-provision` or `pre-reprovision` that include a stack-run, the trigger will be called right after the runner is healthy. `post-provision-sandbox` runs immediately after the initial sandbox apply succeeds, before secrets sync, DNS provisioning, and component deployment. `post-provision` runs after the complete install provision workflow, including component deployment. #### Role change triggers The `role-enabled` and `role-disabled` triggers fire when a customer enables or disables an [operation role](/concepts/operation-roles) in their install stack. Use these to run validation, auditing, or setup tasks whenever elevated permissions are granted or revoked. ```toml actions/role_audit.toml theme={null} # action name = "role_audit" timeout = "30s" [[triggers]] type = "role-enabled" [[triggers]] type = "role-disabled" [[steps]] name = "audit" inline_contents = """ #!/usr/bin/env sh echo "Role change detected — running audit" """ ``` The following triggers require a `component_name` field to be set, as they are tied to a specific component: * `pre-deploy-component` * `post-deploy-component` * `pre-teardown-component` * `post-teardown-component` * `pre-enable-component` * `post-enable-component` * `pre-disable-component` * `post-disable-component` The enable and disable triggers run when an input update changes a toggleable component's enabled state. Actions can be triggered manually, on a cron, or by install events. You can define multiple triggers for an action. The example action defines two triggers. It will run every five minutes, and can also be triggered manually at any time. ```toml actions/.toml theme={null} # action [[triggers]] type = "cron" cron_schedule = "*/5 * * * *" [[triggers]] type = "manual" ``` ### Steps Each step in an action requires a command to be run. You can optionally load a script from a repo and provide env vars to configure the environment the command will run in. The example action is pretty simple, so it has only one step. It loads a curl script from our [open-source repo of commonly-used scripts](https://github.com/nuonco/actions), and sets a few env vars to configure it. ```toml actions/.toml theme={null} # action [[steps]] name = "healthcheck" command = "./healthcheck" [steps.public_repo] repo = "nuonco/actions" branch = "main" directory = "common" [steps.env_vars] ENDPOINT = "https://your-app.{{.nuon.install.sandbox.outputs.public_domain.name}}" METHOD = "HEAD" EXPECTED_STATUS_CODE = "200" ``` ### Running steps in a container image Steps run on the install runner VM by default, which means any tool a step needs beyond the runner's own toolchain has to be installed by the script at run time. Setting `image` on the action runs every step inside a container image you build instead: ```toml actions/db_migrate.toml theme={null} # action name = "db_migrate" timeout = "10m" image = "ghcr.io/acme/migrate-tools:v1.4.0" [[triggers]] type = "manual" [[steps]] name = "migrate" inline_contents = """ #!/usr/bin/env sh migrate -database "$DATABASE_URL" -path /migrations up """ ``` This is behind an org feature flag and has a few constraints (all steps must use `inline_contents`, AWS install runners only). See [Container actions](/guides/container-actions) for the full guide. ## Monitoring workflow runs Action workflow runs can be monitored on the "Actions" tab in each install. You can see a list of all workflow runs, and inspect each run to see logs, what triggered the run, and whether it succeeded or not. # Run adhoc actions Source: https://docs.nuon.co/guides/adhoc-actions Run a one-off command or bash script on an install without defining it in your app's TOML config. Adhoc actions let you run a one-off command or bash script on an install without defining it in your app's TOML config. This is useful for debugging, running database migrations manually, or any other operational task that doesn't need a recurring or lifecycle-triggered workflow. ## Running an adhoc action Navigate to an install in the dashboard and click **Manage** > **Run adhoc action**. You'll be prompted to configure the run: * **Name** (optional) — a display name shown in workflow history. * **Single Command / Bash Script** — choose between a single-line shell command or a multi-line bash script. * **Timeout** — execution timeout in seconds (1–3600, default: 300). * **Environment Variables** — key/value pairs injected into the execution environment. Values support Go templating (see below). * **Role** — the IAM role the runner will assume when executing the action. After submitting, you'll be taken directly to the workflow run to monitor logs and status. ## Templating Both commands and scripts support Go template syntax for injecting install state at execution time. For example: ```bash theme={null} #!/bin/bash curl -f "https://{{.nuon.install.sandbox.outputs.public_domain.name}}/health" ``` Environment variable values also support templates: ```bash theme={null} #!/bin/bash curl -f "$HEALTH_ENDPOINT" ``` ``` HEALTH_ENDPOINT = "https://{{.nuon.install.sandbox.outputs.public_domain.name}}/health" ``` ## Rerunning an adhoc action From the workflow run detail view, you can rerun a previous adhoc action. The form will be pre-populated with the original command, script, environment variables, and role — you can edit before submitting. # AI and App Building Source: https://docs.nuon.co/guides/agents/ai-and-apps Use AI agents to generate and manage your Nuon app configuration. You can use any terminal-based AI agent to build Nuon app configurations — instead of writing TOML files by hand, describe your application and let the agent scaffold everything for you. To operate orgs, installs, and workflows after config is synced, connect MCP and use [sample queries](/guides/agents/sample-queries). ## Getting started Clone the [example apps repo](https://github.com/nuonco/example-app-configs) and start your agent from that directory. The repo includes an `AGENTS.md` file (with a `CLAUDE.md` symlink) that gives any agent the context it needs: links to the Nuon OSS repo, example configs, and sandbox infrastructure references. ```bash theme={null} git clone https://github.com/nuonco/example-app-configs cd example-app-configs claude # or: gemini, amp, etc. ``` When starting the agent, also point it at your own resources — Helm charts, Terraform modules, and any relevant documentation — so it can tailor the config to your stack. Works with any agent: Claude Code, Google Gemini CLI, Amp Code, and others. For agents that don't read `CLAUDE.md`, create a symlink — e.g., `ln -s AGENTS.md GEMINI.md`. ## Claude Code Claude Code users can also load the [nuon-plugin](https://github.com/nuonco/nuon-plugin) for deeper integration, or install individual [skills](https://github.com/nuonco/skills) from the Nuon skills library. The plugin adds `/nuon:init`, `/nuon:convert`, and `/nuon:validate` slash commands directly in your Claude session. The skills (`nuon-creator`, `nuon-app-config`, `nuon-actions`, `nuon-policy`) provide focused, auto-invoked assistance for specific tasks like config generation, action scripting, and policy authoring. ```bash theme={null} npx skills add nuonco/skills ``` # MCP walkthrough Source: https://docs.nuon.co/guides/agents/mcp-walkthrough Connect an LLM client to Nuon and run your first queries. ## Prerequisites * [Nuon CLI](/cli) installed and authenticated (`nuon auth login`, `nuon orgs select`) * An LLM client that supports MCP (Cursor, Claude Code, etc.) ## Steps Add the stdio proxy. Token and org stay in `~/.nuon`. ```bash theme={null} nuon agents mcp setup --platform claude-code nuon agents mcp setup --platform cursor nuon agents mcp setup --platform amp ``` Or register with the client CLI: ```bash Claude Code theme={null} claude mcp add --transport stdio nuon -- nuon agents mcp ``` ```bash Amp theme={null} amp mcp add nuon -- nuon agents mcp ``` The proxy derives its URL from the configured API hostname (`api.` becomes `mcp./mcp`). Override it with `--url` (and `--name` for the client's MCP list). Cursor Agent: `agent mcp enable ` after setup. Full examples: [Connect](/guides/agents/overview#connect) and [Override the server](/guides/agents/overview#override-the-server). ```bash theme={null} nuon agents context ``` Confirm you are authenticated and an org is selected. If not, run `nuon auth login` and `nuon orgs select`. In the LLM client, paste: ``` Who am I, and what installs are in this org? ``` The client should call `whoami` and `list_installs`. You do not need `select_org` when using the stdio proxy (org is already in `X-Nuon-Org-ID`). More prompts: [Sample queries](/guides/agents/sample-queries). Pass `--allow-writes` only when you need to mutate (approve steps, run actions, preview branches, and so on). Re-run setup so the client config includes it, or start the proxy directly: ```bash theme={null} nuon agents mcp setup --platform cursor --allow-writes nuon agents mcp --allow-writes ``` Your token must also have create permission. Tool catalog: [Tools](/guides/agents/tools). Prompts to paste: [Sample queries](/guides/agents/sample-queries). # Agents Source: https://docs.nuon.co/guides/agents/overview Operate and build Nuon from LLM clients. **AI agent** here means an LLM client (Claude Code, Cursor, etc.) driving the control plane. This is not the [install runner](/concepts/runners) in customer clouds. Use an LLM client to read and operate orgs, apps, installs, and workflows, or to scaffold app config from example repos. Connect Claude Code, Cursor, or Amp and run your first calls. Read and write tools exposed on the control plane. Prompts to paste into your client after MCP is connected. Generate Nuon app config with Claude Code, Gemini, Amp, and skills. ## Prerequisites ```bash theme={null} nuon auth login nuon orgs select ``` `nuon agents context` prints your auth, selected org/app/install, and MCP HTTP URL. ## Connect **Recommended:** register the stdio proxy so the client runs `nuon agents mcp`. Token and org stay in `~/.nuon`. `nuon agents mcp setup` writes that stdio config for Claude Code, Cursor, or Amp. The proxy derives its URL from the configured API hostname (`api.` becomes `mcp./mcp`) and registers as `nuon`. ### Setup (stdio) ```bash theme={null} nuon agents mcp setup --platform claude-code nuon agents mcp setup --platform cursor nuon agents mcp setup --platform amp ``` `--platform claude` is an alias for `claude-code`. `nuon mcp setup` is the same command. | Platform | File | | ------------- | --------------------------------------- | | `claude-code` | `.mcp.json` | | `cursor` | `.cursor/mcp.json` | | `amp` | `.amp/settings.json` (`amp.mcpServers`) | Existing servers in the file are kept. Cursor Agent: `agent mcp enable ` after setup. ### Override the server The proxy derives its upstream URL from `api_url` in your CLI config when the hostname starts with `api.` (`https://api.nuon.co` → `https://mcp.nuon.co/mcp`). To point somewhere else, pass `--url`; to change the name in the client's MCP list, pass `--name`. ```bash theme={null} nuon agents mcp --url https://mcp.example.com/mcp --name nuon-example ``` `--url`, `--name`, and `--allow-writes` pass through `setup`, so the generated client config keeps them: ```bash theme={null} nuon agents mcp setup --platform cursor --url https://mcp.example.com/mcp --name nuon-example nuon agents mcp setup --platform cursor --allow-writes ``` A non-default CLI config (`-C /path/to/config`) carries its own token, org, and `api_url`. `setup` copies the `-C` into the command it writes, so the client keeps using that config. Run `nuon agents context` to confirm which MCP URL resolves before registering. ### Client CLIs You can also register stdio yourself. Read-only by default. The name you pass (`nuon`) is what appears in the client's MCP list. ```bash Claude Code theme={null} claude mcp add --transport stdio nuon -- nuon agents mcp # writes claude mcp add --transport stdio nuon -- nuon agents mcp --allow-writes ``` ```bash Amp theme={null} amp mcp add nuon -- nuon agents mcp # writes amp mcp add nuon -- nuon agents mcp --allow-writes # this workspace only amp mcp add nuon --workspace -- nuon agents mcp ``` ```json Cursor theme={null} { "mcpServers": { "nuon": { "command": "nuon", "args": ["agents", "mcp"] } } } ``` Cursor / Cursor Agent has no `mcp add`. Save the JSON above as `~/.cursor/mcp.json` or project `.cursor/mcp.json`, then `agent mcp enable ` — or run `nuon agents mcp setup --platform cursor`. Pass `--allow-writes` in stdio `args` when you need writes. Your token must have create permission. You do not need `select_org` when the org is already in the CLI config (the stdio proxy sends `X-Nuon-Org-ID`). ## Related CLI flags These apply to `nuon` commands, not MCP tools: | Flag | Purpose | | ---------------- | --------------------------------------------------------- | | `--output agent` | Single JSON envelope on stdout (`{"ok":true,"data":...}`) | | `--read-only` | Block mutating CLI commands (exit 2) | See [CLI commands](/cli-commands) for the full reference. # Sample queries Source: https://docs.nuon.co/guides/agents/sample-queries Prompts to paste into an LLM client after Nuon MCP is connected. Copy a prompt into Claude Code, Cursor, or Amp after [MCP is connected](/guides/agents/overview). The client should pick tools from the [catalog](/guides/agents/tools); you do not need to name them. Write prompts need `--allow-writes` on the stdio proxy. Replace names like `acme-prod` with your own install, app, or workflow IDs. ``` What apps and installs are in this org? Summarize each install's app and status. ``` Variants: `Show me installs for the payments app.` ``` What's the status of install acme-prod? Include components, recent workflows, and anything unhealthy. ``` ``` Are there any pending workflow approvals in this org? For each one, tell me the install, workflow, and what is waiting. ``` ``` Review the pending plan on workflow . Summarize the change, then ask me before approving or rejecting it. ``` Write. The agent should inspect the workflow first and wait for your confirmation. ``` Why did the latest deploy on install acme-prod fail? Pull the deploy and workflow step logs and quote the error. ``` ``` Why did the latest build for component fail? Show the git ref and the relevant log lines. ``` ``` Watch workflow until it finishes. Tell me when the status changes and when it reaches a terminal state. ``` ``` What actions can I run on install acme-prod? For each one, say whether it can be triggered manually. ``` ``` Run the action on install acme-prod. Confirm the action with me before triggering it, then watch the workflow. ``` Write. ``` Show the runbooks for app . Summarize the steps in each one. ``` ``` What's the latest on the default branch for app ? Did the last run succeed, what changed, and how far have install-group deploys gotten? ``` Requires the app-branches org feature. ``` Preview PR for app against install acme-prod. Use plan-only unless I ask you to apply. Watch the workflow and summarize the result. ``` Write. Same as `nuon apps branches preview`. Ask before `apply`. HTTP MCP cannot read the local workspace. For local toml, run `nuon apps sync` first, then pass the resulting `app_config_id`. ``` Plan a reprovision of install acme-prod. Do not apply until I confirm. If I ask to deprovision, require an explicit confirm from me first. ``` Write. Deprovision requires `confirm=true`. `plan_only` does not. ``` What are the current inputs on install acme-prod? Propose an update for and wait for my approval before writing it. ``` Write. ``` Workflow failed. Tell me which step failed and whether it is retryable. If I say yes, retry it and watch until it finishes. ``` Write. ``` Cancel in-progress workflow on install acme-prod. Confirm with me first, then verify it stopped. ``` Write. # Tools Source: https://docs.nuon.co/guides/agents/tools Control-plane tools available to LLM clients via MCP. Writes are hidden from the stdio proxy unless `--allow-writes` is set. HTTP MCP lists them whenever the token can create. For prompts to paste into a client, see [Sample queries](/guides/agents/sample-queries). To connect, see [Agents](/guides/agents/overview). ## Catalog | Domain | Read | Write | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Orgs | `whoami`, `list_orgs`, `select_org` | | | Apps | `list_apps`, `get_app`, `list_app_branches`, `get_app_branch`, `list_app_branch_preview_sources` | `preview_app_branch` | | Components | `list_components`, `get_component`, `list_builds`, `get_build` | | | Installs | `list_installs`, `get_install`, `list_install_components`, `get_install_inputs`, `list_workflows`, `get_workflow`, `get_workflow_step`, `watch_workflow`, `get_pending_approvals`, `list_deploys`, `get_deploy` | `update_install_inputs`, `deploy_install_components`, `reprovision_install`, `reprovision_sandbox`, `deprovision_install`, `deprovision_sandbox`, `approve_step`, `reject_step`, `retry_step`, `cancel_workflow` | | Actions | `list_install_actions`, `get_action` | `run_action` | | Logs | `get_workflow_step_logs`, `get_deploy_logs`, `get_build_logs` | | | Runbooks | `list_runbooks`, `get_runbook` | | ## Writes Mutating tools are prefixed with `WRITE OPERATION:` in their descriptions. The stdio proxy hides them unless you pass `--allow-writes`. Your token must also have create permission. Destructive tools (deprovision, approve/reject, preview apply, and similar) should be confirmed with the user before the client sets flags such as `confirm=true` or `mode=apply`. # Configure app branches Source: https://docs.nuon.co/guides/app-branches Connect a git branch to your app, group your installs, and roll changes out across your fleet from a git push. This guide walks through setting up an [app branch](/concepts/app-branches) on an existing app: writing the branch config, sorting your installs into deployment groups, triggering runs, and approving them. ## Prerequisites * An [app](/concepts/apps) already synced to Nuon, with at least one install. * The [Nuon CLI](/cli) installed and authenticated (`nuon auth login`). * For pull request previews, the **Nuon GitHub App connected in your org for the repository's owner**. See [connecting a repository](/guides/vcs). Every branch of a given app must point at the same repository, and an install can belong to only one branch at a time. See [rules and limits](/concepts/app-branches#rules-and-limits) before you plan more than one branch. ## Write a branch config Add a `branch.toml` file to the root of your app config directory, next to `metadata.toml`. It declares the branch name, the repo and branch to track, and the deployment groups the rollout moves through. ```toml branch.toml theme={null} name = "main" [connected_repo] repo = "acmeco/my-app-config" directory = "." branch = "main" [[install_groups]] name = "staging" order = 1 [install_groups.label_selector] env = "staging" [[install_groups]] name = "production" order = 2 [install_groups.label_selector] env = "prod" ``` Three things to get right: * **The repo block.** Use `[connected_repo]` for a repository connected through the Nuon GitHub App, or `[public_repo]` for a public one. Exactly one of the two, and `repo`, `directory`, and `branch` are all required. `directory` is the path to your app config within the repo, or `"."` if it is at the root. * **`name`** is the branch's name in Nuon, and it comes from this key, not from the filename. It is what you pass to `nuon sync --branch`. * **Group order.** `order` decides which group deploys first, lowest first. List your groups in the order you want them to run. Every branch needs one of `[connected_repo]` or `[public_repo]`: the repo block is what the deployment groups sync against. Every field is documented in the [branch config reference](/config-ref/branch). ### Selecting installs for a group Each group picks its installs one of two ways: a `label_selector`, or an explicit list (`install_ids` and `install_names`, which may be combined; names resolve to IDs at sync time). A label selector cannot be combined with an explicit list in the same group: ```toml theme={null} # Recommended: matches any install carrying all of these labels. [[install_groups]] name = "production" order = 2 [install_groups.label_selector] env = "prod" tier = "enterprise" ``` ```toml theme={null} # Explicit names, resolved to install IDs when you sync. [[install_groups]] name = "pilot" order = 1 install_names = ["customer-acme"] ``` ```toml theme={null} # Explicit IDs. [[install_groups]] name = "pilot" order = 1 install_ids = ["inlbmky2fz8qvrxp3d7twahjc9"] ``` Prefer `label_selector`: it re-evaluates on every run, so group membership follows your labels with no re-sync. Explicit `install_names` are resolved to IDs once, at sync time. ### One branch or several Two layouts are supported, and they are mutually exclusive: * **`branch.toml`**: a single branch. This is what most apps want, and what the rest of this guide assumes. * **`branches/*.toml`**: one file per branch, for tracking several branches of the same repo (a `staging` branch and a `main` branch, for example). The `name` key inside each file sets the branch name; the filename does not. ## Label your installs A label selector only matches installs that actually carry the labels. Set them with the CLI: ```sh theme={null} nuon installs labels set --install-id customer-acme env=prod tier=enterprise ``` Labels are key/value pairs, passed as positional `key=value` arguments. To inspect or remove them: ```sh theme={null} nuon installs labels list --install-id customer-acme nuon installs labels unset --install-id customer-acme tier ``` You can also confirm which installs a selector will match before you run anything: ```sh theme={null} nuon installs list --labels env=prod --labels tier=enterprise --limit 200 ``` All labels passed to `--labels` must match, so this is the same AND semantics a group's `label_selector` uses. On a fleet of more than 20 installs, pass `--limit` at least your install count to list every match. Prefer keeping labels in install config files (a `[labels]` table, one file per install) so they are reviewed like the rest of your config, and apply them with `nuon installs sync -a -d installs/`. To start from an install that already exists, export it: `nuon installs generate-config -i > installs/.toml`. ## Sync the branch config Syncing your app config creates or updates the branch, along with its deployment groups: ```sh theme={null} nuon apps sync ``` Branches are upserted by name and are never pruned. Removing a branch from your config does not delete it in Nuon. Use `nuon apps branches delete` for that. Confirm what Nuon now has: ```sh theme={null} nuon apps list # find your app's ID nuon apps branches list --app-id nuon apps branches get --app-id --branch-id ``` `list` prints each branch's name and ID; `get` takes the branch ID and returns the full branch, deployment groups included. The `nuon apps branches` subcommands take the app **ID** (`app...`), which `nuon apps list` prints. Or select the app once with `nuon apps select` (no flags, pick from the list) and omit `--app-id` wherever it is optional. ## Trigger a run Once the branch exists, any push to the tracked branch starts a run: ```sh theme={null} git commit -am "bump chart version" git push origin main ``` Nuon receives the push, and the run appears in the dashboard and in `nuon apps branches runs`. You do not have to wait for a push, though. Two other ways to start a run, both using the app you selected with `nuon apps select`: ```sh Trigger the committed config theme={null} # Runs against the tracked branch's latest commit. nuon apps branches trigger --branch-id main # Plan only — plans every group, applies nothing. nuon apps branches trigger --branch-id main --preview # Rebuild every component instead of only what changed. nuon apps branches trigger --branch-id main --force ``` ```sh Sync local files, then run theme={null} # Syncs the config in the current directory, then triggers a run for that config. nuon sync --branch main # Same, but plan-only. nuon sync --branch main --preview # Pick the branch interactively. nuon sync --app-branch ``` Anything that can run the CLI or call the API can start the same run, which is how a CI job drives a rollout without a webhook. The [GitHub Actions guide](/guides/github-actions) shows a workflow that triggers a run once your tests pass. `--preview` on a manual run suppresses the **apply**, not the workflow. The run still creates `plan install group: ` and `deploy install group: ` for every group, but each approval gate auto-approves ("Auto-approved in plan-only mode") and nothing is applied — the run finishes on its own, and you read the per-install diffs from the completed run. This differs from a pull request preview, which never creates the group steps at all. `nuon sync --branch` runs against your **local, uncommitted files**, not the tracked branch's commit — the right tool for iterating on a change before pushing it. The `--branch`, `--app-branch`, and `--preview` flags live on `nuon sync` (not `nuon apps sync`), and `--preview` takes effect alongside `--branch` or `--app-branch`. Because `nuon sync --branch` hands Nuon a config it already built locally, that run's first two steps appear as `fetch commit (skipped)` and `fetch app config (skipped)`. A run started from a push or from `nuon apps branches trigger` fetches from git and shows both steps running. `nuon apps branches trigger` and `nuon apps branches runs` open the full-screen workflow TUI, so you can watch steps and approve from the terminal. See the [TUI reference](/tui). ## Approve or skip a deployment group A run pauses at each group's plan step until someone acts on it. In the dashboard, open the app's **Branches** view, then the run, and you will see the **Deployment plan** with each group's steps. For each group: * **Approve install group plan** — read the per-install diff, then approve to let the group's deploy step run. * **Skip install group** — move the rollout past this group's deploy without applying it. The run stops at the next group's plan step, so each group is approved or skipped on its own. A branch run in the Nuon dashboard awaiting approval for the stage install group's plan, with Review changes and Approve actions and the run's component builds listed There is no setting that auto-approves a deployment group's plan. Each one needs a human in the dashboard or an API caller. If you want a machine to advance a rollout, subscribe to the [webhook](/guides/webhooks) events for the run and drive the approval through the API once your own checks pass. ## Set up pull request previews The one piece of setup is a **GitHub App connection in your org for the repository's owner**; the branch config itself needs nothing. Opening a pull request against a tracked branch, or pushing another commit to an open one, starts a plan-only run, and Nuon reports back on the pull request: * a comment headed `## Nuon Preview — ` (the branch's `name`, so usually `main`) with a table of config changes by section, edited in place on each later push rather than added to. * a mode-specific commit status such as `nuon/// preview (plan-only)`, linking to the preview run and moving from pending to success or failure. If the pull request does not change the app config, the run stops early and the comment reports that the preview was skipped. Connect the GitHub App once, from [connecting a repository](/guides/vcs). After that, every branch tracking a repo under that owner gets previews. The connection is an org-level setting, not a property of the branch config: a `[public_repo]` branch pointing at `acmeco/my-app` gets previews just like a `[connected_repo]` one, provided your org has connected `acmeco`. Previews start posting as soon as your org has the GitHub App connection for the repository's owner. If they have not appeared yet, check that connection rather than your `branch.toml`. To reproduce a preview locally before opening the pull request: ```sh theme={null} nuon sync --branch main --preview ``` ## Roll back a change Every config change to an install is recorded as an app config version, and any earlier version can be applied again, to one install or to a whole deployment group, with a plan in front of it. The [rollback guide](/guides/rollback-install-config) walks through the exact calls. ## Command reference | Command | What it does | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `nuon apps branches list --app-id ` | List an app's branches. Alias: `nuon apps br`. | | `nuon apps branches get --app-id --branch-id ` | Show a branch's details, including its deployment groups. Takes an ID, not a name. | | `nuon apps branches create --app-id --name ` | Create a branch without syncing a config file. | | `nuon apps branches trigger --branch-id ` | Trigger a run. `--preview` for plan-only, `--force` to rebuild everything. | | `nuon apps branches runs --branch-id ` | List runs and open one in the workflow TUI. | | `nuon apps branches delete --app-id --branch-id --confirm` | Delete a branch. | | `nuon sync --branch ` | Sync the current directory, then trigger a run for it. Add `--preview` for plan-only. | | `nuon sync --app-branch` | Same, choosing the branch interactively. | | `nuon installs labels set --install-id k=v ...` | Set labels used by group selectors. | | `nuon installs labels list --install-id ` | List an install's labels. | | `nuon installs labels unset --install-id ...` | Remove labels. | | `nuon installs list --labels k=v` | Preview which installs a selector matches. | ## Troubleshooting **The branch synced but has no deployment groups.** The branch config is missing its `[connected_repo]` / `[public_repo]` block. Add one and sync again. **A push did not start a run.** Branch matching is on the exact repository and branch name. Check that the branch's `branch` value matches the ref you pushed and that the repo is the one Nuon has connected. There are no path filters, so a push that matches always starts a run. If nothing happened, the match itself failed. **The pull request has no Nuon comment.** Your org has no GitHub App connection for that repository's owner. The preview run itself succeeds and this is not reported as an error, so check the connection rather than the branch config. Note this is about the *owner*, not the branch's repo block. A `[public_repo]` branch is fine if the owner is connected. **A sync failed on an install name.** `install_names` must resolve to installs that exist in the org you are syncing into. Switch the group to a `label_selector`, or create the install first. **A group deployed to nothing.** No install carried the selector's labels. Check with `nuon installs list --labels ...`. ## Next steps Run types, plan and approval flow, and install version history. Roll a change through the `eks-simple` example app end to end. Re-apply a previous config version to an install or a whole group. Every field on `branch.toml`. React to branch runs from your own systems. # App Initialization Source: https://docs.nuon.co/guides/app-init Initialize and scaffold Nuon application configuration files with the CLI. The `nuon apps init` command helps you quickly scaffold configuration files for your Nuon application. It generates the necessary TOML configuration files with proper structure and defaults, making it easy to get started with Nuon. ## Basic Usage To initialize a new app configuration in the default directory (`./app-config`): ```bash theme={null} nuon apps init ``` This generates a complete app configuration structure with all core configuration files: * `inputs.toml` - Define customer-facing configuration options * `sandbox.toml` - Configure the infrastructure sandbox * `stack.toml` - Define the stack * `runner.toml` - Configure the deployment runner * `secrets.toml` - Manage secrets configuration * `break_glass.toml` - Configure emergency access policies * `policies.toml` - Define organizational policies * `components/` - Directory for component configurations * `actions/` - Directory for action workflows ## Interactive Mode For a guided setup experience, use interactive mode: ```bash theme={null} nuon apps init --interactive # or nuon apps init -i ``` Interactive mode walks you through: 1. **Template Selection** - Choose a prebuilt template or start from scratch 2. **Component Configuration** - Select which sample components to include 3. **Generator Options** - Configure comment and default value preferences This is especially helpful when you're new to Nuon or want to explore available options. ## Using Prebuilt Templates Nuon provides production-ready templates for common deployment scenarios. Use the `--prebuild-template` flag to bootstrap your configuration quickly: ### AWS EKS Template ```bash theme={null} nuon apps init --prebuild-template aws-eks ``` This generates a complete configuration optimized for deploying containerized applications to AWS Elastic Kubernetes Service (EKS). Additional templates coming soon... ## Configuration Options ### Path Configuration Specify a custom output directory: ```bash theme={null} nuon apps init --path ./my-app-config ``` ### Include Comments Add inline comments explaining each configuration field: ```bash theme={null} nuon apps init --enable-comments ``` This is helpful for learning and understanding configuration options. ### Include Default Values Explicitly set all fields to their default values: ```bash theme={null} nuon apps init --enable-defaults ``` By default, only required fields are included. This flag shows all available options. ### Skip Non-Required Fields Generate a minimal configuration with only required fields: ```bash theme={null} nuon apps init --skip-non-required ``` This creates a cleaner starting point when you know you'll customize extensively. ### Overwrite Existing Files Force overwrite of existing configuration files: ```bash theme={null} nuon apps init --overwrite ``` Be careful with `--overwrite` as it will replace existing files without prompting. Always commit your changes to version control before using this flag. ## Initializing Individual Configurations Instead of generating the entire configuration structure, you can initialize specific configuration files using subcommands. ### Sandbox Configuration Generate only the sandbox configuration: ```bash theme={null} nuon apps init sandbox \ --terraform-version 1.11.3 \ --public-repo nuonco/aws-eks-sandbox \ --public-repo-dir terraform \ --public-repo-branch main \ --var region=us-west-2 \ --env-var LOG_LEVEL=info ``` Key flags: * `--terraform-version` - Terraform version to use * `--public-repo` - Public GitHub repository (e.g., `owner/repo`) * `--connected-repo` - Connected private repository * `--drift-schedule` - Cron expression for drift detection * `--var` - Terraform variable (can be specified multiple times) * `--env-var` - Environment variable (can be specified multiple times) ### Stack Configuration Generate the stack configuration. ```bash theme={null} nuon apps init stack \ --name my-app-stack \ --description "Production EKS cluster" \ --type aws-cloudformation \ --vpc-template-url https://s3.amazonaws.com/templates/vpc.yaml \ --runner-template-url https://s3.amazonaws.com/templates/runner.yaml ``` Required flags: * `--name` - Name of the stack * `--description` - Description of the stack Supported `--type` values: `aws-cloudformation`, `azure-bicep` (Azure), `gcp-terraform` (Google Cloud). ### Runner Configuration Generate the runner configuration: ```bash theme={null} nuon apps init runner \ --runner-type kubernetes \ --helm-driver configmap \ --env-var NUON_LOG_LEVEL=debug ``` Required flags: * `--runner-type` - Type of runner: `kubernetes`, `docker`, or `vm` ### Component Configurations Generate component configuration files for different component types. #### Terraform Module Component ```bash theme={null} nuon apps init component terraform-module \ --name database \ --var-name db \ --terraform-version 1.11.3 \ --connected-repo my-org/my-repo \ --connected-repo-dir terraform/database \ --var environment=production \ --dependency vpc ``` #### Helm Chart Component ```bash theme={null} nuon apps init component helm-chart \ --name postgresql \ --chart-name postgresql \ --helm-repo-url https://charts.bitnami.com/bitnami \ --helm-chart postgresql \ --helm-version 12.0.0 \ --namespace databases \ --value persistence.enabled=true \ --dependency vpc ``` #### Kubernetes Manifest Component ```bash theme={null} nuon apps init component kubernetes-manifest \ --name app-deployment \ --namespace default \ --manifest "apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: replicas: 3" ``` ### Action Configuration Generate an action workflow configuration: ```bash theme={null} nuon apps init action \ --name backup \ --trigger-type cron \ --cron-schedule "0 2 * * *" \ --step-name run-backup \ --step-command "./backup.sh" \ --connected-repo my-org/my-repo \ --connected-repo-dir scripts \ --env-var BACKUP_BUCKET=s3://backups ``` Common trigger types: * `manual` - Triggered manually from dashboard or CLI * `cron` - Scheduled using cron expression * `post-provision-sandbox` - Runs after initial sandbox provisioning and before component deployment * `post-provision` - Runs after installation provisioning * `post-deploy-component` - Runs after deployment of the component specified by `component_name` * `post-deploy-all-components` - Runs after an all-components deployment ## Common Workflows ### Starting a New Application 1. **Initialize with template:** ```bash theme={null} nuon apps init --interactive --path ./my-app ``` 2. **Review and edit generated files:** ```bash theme={null} cd my-app cat app.toml ``` 3. **Customize components:** ```bash theme={null} nuon apps init component helm-chart \ --name api \ --chart-name my-api-chart \ --connected-repo my-org/charts \ --connected-repo-dir api ``` 4. **Sync to Nuon:** ```bash theme={null} nuon apps sync ``` ### Adding to an Existing Application If you already have some configuration files and want to add more: ```bash theme={null} # Add a new component without overwriting existing files nuon apps init component terraform-module \ --name new-service \ --path ./app-config ``` ### Regenerating Configuration If you need to regenerate with different options: ```bash theme={null} # Backup first cp -r app-config app-config.backup # Regenerate with comments and defaults nuon apps init \ --path ./app-config \ --enable-comments \ --enable-defaults \ --overwrite ``` ## Configuration File Structure After running `nuon apps init`, your directory will have this structure: ``` app-config/ ├── inputs.toml # Customer-facing configuration ├── sandbox.toml # Infrastructure sandbox ├── stack.toml # Infrastructure stack definition ├── runner.toml # Runner configuration ├── secrets.toml # Secrets management ├── break_glass.toml # Emergency access ├── policies.toml # Organizational policies ├── components/ # Component definitions │ ├── component1.toml │ └── component2.toml ├── permissions / # Permission definitions │ ├── provision.toml │ └── deprovision.toml │ └── maintenance.toml └── actions/ # Action workflows ├── action1.toml └── action2.toml ``` ## Next Steps After initializing your app configuration: 1. **Review Generated Files** - Open each TOML file and customize values for your application 2. **Add Components** - Use `nuon apps init component` to add application-specific components 3. **Configure Actions** - Set up automated workflows with `nuon apps init action` 4. **Sync to Nuon** - Run `nuon apps sync` to create or update your app in Nuon 5. **Test Locally** - Validate your configuration before deploying
The `nuon apps init` command is a local operation and does not require authentication. It only generates configuration files on your local machine. Use `nuon apps sync` to push your configuration to the Nuon platform. ## Tips and Best Practices ### Version Control Always commit generated configuration files to version control: ```bash theme={null} git add app-config/ git commit -m "Initialize Nuon app configuration" ``` ### Iterative Development Start minimal and add complexity as needed: ```bash theme={null} # Start with minimal config nuon apps init --skip-non-required # Add components incrementally nuon apps init component helm-chart --name api nuon apps init component terraform-module --name database ``` ### Use Comments for Learning When learning Nuon, generate files with comments: ```bash theme={null} nuon apps init --enable-comments --enable-defaults ``` This creates well-documented configuration files that explain each option. ### Template Customization After using a prebuilt template, customize it for your needs: ```bash theme={null} # Start with template nuon apps init --prebuild-template aws-eks # Customize sandbox nuon apps init sandbox --overwrite \ --var region=eu-west-1 \ --var instance_type=t3.large ``` # App and Install Life Cycle Source: https://docs.nuon.co/guides/app-install-life-cycle Understand the life cycle of apps and installs in Nuon. [Apps](../concepts/apps) and [installs](../concepts/installs) in Nuon follow a defined lifecycle. You'll start by packaging your app and syncing it to build OCI artifacts. Then you can create one or many customer-specific installations, which you'll update with change controls and monitor over time. Understanding this life cycle is crucial to effectively using Nuon to manage your apps and installs. # App Life Cycle ## Package your app The first step in the app life cycle is to package your app as a set of TOML files in a git-backed repository. These files define the app's sandbox (e.g., VPC, networking, and Kubernetes infrastructure), components, inputs, variables, and other configuration details. These configurations can point to your existing Terraform modules, Helm charts, Kubernetes manifests, or other infrastructure-as-code resources. For examples of packaged apps, see our [public GitHub repository](https://github.com/nuonco/example-app-configs). Packaging your app can be customized to re-use existing VPCs, networking, or Kubernetes clusters, or by default Nuon creates new ones for each install. ## Sync and Build When an app is initially created and synced to the Nuon control plane with the [Nuon CLI](../cli), a build occurs for each component in the app. Builds create an OCI artifact for each component and the Nuon control plane stores them in the container registry like AWS ECR. **Important:** After the initial sync, component changes require rebuilding. `nuon apps sync` will automatically rebuild when it detects changes to TOML files or referenced files like Helm values. CLI Build However, you must manually rebuild in the dashboard when: * Git repository source code changes (e.g., Terraform modules or Helm charts) * Source code in your app repository changes (push your branch first, then rebuild) **Tip:** Double-check your branch name in the component TOML file when source is included in your app repo—this is a common oversight. Remember to run `nuon apps sync` whenever anything in your TOML files changes. Rebuild components # Install Life Cycle ## Installs, defined for your customers Installs into customer cloud accounts are created from apps and inherit the infrastructure and components defined in the app. When an install is created, it uses the latest built version of each component. An install's life cycle is managed by [workflows](../concepts/workflows) that define the steps to deploy, update, manage, and tear down the install. Workflows Creating an install starts by providing any inputs defined by the app, such as API keys, cloud region, node instance type, or app release version. This is how you customize installs for each customer. Inputs Install workflows deploy the sandbox, components, and actions from your app in sequence. Workflow approvals ## Update, with change controls Because apps are git-backed, you can update individual or multiple installs to the latest release of your app or upgrade components or even infrastructure. e.g., Kubernetes version. Updates have approval gates by default to ensure changes are reviewed before being applied to customer installs. e.g., Helm diffs, Terraform plan outputs. The user can optionally Accept All approvals to streamline the update process. Component update approvals Kyverno policies can also be applied to installs to enforce security and compliance during updates. e.g., creating ingress resources or tampering with container resources requests/limits. **Critical step:** When you rebuild a component for an app with existing installs, you must manually redeploy that component through the dashboard or the CLI. The install won't receive the changes automatically—this is a common step that gets overlooked. Redeploy component ## Monitor and Remediate Once deployed, installs can be monitored for health and performance metrics with actions, bash scripts included in the app. e.g., checking pod status, disk space, ALB availability, etc. Action healthcheck Actions can also perform remediation tasks automatically or manually through the dashboard to resolve issues detected during monitoring. Action remediation If elevated permissions are required, the user maintaining the install can request a break glass role with elevated permissions for a limited time to perform necessary tasks. The end customer approves the request by re-applying the stack, which updates the permissions in the install's runner. # Nuon BYOC Source: https://docs.nuon.co/guides/byoc A single tenant instance of Nuon, that you own
Nuon BYOC requires a paid license. [Contact sales](https://nuon.co/contact-sales) to get started. ## Architecture We support running Nuon BYOC on AWS and GCP. The shape of the architecture is the same across platforms, but using the services native to each cloud. Three stacked layers: Nuon at the top; your cloud account (AWS or GCP) in the middle, containing a BYOC Runner and your Nuon instance; and your customers' clouds (AWS, Azure, or GCP) at the bottom, containing an Install Runner and Installs. The BYOC Runner polls Nuon for jobs and installs and updates your Nuon instance. The Install Runner polls your Nuon instance for jobs, pulls artifacts, and deploys installs. * **Compute:** EKS cluster in a dedicated VPC * **Databases:** RDS for the Nuon control plane and Temporal * **Artifacts and storage:** ECR for OCI artifacts, S3 for object storage **Before you start** * Admin permissions in the account * Quota headroom for VPCs, Elastic IPs, and Internet Gateways * A paid account. Nuon's resource requirements exceed AWS Free Tier. * **Compute:** GKE cluster in a dedicated VPC network * **Databases:** Cloud SQL for the Nuon control plane and Temporal * **Artifacts and storage:** Artifact Registry for OCI artifacts, Cloud Storage for object storage **Before you start** Enable these APIs in your project: * **Compute and Kubernetes:** Compute Engine, Kubernetes Engine * **Data:** Cloud SQL Admin, Artifact Registry * **Networking:** Cloud DNS, Service Networking, Certificate Manager * **Project, identity, and secrets:** Cloud Resource Manager, IAM Service Account Credentials, Secret Manager # Install Nuon BYOC Source: https://docs.nuon.co/guides/byoc/installation Install Nuon BYOC in your cloud Once the requirements are ready, you can provision the Install Stack for your Nuon BYOC install. This will provision the foundational network resources and the Nuon runner. Once the runner is online, it will take over the process and deploy Nuon in your cloud. This is the same process your customers will complete when installing your app using Nuon. ## Gather Inputs and Secrets Gather all the inputs and secrets from the [Requirements](/guides/byoc/requirements). ### Authentication Inputs | Input | Value | | ------------------ | ------------------------------------------- | | Auth Provider Type | `google` or `oidc` | | Auth Issuer URL | The issuer URL from your identity provider. | | Auth Client ID | Client ID from your identity provider | | Auth Redirect URL | `https://auth./auth` | If using the deprecated Auth0 integration, you will need to provide these inputs instead. | Input | Value | | ------------------------------ | ------------------------------- | | Auth0 Issuer URL | Your Auth0 tenant URL | | Auth0 Audience | Your Auth0 API identifier | | Auth0 Client ID - CTL API | Your Auth0 native app client ID | | Auth0 Client ID - Dashboard UI | Your Auth0 SPA client ID | ### GitHub Inputs | Input | Value | | -------------------- | ------------------------------ | | GitHub App Name | Name of your GitHub App | | GitHub App ID | ID of your GitHub App | | GitHub App Client ID | Client ID from your GitHub App | ### DNS Inputs | Input | Value | | ----------- | ---------------------------------------------------------------------------- | | Root Domain | Your custom domain, or `.nuon.run` for Nuon-provided domain | ### Secrets | Secret | Value | | ------------------------- | ---------------------------------------------- | | `github_app_key` | Your base64-encoded GitHub App PEM key | | `nuon_auth_client_secret` | OIDC client secret from your identity provider | ## Provision the Install Stack The Install Stack consists of the Nuon Runner and the foundational network resources it depends on. Once the Runner is provisioned it will take over the installation process and deploy the components of Nuon BYOC. We provide Terraform modules to provision and manage the Stack. The install process is the same on AWS and GCP: `install_id`, `inputs`, `secrets`, and `roles` are identical. Use `nuonco/stack/aws` or `nuonco/stack/gcp` for the platform you are installing into. See [Architecture](/guides/byoc#architecture) for platform-specific resources. ### Set Up the Stack Module Select or create a Terraform project to manage it in your preferred CI platform. Then, add and initialize the Nuon stack provider and module. Configure the region and install ID. ```hcl theme={null} terraform { required_providers { aws = { source = "hashicorp/aws" } stack = { source = "nuonco/stack" } } } provider "aws" { region = "" // [!code hl] } provider "stack" { api_url = "https://runner.nuon.co" } module "nuon_byoc_stack" { source = "nuonco/stack/aws" version = "~> 1.1" install_id = "" // [!code hl] inputs = {} secrets = {} roles = {} } ``` Configure the project, region, and install ID. ```hcl theme={null} terraform { required_providers { google = { source = "hashicorp/google" } stack = { source = "nuonco/stack" } } } provider "google" { project = "" // [!code hl] region = "" // [!code hl] } provider "stack" { api_url = "https://runner.nuon.co" } module "nuon_byoc_stack" { source = "nuonco/stack/gcp" version = "~> 1.1" install_id = "" // [!code hl] inputs = {} secrets = {} roles = {} } ``` Initialize the stack to ensure everything is configured correctly. ```sh theme={null} terraform init ``` ### Provide the Inputs and Secrets Fill out the inputs and secrets on the module. The maps below are the same on AWS and GCP. We recommend storing the secret values in a secret store, and providing them to the module via env vars or an ephemeral `*.auto.tfvars` file. ```hcl theme={null} module "nuon_byoc_stack" { // ... inputs = { github_app_client_id = "" // [!code ++:10] github_app_id = "" github_app_name = "" nuon_auth_allow_all_users = "false" nuon_auth_allowed_domains = "" nuon_auth_client_id = "" nuon_auth_issuer_url = "" nuon_auth_provider_type = "" read_only_enable_cluster_access = "false" read_only_role_arn = "" } secrets = { nuon_auth_client_secret = { value = var.nuon_auth_client_secret } // [!code ++:2] github_app_key = { value = var.github_app_key } } } variable "nuon_auth_client_secret" { // [!code ++:9] type = string sensitive = true } variable "github_app_key" { type = string sensitive = true } ``` ### Provision the Stack Apply to provision the Stack and install the Runner. Once the Runner is online, it will take over and complete the installation process. ```sh theme={null} terraform apply ``` ## Verify the Installation Once installation is complete, verify that you can reach the Nuon Dashboard in your browser, at `https://app.`. Also verify you can reach the API from the command line using curl. ```bash theme={null} curl https://api./health ``` # Optional Nuon BYOC Configuration Source: https://docs.nuon.co/guides/byoc/optional Additional configuration options for Nuon BYOC Once Nuon BYOC is installed, there is additional configuration you can optionally perform. Terraform snippets below use `nuonco/stack/aws`. On GCP, set `source = "nuonco/stack/gcp"`. `install_id`, `inputs`, `secrets`, `roles`, and `runner_enabled` are the same. ## Disable the Provision Role The Provision role provides permissions to create resources, which are not required after installation is complete. You can disable the Provision role to reduce the permissions the Runner has access to. ```hcl theme={null} module "nuon_byoc_stack" { // ... roles = { // [!code ++:3] provision = false } } ``` ## Disable the Runner Once Nuon BYOC is deployed, it does not require the Runner to operate. The Runner is only required to deploy updates, or perform triage during incidents. If you wish to completely cut off Nuon's access to your Nuon BYOC install, you can disable the Runner. This deprovisions the compute that hosts the Runner VM, so no instances are left running. ```hcl theme={null} module "nuon_byoc_stack" { // ... runner_enabled = false // [!code ++] } ``` You can disable and re-enable the runner at any time. To receive updates, or provide temporary access to triage issues, simply set `runner_enabled` back to `true`. ```hcl theme={null} module "nuon_byoc_stack" { // ... runner_enabled = false // [!code --] runner_enabled = true // [!code ++] } ``` ## Export Telemetry On AWS, the Stack creates the `nuon//telemetry-export-config` secret and grants the Runner read access. On GCP, the secret is named `-telemetry-export-config`. Update the secret to configure telemetry export to your own OTLP-compatible backend, starting with Runner audit logs. See [Export Runner Audit Logs](/guides/export-runner-audit-logs) for the configuration reference, cloud-specific steps, and verification guidance. ## Use your own S3 Bucket Nuon requires a public S3 bucket to host CloudFormation templates for customer AWS installs (Quick create links), including when the BYOC control plane runs on GCP. We provision a bucket for you during installation. To use a bucket you own, tell Nuon during onboarding — you will need an AWS account that can host that bucket. ## Use your own Slack App By default, Nuon will create a Slack app to power the Slack integration. To use your own Slack app instead, create it with the manifest below, then add the inputs and secrets to your stack module and apply. You can do this at first install or later. Create a Slack app using this manifest file, replacing `` with the domain you have chosen. ```json theme={null} { "display_information": { "name": "", "description": "Nuon BYOC Slack integration for ", "background_color": "#0b0b0f", "long_description": "Nuon BYOC posts deployment lifecycle events from your installs, sandboxes, runners, and actions into the Slack channels you choose. Subscribe per org, filter by interest (failures, components, sandboxes, runners, actions)." }, "features": { "bot_user": { "display_name": "Nuon BYOC", "always_online": true }, "slash_commands": [ { "command": "/nuon-byoc", "url": "https://slack./slack/commands/nuon", "description": "Manage Nuon BYOC notifications in Slack", "usage_hint": "subscribe [install] | unsubscribe | status | help", "should_escape": false } ] }, "oauth_config": { "redirect_urls": [ "https://slack./slack/oauth/callback" ], "scopes": { "bot": [ "chat:write", "chat:write.public", "channels:read", "groups:read", "team:read", "commands" ] }, "pkce_enabled": false }, "settings": { "event_subscriptions": { "request_url": "https://slack./slack/events", "bot_events": [ "app_uninstalled", "channel_archive", "channel_left", "channel_rename", "tokens_revoked" ] }, "interactivity": { "is_enabled": true, "request_url": "https://slack./slack/interactions", "message_menu_options_url": "https://slack./slack/interactions" }, "org_deploy_enabled": false, "socket_mode_enabled": false, "token_rotation_enabled": false, "is_mcp_enabled": false } } ``` Click **Create**. Add the following inputs and secrets to your stack module, then apply. ```hcl theme={null} module "nuon_byoc_stack" { // ... inputs = { // ... slack_client_id = "" // [!code ++:2] slack_oauth_redirect_url = "https://slack./slack/oauth/callback" } secrets = { // ... slack_client_secret = { value = var.slack_client_secret } // [!code ++:3] slack_signing_secret = { value = var.slack_signing_secret } slack_state_jwt_secret = { value = var.slack_state_jwt_secret } } } variable "slack_client_secret" { // [!code ++:17] type = string sensitive = true description = "Client secret from your Slack app." } variable "slack_signing_secret" { type = string sensitive = true description = "Signing secret from your Slack app." } variable "slack_state_jwt_secret" { type = string sensitive = true description = "High-entropy string used to sign the Slack OAuth state JWT (for example, openssl rand -hex 32)." } ``` For detailed instructions on configuring and using the Slack app, see the [Slack integration guide](/guides/slack). # Nuon BYOC Requirements Source: https://docs.nuon.co/guides/byoc/requirements Prerequisites for installing Nuon BYOC on AWS or GCP Before installing Nuon BYOC, you will need to prepare the following dependencies. ## Cloud Account You will need an AWS or GCP account to run Nuon BYOC in. The installation process is the same on both platforms. Platform-specific architecture is covered in [Architecture](/guides/byoc#architecture). If you are hosting Nuon BYOC on GCP, a public S3 bucket is still required to support CloudFormation Quick create links for customer AWS install stacks. By default, we will provision a bucket for you, but if you would like to own the bucket yourself, you will need an AWS account. ## DNS ### Root DNS In order to serve Nuon BYOC at your own domain — for example, `nuon.my-domain.com` — you will need a DNS zone to place the records in. Before creating the install, create the DNS zone `my-domain.com` in your preferred hosting provider, if it does not already exist. While provisioning the install stack, provide `nuon.my-domain.com` as the value for the **Root Domain** input. Once Nuon BYOC has fully provisioned, the nameservers for the install will be available in the outputs. Create an NS record named `nuon.my-domain.com` using the nameserver values from the install outputs. Once propagation is complete, your Nuon BYOC install will be available at `nuon.my-domain.com`. Nuon will provision the following subdomains under `nuon.my-domain.com`. Only the runner API needs to be public. The rest can be private to your internal network. | Subdomain | Service | Public | | --------- | ---------------------------------------------------------------------------- | --------------------- | | app | The vendor dashboard | | | api | The control plane API, used by the Vendor Dashboard and the CLI | | | admin | The admin API. Exposes functionality for administration of the control plane | | | runner | The API used by runners to communicate with the control plane | | | slack | The Slack integration API (optional) | | The Nuon Dashboard uses cookies for authentication, and they will be shared on all subdomains of the provided root domain. We strongly recommend creating a Nuon-specific subdomain to avoid leaking auth cookies. ### Install Delegation DNS (Optional) If you don't want your customers to have to set up DNS when installing your app, you can configure DNS delegation. A subdomain is provisioned for each install under a subdomain you control. Nuon Cloud, for example, creates a subdomain for each install at `.nuon.run` by default. For details, see [Custom Domains](/guides/custom-domains). ## GitHub App Create a GitHub App so Nuon can access your GitHub org. This allows it to access private repos and receive webhook events. Go to [GitHub App Settings](https://github.com/settings/apps) and click **New GitHub App**. Configure the app with the following settings. | Setting | Value | | ------------------ | ---------------------------------------- | | GitHub App name | Choose any name (e.g., "Nuon BYOC") | | Homepage URL | `https://app.` | | Setup URL | `https://app./connect` | | Redirect on Update | Checked | | Webhook | Unchecked | Configure the following permissions. Repository permissions | Permission | Access | | ------------- | -------------- | | Contents | Read-only | | Metadata | Read-only | | Pull requests | Read and write | Organization permissions | Permission | Access | | ---------- | -------------- | | Webhooks | Read and write | The **Webhooks** permission lets Nuon register a webhook to trigger [app branch runs](/concepts/app-branches). If you do not want to grant this access, you can instead trigger runs from your own CI using the [Nuon GitHub Action](/guides/github-actions). Under "Where can this GitHub App be installed?", select **Only on this account** (unless you need to access repos in other GitHub organizations). Click **Create GitHub App**. After creation, scroll to the bottom of the page and click **Generate a private key**. Save the PEM file to provide as a secret. The GitHub App PEM key must be base64 encoded to preserve newlines. ```bash theme={null} base64 -i your-github-app-key.pem ``` Save the **App ID** and **Client ID** from the app settings page to provide as inputs. ## Identity Provider Configure your preferred identity provider to control user access to Nuon. ### Google To use Google as your IdP, set up an OAuth client in the Google Cloud Console. Go to the [Google Cloud Console](https://console.cloud.google.com/) and create or select a project. Navigate to **APIs & Services** > **Credentials**. Click **Create Credentials** > **OAuth client ID** and select **Web application** as the application type. Configure the OAuth client. | Setting | Value | | ----------------------------- | -------------------------------------- | | Name | `Nuon BYOC` (or any name) | | Authorized JavaScript origins | `https://auth.` | | Authorized redirect URIs | `https://auth./auth` | Click **Create**. After creation, save the **Client ID** to provide as an input, and the **Client Secret** to provide as a secret. You will also need the **Issuer URL** for Google. It is always `https://accounts.google.com`. ### Okta To use Okta as your IdP, set up an OIDC Application in Okta. In the Okta Admin Console, navigate to **Applications** and create a new **OIDC application**. 1. For Sign-in method, select **OIDC - OpenID Connect** 2. For Application type, select **Web Application** Set the **Sign In Redirect** to `https://auth./auth` Set **Trusted Origins** to `` Click **Save**. After creation, save the **Issuer URL** and **Client ID** to provide as an input, and the **Secret** to provide as a secret. ### Auth0 (Legacy) This section does not document using Auth0 as a regular auth provider. Nuon BYOC previously required Auth0 for authentication. This is no longer the case. This documentation is retained for anyone still using the legacy Auth0 integration. New Nuon BYOC installs should use one of the IdP integrations documented above. To use Auth0 for authentication, you will need to configure an API, applications, and a custom action in your Auth0 tenant. Nuon provides a Terraform module to automate Auth0 configuration. We recommend this over manual configuration. Apply the following Terraform to use it. ```hcl theme={null} module "byoc_auth0" { source = "github.com/nuonco/byoc-auth0" # Your Auth0 tenant domain auth0_domain = "your-tenant.auth0.com" # The root domain for your BYOC install public_domain = "" # Your Nuon install ID install_id = "" install_name = "" } ``` After applying, the module outputs the values you will need for the install inputs. If you would prefer to configure Auth0 manually, follow the steps below. Add an action to enrich the access token with the user's email. 1. Go to **Actions > Library** in your Auth0 dashboard 2. Click **Create Action > Build from scratch** 3. Name it `AddScope` and select the latest runtime 4. Replace the code with: ```javascript theme={null} exports.onExecutePostLogin = async (event, api) => { const email = event.user.email; api.accessToken.setCustomClaim(`email`, email); }; ``` 5. Deploy the action 6. Go to **Actions > Triggers > Post Login** 7. Drag the `AddScope` action into the flow and save Create an API with the following settings. | Setting | Value | | ------------------------------------------ | ------------------------------- | | Name | `API Gateway ` | | Identifier | `api.` | | Maximum Access Token Lifetime | `2592000` | | Implicit/Hybrid Flow Access Token Lifetime | `86400` | | Allow Skipping User Consent | `true` | The Identifier must match your API URL exactly. It cannot be changed after creation. Create a Single Page Application for the Dashboard UI. | Setting | Value | | --------------------------------- | -------------------------------------------------- | | Name | `Nuon App - ` | | Allowed Callback URLs | `https://app./api/auth/callback` | | Allowed Logout URLs | `https://app.` | | Allowed Web Origins | `https://app.` | | Allow Cross-Origin Authentication | `true` | | Maximum Refresh Token Lifetime | `31557600` | | Allow Refresh Token Rotation | `true` | | Rotation Overlap Period | `0` | Create a Native Application for CLI authentication. | Setting | Value | | ------------------------------------ | ----------------------------------------- | | Name | `Nuon CTL API - ` | | Description | `For Nuon BYOC Install ` | | Allow Cross-Origin Authentication | `true` | | Device Code (Advanced > Grant Types) | Checked | # Updating Nuon BYOC Source: https://docs.nuon.co/guides/byoc/updates Update Nuon BYOC and the Stack There are two types of updates that apply to your Nuon BYOC install. 1. Updates to Nuon BYOC itself 2. Updates to the Nuon BYOC stack module ## Updating Nuon BYOC In most cases updates of the first type will require nothing from you. We will notify you when an update is ready, and deploy it on your approval. Occasionally, in the case of new features, there may be new inputs or secrets required. In those cases, we will notify you about the changes, but the Stack module will also provide actionable error messages if a required input or secret is missing. ## Updating the Stack The Stack does not have to be upgraded often. When it is required, though, you simply need to update the stack module version. We recommend using a minor version constraint for the module, currently `~> 1.1`. This way, running `terraform init -upgrade` will pull in new patch versions. We will notify you if a minor or major version update is needed. In those cases, bump the version on the module (`nuonco/stack/aws` or `nuonco/stack/gcp`) and run `terraform init -upgrade`. ```hcl theme={null} module "nuon_byoc_stack" { // ... version = "~> 1.0" // [!code --] version = "~> 1.1" // [!code ++] } ``` # CLI Extensions Source: https://docs.nuon.co/guides/cli-extensions Extend the Nuon CLI with community and first-party plugins. Extensions let you add new commands to the `nuon` CLI. Once installed, an extension called `policies` is available as `nuon policies`, just like a built-in command. Extensions are generally available and do not require preview flags. The extension system was heavily inspired by [GitHub CLI extensions](https://docs.github.com/en/github-cli/github-cli/creating-github-cli-extensions). ## Available extensions | Extension Name | Description | Repository | | -------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | api | API client for the Nuon public API | [nuonco/nuon-ext-api](https://github.com/nuonco/nuon-ext-api) | | render | Render app config templates using install state from the Nuon API | [nuonco/nuon-ext-render](https://github.com/nuonco/nuon-ext-render) | | cf-stack | Manage CF stack install and upgrade workflows | [nuonco/nuon-ext-cf-stack](https://github.com/nuonco/nuon-ext-cf-stack) | | starship | Starship prompt module showing current Nuon org, install, and environment | [nuonco/nuon-ext-starship](https://github.com/nuonco/nuon-ext-starship) | | terraform | Drop into a Terraform shell for Nuon-managed sandbox and component workspaces | [nuonco/nuon-ext-terraform](https://github.com/nuonco/nuon-ext-terraform) | | ctx | Switch between nuon CLI configurations | [nuonco/nuon-ext-ctx](https://github.com/nuonco/nuon-ext-ctx) | | overlays | Apply Kustomize-style config overlays to Nuon app configurations | [nuonco/nuon-ext-overlays](https://github.com/nuonco/nuon-ext-overlays) | ## Compiled vs interpreted extensions Extensions come in two flavors: * **Compiled extensions** ship precompiled binaries via GitHub Releases. The CLI downloads the correct binary for your platform during install. This is the standard distribution model for production extensions. * **Interpreted extensions** are cloned as source and run directly. The CLI supports two interpreted types: * **Script:** a single executable file (bash, Python, etc.) with a shebang line * **Python:** a [uv](https://docs.astral.sh/uv/)-managed project executed via `uv run` The extension type is auto-detected during installation, you don't need to declare it. The CLI checks for GitHub Release assets first; if none match your platform, it clones the repo and detects the type from the directory contents. ## Quick start ```sh theme={null} # Browse available extensions nuon ext browse # Install an extension nuon ext install nuonco/nuon-ext-policies # Install at a specific branch, tag, or commit nuon ext install nuonco/nuon-ext-api@main # Install from a local directory (for development) nuon ext install ./nuon-ext-my-tool # Run it nuon policies --help ``` ## Managing extensions All extension management commands live under `nuon extensions` (alias: `nuon ext`). ### Browsing available extensions ```sh theme={null} nuon ext browse ``` This queries the `nuonco` GitHub organization for repositories matching the `nuon-ext-*` naming convention and shows which ones you already have installed. Extensions can be installed from any GitHub organization, but `browse` lists Nuon-authored extensions by default. Browse results are filtered to repositories with the `nuon-extensions` GitHub topic. ### Installing an extension ```sh theme={null} nuon ext install ``` You can provide the extension name in several formats: | Input | Resolved to | | --------------------------- | ----------------------------------------- | | `policies` | `nuonco/nuon-ext-policies` (latest) | | `nuon-ext-policies` | `nuonco/nuon-ext-policies` (latest) | | `nuonco/nuon-ext-policies` | `nuonco/nuon-ext-policies` (latest) | | `myorg/nuon-ext-foo` | `myorg/nuon-ext-foo` (latest) | | `nuonco/nuon-ext-demo@main` | `nuonco/nuon-ext-demo` at branch `main` | | `demo@v1.0.0` | `nuonco/nuon-ext-demo` at tag `v1.0.0` | | `demo@abc123` | `nuonco/nuon-ext-demo` at commit `abc123` | | `./nuon-ext-my-tool` | Local directory (symlink) | Shorthand names (without an org prefix) default to the `nuonco` GitHub organization. To install an extension from another org, use the full `org/nuon-ext-name` format. #### How install works The install behavior depends on the extension type: * **Compiled extensions:** The CLI checks the latest GitHub Release for a platform-specific binary asset (e.g. `nuon-ext-policies-darwin-arm64`). It also supports release archives like `nuon-ext-policies-darwin-arm64.tar.gz` and `nuon-ext-policies-darwin-arm64.zip`. If a match is found, it downloads and installs the binary directly. No clone is needed. * **Interpreted extensions:** If no release asset matches your platform, the CLI clones the repository and detects the extension type from the directory contents (`pyproject.toml` for python, executable `nuon-ext-` for script). When `@ref` is specified (e.g. `demo@main`, `nuonco/nuon-ext-demo@v1.0`), the CLI first checks for a release at that tag and installs a matching binary asset if one exists. If no matching release asset is found (for example when using a branch name or commit SHA), it clones the repo at that exact ref. #### Local install ```sh theme={null} nuon ext install ./path/to/nuon-ext-my-tool ``` Local installs create a symlink from the extensions directory to your source directory. This enables a live development loop: rebuild the binary or edit a script and the changes take effect immediately. The directory must contain a `nuon-ext.toml` and the directory name must match `nuon-ext-`. If the extension is already installed, the command fails. For local development installs, remove and re-install from source to refresh the symlinked install. ### Listing installed extensions ```sh theme={null} nuon ext list ``` Shows a table of installed extensions with their name, version, repository, and description. Use `--json` for machine-readable output. ### Upgrading extensions ```sh theme={null} # Upgrade a specific extension nuon ext upgrade policies # Force re-download even if already at the latest version nuon ext upgrade policies --force # Upgrade all installed extensions nuon ext upgrade ``` Upgrade checks for newer GitHub releases and re-downloads the binary if a new version is available. This currently only works for compiled extensions installed from a release. Interpreted extensions installed via clone should be re-installed to pick up changes. ### Removing an extension ```sh theme={null} nuon ext remove policies ``` This deletes the extension and its local files. For locally installed extensions (symlinks), the symlink is removed but the source directory is left untouched. ### Running an extension explicitly ```sh theme={null} nuon ext exec policies [args...] ``` The `exec` subcommand is an escape hatch for cases where an extension name conflicts with a built-in command. In most cases, you can run extensions directly as top-level commands (e.g. `nuon policies`). ## How extensions work ### Top-level commands When the CLI starts, it scans the extensions directory and registers each installed extension as a top-level command. This means `nuon policies` works the same as `nuon ext exec policies`. When using top-level extension commands, Nuon root flags (for example `--config` / `-C`) are handled by the CLI, and only arguments after the extension command are forwarded to the extension process. ### Environment variables When running an extension, the CLI provides context through environment variables: | Variable | Description | | ------------------ | --------------------------------------- | | `NUON_API_URL` | API endpoint URL | | `NUON_ORG_ID` | Current organization context | | `NUON_APP_ID` | Current app context (if set) | | `NUON_INSTALL_ID` | Current install context (if set) | | `NUON_API_TOKEN` | Authentication token | | `NUON_EXT_NAME` | Extension name | | `NUON_EXT_DIR` | Path to the extension's local directory | | `NUON_CONFIG_FILE` | Path to the Nuon config file | Extensions can use these variables to interact with the Nuon API or read the local configuration. These values are populated from your active CLI config/context. ### Authentication Extensions declare their auth requirements in a `nuon-ext.toml` manifest. If an extension requires an API token or org context and one isn't configured, the CLI prints a warning before running the extension. The extension still runs and is allowed to handle the missing credentials on its own. ### Local storage Extensions are stored under `~/.config/nuon/extensions/`. Each extension gets its own directory: ``` ~/.config/nuon/extensions/ ├── nuon-ext-policies/ # compiled - downloaded binary only │ ├── nuon-ext-policies # platform binary │ ├── nuon-ext.toml # cached manifest │ └── manifest.json # install metadata ├── nuon-ext-gen-readme/ # interpreted - full repo clone │ ├── pyproject.toml # project definition │ ├── nuon-ext.toml # manifest (from repo) │ ├── manifest.json # install metadata │ └── src/ # source code ├── nuon-ext-my-tool -> /home/user/... # local install (symlink) ``` ## Creating an extension Every extension needs two things: a GitHub repository named `nuon-ext-` and a `nuon-ext.toml` manifest at the root. Beyond that, the approach differs depending on whether you're building a compiled or interpreted extension. ### Repository name The repository must use the `nuon-ext-` prefix. The rest becomes the command name: ``` nuonco/nuon-ext-policies → nuon policies nuonco/nuon-ext-cost-report → nuon cost-report ``` ### Extension manifest Every extension repository must include a `nuon-ext.toml` file at the root: ```toml nuon-ext.toml theme={null} [extension] name = "policies" description = "Check deployment health across installs" min_cli_version = "1.5.0" [extension.auth] requires_token = true requires_org = true ``` | Field | Required | Description | | ------------------------------- | -------- | -------------------------------------------- | | `extension.name` | Yes | Must match the repo suffix after `nuon-ext-` | | `extension.description` | Yes | Shown in `list` and `browse` output | | `extension.min_cli_version` | No | Minimum CLI version required | | `extension.auth.requires_token` | No | Warn if no API token is configured | | `extension.auth.requires_org` | No | Warn if no org is selected | ### Compiled extensions (binary) Compiled extensions ship precompiled binaries for each platform via GitHub Releases. This is the best choice when you want fast startup, no runtime dependencies, and a clean upgrade path via `nuon ext upgrade`. Create GitHub releases with assets following this naming scheme: ``` nuon-ext---[.exe] ``` Archive variants are also supported with the same base name: ``` nuon-ext---[.exe].tar.gz nuon-ext---[.exe].zip ``` For example: ``` nuon-ext-policies-darwin-arm64 nuon-ext-policies-darwin-amd64 nuon-ext-policies-linux-amd64 nuon-ext-policies-linux-arm64 nuon-ext-policies-windows-amd64.exe ``` The CLI automatically selects the correct binary for the user's platform during install. You can use any language and build system. [GoReleaser](https://goreleaser.com/) works well for Go extensions. ### Interpreted extensions (script and python) Interpreted extensions are cloned as source and run directly. They're simpler to set up and great for tooling that doesn't need to be compiled. For example: documentation generators, linters, config validators, and similar utilities. The CLI supports two interpreted types: | Type | Detection | Execution | Requires | | ---------- | ----------------------------------------- | ------------------------ | -------------------------------------------- | | **script** | Executable `nuon-ext-` at repo root | Run script directly | Nothing extra | | **python** | `pyproject.toml` at repo root | `uv run nuon-ext-` | [uv](https://docs.astral.sh/uv/) on the host | #### Script extensions Place a single executable file named `nuon-ext-` at the repo root with a shebang line: ```bash nuon-ext-hello theme={null} #!/usr/bin/env bash echo "Hello from $NUON_EXT_NAME" ``` The script receives the same environment variables as any other extension. Make sure the file is marked executable (`chmod +x`). #### Python extensions Python extensions use a standard `pyproject.toml` with a console script entry point. The CLI runs them via `uv run nuon-ext-`, so [uv](https://docs.astral.sh/uv/) manages the virtualenv and dependencies automatically. A minimal project structure: ``` nuon-ext-my-tool/ ├── nuon-ext.toml ├── pyproject.toml └── src/ └── nuon_ext_my_tool/ ├── __init__.py └── cli.py ``` With a `pyproject.toml` entry point like: ```toml theme={null} [project.scripts] nuon-ext-my-tool = "nuon_ext_my_tool.cli:main" ``` We recommend [click](https://click.palletsprojects.com/) for building the CLI interface. Users install interpreted extensions with `@ref` to pin a branch: ```sh theme={null} nuon ext install myorg/nuon-ext-my-tool@main ``` # Component Dependencies Source: https://docs.nuon.co/guides/component-dependencies Component dependencies allow you to create dependencies between components, and model your app as a graph. ## Why Dependencies? Most applications have either explicit or implicit dependencies between the different components that comprise them. For example, given an app that comprises a [Helm chart](/guides/helm-chart-components) component and a [Terraform component](./terraform-components): ```toml components/helm.toml theme={null} # helm name = "helm" type = "helm_chart" chart_name = "helm" dependencies = ["database"] [connected_repo] directory = "components/helm" repo = "/" branch = "main" [values] database_url = "{{.nuon.components.database.outputs.database_url}}" ``` ```toml components/database.toml theme={null} # terraform name = "database" type = "terraform_module" terraform_version = "1.11.3" [connected_repo] directory = "components/database" repo = "/" branch = "main" ``` In this example, the `helm` chart requires that the database be provisioned *before* it is deployed. Otherwise, the database will not exist yet, and the `database_url` output will not be defined. When deprovisioning the install, the `helm` chart must be deprovisioned *before* the database, to ensure no active dependencies exist, preventing the database from being shut down. ## Defining Component Dependencies To support this, you can define one or more dependencies on each component, telling Nuon what other components it relies on. Nuon will use your configuration to build a dependency graph, to ensure components are provisioned and deprovisioned in the correct order. The dependency graph is directed and *acyclic*, which means circular dependencies are not supported. Nuon checks for cycles and will error if you attempt to define one. Dependencies can be explicitly defined using the `dependencies` field in each component's configuration file. You can specify which components a given component depends on by listing their names in the `dependencies` array. Taking the example above, you can make the Helm chart depend on the database by adding it to the dependencies list. With the directory-based approach, you can specify dependencies explicitly in each component file: ```toml components/database.toml theme={null} # terraform name = "database" type = "terraform_module" terraform_version = "1.11.3" [connected_repo] directory = "components/database" repo = "/" branch = "main" ``` ```toml components/helm.toml theme={null} # helm name = "helm" type = "helm_chart" chart_name = "helm" dependencies = ["database"] [connected_repo] directory = "components/helm" repo = "/" branch = "main" [values] database_url = "{{.nuon.components.database.outputs.database_url}}" ``` ## Install Provisioning When an install is provisioned, Nuon will generate a graph of the app based on the defined dependencies. Nuon will automatically deploy the *latest* build of each component to the install. In the previous example, this means that the database component would be deployed *before* the Helm chart. For example, when you create a new install, each component will automatically be deployed. To check the status of each component on the install: ```sh theme={null} nuon installs components ``` ## Install Deprovisioning When an install is deprovisioned, Nuon will generate a graph of all the deployed components on the install, and walk them in reverse, to deprovision them properly. In the previous example, this means the helm component would be deprovisioned *before* the database. ## Toggleable components If a component is [toggleable](/guides/toggleable-components), its enabled state has to stay consistent with the dependency graph: an enabled component cannot depend on a disabled one, and a component cannot be disabled while a dependent is still enabled. Nuon validates this on every sync. See the [Toggleable Components guide](/guides/toggleable-components#dependencies) for details. # Component Health Source: https://docs.nuon.co/guides/component-health Know whether every component on every install is running right now — not whether its last deploy worked. Alerts, deploy gates, and uptime history included. Your deploy succeeded. Is your app actually serving? Those are different questions, and only one of them is what your customer asks you. Deploy status tells you the last apply worked — `helm upgrade` can exit 0 while the release sits in `pending-install` and nothing serves traffic. Terraform and Helm skip when state already matches, so a component reports Active while its pods are stuck. Something fixed by hand out of band keeps showing yesterday's error. Component health answers the live question instead — continuously, per component, on every install. It's a **separate axis**: Nuon never overwrites deploy status with health or the other way round. Every resource each component manages, with live status, browsable per install. Debounced alerts to Slack and webhooks, with the failing resource and why. A deploy isn't done when `helm` exits — it's done when the component stays healthy. 90 days of timeline and uptime per component and install. ## Turn it on Component health is behind the `component-health` org feature. Ask us to switch it on for your organization — there's nothing to install and no agent runs in your customers' clusters. Health reads your customers' clusters using the same access your deploys already use. An install that hasn't deployed since the feature was enabled needs one deploy — or click **Refresh cluster access** on the install's health card to pick it up immediately. Within a minute or two you'll see every resource each component manages, with per-resource health, filterable by component, kind, namespace and status. No configuration is required for any of this. Everything below is optional and only extends what already happens. ## What you get with no configuration Helm chart and Kubernetes manifest components are checked automatically, about once a minute. **Standard workloads** — Deployments, StatefulSets, DaemonSets, Pods, Services, PersistentVolumeClaims, Ingresses and Jobs — plus the Helm release's own status. **Your custom resources too.** Nuon reads the kinds your chart or manifest actually ships, so a `Certificate`, an `Issuer`, a Karpenter `NodePool` or your own CRD is watched like anything else. A component that starts shipping a new kind picks it up on its next deploy or drift check. **Controller-side failures.** Some failures never appear in an object's own status — an Ingress whose load balancer rejected a certificate looks fine if you only read the Ingress. Nuon reads Kubernetes warning events too, so those surface as well. **Terraform components** get their cloud resources listed so you can see what a component owns. Those rows are inventory, not assessment, so they don't move the verdict on their own — [add a probe or a custom check](#choosing-a-check) to give a Terraform component a real verdict. ## How a verdict is reached ```mermaid theme={null} flowchart LR v{"Component verdict
debounced"} k8s["Kubernetes resources
automatic"] --> v probes["Probes
http, tcp, exec"] --> v custom["Custom checks
your systems"] --> v v --> badge["Health badge
and Resources tab"] v --> alerts["Slack and webhooks"] v --> gate["Deploy gate
opt-in"] v --> history["Timeline and uptime"] ``` The worst thing observed sets the verdict. A component is only as healthy as its unhappiest part. | Verdict | Meaning | | ---------------- | ------------------------------------------------------------------------- | | `healthy` | Everything Nuon observes is fine. | | `progressing` | A rollout is in flight and can still converge. Not yet a problem. | | `degraded` | Something is wrong but the component is partly serving. | | `unhealthy` | The component is not serving. | | `unknown` | No fresh observations — usually the runner is offline. **Not a failure.** | | `not-applicable` | Nothing observable here, or the component has never deployed. | Two rules are worth knowing, because they're why you won't get paged at 3am for nothing: **Verdicts are debounced.** Three consecutive bad observations flip a component bad; two good ones bring it back. A single pod restart or a controller backing off doesn't alert. **Absence of data is never failure.** If observations stop for five minutes the verdict becomes `unknown` — visually distinct from a failure, and it never alerts. Nuon doesn't guess in either direction. A rollout that *can't* converge is reported as `degraded`, not `progressing` — a bad image tag or an unschedulable pod surfaces straight away rather than waiting out the Kubernetes progress deadline. And anything stuck `progressing` for 30 minutes is treated as `degraded`, because at that point it isn't progressing. ## Choosing a check Three ways to add your own signal. They compose freely — most components need none of them. | | Use it when | Runs where | Can gate a deploy | | ------------------ | ----------------------------------------------------------------- | --------------------------- | --------------------------- | | **Probe** | Nuon can reach the thing — an endpoint, a port, a command | Nuon, every cycle | Yes | | **Custom check** | Only *your* systems know — CI, a business metric, a nightly audit | Your systems, on your clock | Yes, with `required_checks` | | **Required check** | A deploy must not finish until an external system says so | — | That's its whole job | **Probes work on every component type, including Terraform.** You don't need a Kubernetes footprint, and you don't need to write an action. If Nuon can reach it over HTTP, TCP, or a command, a probe is the simplest thing that works. ## Probes Probes assert your app is actually serving, above the level of infrastructure. A Deployment can be perfectly ready while the thing inside it returns 500. ```toml Kubernetes component theme={null} [[components.api.health.probes]] type = "http" url = "https://{{.nuon.install.sandbox.outputs.public_domain}}/healthz" [[components.api.health.probes]] type = "tcp" url = "db.internal:5432" [[components.api.health.probes]] type = "exec" name = "migrations-current" command = ["/usr/local/bin/check-migrations", "--strict"] ``` ```toml Terraform component theme={null} # An EC2 instance has no Kubernetes status to read, so give it a probe. # Reference the module's own output. [[components.ec2.health.probes]] type = "http" url = "http://{{.nuon.components.ec2.outputs.public_ip}}/status/200" ``` * **`http`** passes on 2xx/3xx and never follows redirects. * **`tcp`** passes if the port accepts a connection. * **`exec`** passes on exit code 0 and reports the command's output when it fails. `exec` commands are an argv, never a shell string, and run with a minimal environment (`PATH`, `HOME`, `TMPDIR`) — they can't see the runner's credentials. Every probe is bounded by a short timeout and runs once per cycle. Probe results are ordinary observations: same verdict, same debounce, same alerts, same history. Templated targets are resolved from the install's state, so a probe pointing at a component's own output can't resolve until that component has applied at least once. Until then the probe reports `unknown` and says so — it stays visible rather than silently vanishing, and it never fails a deploy. ## Custom checks For anything Nuon can't reach — a business metric, a queue depth, something your CI already computed — push it in and it becomes part of the component's health. ```bash theme={null} curl -X PUT \ "https://api.nuon.co/v1/installs/$INSTALL_ID/components/$COMPONENT_ID/health/checks/checkout-latency" \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -d '{"status":"degraded","message":"p99 1.8s over 1.2s budget"}' ``` `status` is one of `healthy`, `degraded`, `unhealthy`, `unknown`. Names are 1–100 characters of letters, digits, dots, dashes or underscores. **A custom check can make a component worse, never better.** Reporting `healthy` will not paper over crash-looping pods. **Say how often you'll report.** A check's last value stands for 5 minutes by default. If yours reports less often, set `stale_after` (up to `60m`): ```bash theme={null} -d '{"status":"healthy","stale_after":"30m","message":"nightly audit clean"}' ``` Past its window a check reads `unknown` rather than keeping its last answer — it stays visible but stops voting, because a stale answer isn't an answer. ## Required checks Sometimes a deploy shouldn't be considered finished until something outside Nuon confirms it — migrations applied, a smoke suite green, a canary analysis passed. List those by name and the deploy waits for them, the same way a GitHub branch rule waits for a required status check. ```toml theme={null} [components.api.health] block_deploy = true required_checks = ["migrations-applied", "smoke-tests"] ``` Each name must be [pushed as a custom check](#custom-checks) **after the apply finishes** and be healthy when the window closes. Nuon can't produce these itself — that's the point — so something in your pipeline has to push them: ```bash theme={null} curl -X PUT ".../health/checks/migrations-applied" \ -H "Authorization: Bearer $NUON_API_TOKEN" -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -d '{"status":"healthy","message":"schema at revision 41"}' ``` If a required check never reports inside the window, the deploy **fails** — it doesn't wait forever. Give your pipeline room by lengthening `stabilization_window`, rather than expecting the gate to wait longer on its own. Every required check starts the window as `unknown` and only counts once it reports, so a value left over from a previous deploy can't satisfy the gate for this one. ### Example: gate a deploy on a k6 smoke suite Say you want no deploy to count as finished until k6 has run a smoke suite against the install and Datadog confirms error rate is within budget. Both are things only your systems know, so both are custom checks — and both are listed as required. ```toml theme={null} [components.api.health] block_deploy = true stabilization_window = "10m" # room for the suite to run required_checks = ["smoke-tests", "error-budget"] ``` ```mermaid theme={null} sequenceDiagram participant N as Nuon participant CI as Your CI participant K as k6 / Datadog N->>N: apply finishes N->>N: verify health starts
both checks unknown Note over N: deploy is held here CI->>K: run smoke suite, query error budget K-->>CI: results CI->>N: PUT smoke-tests = healthy CI->>N: PUT error-budget = healthy N->>N: both reported in-window,
resources healthy N->>N: window closes, deploy succeeds ``` Your pipeline pushes the results whenever they're ready: ```bash theme={null} BASE="https://api.nuon.co/v1/installs/$INSTALL_ID/components/$COMPONENT_ID/health/checks" AUTH=(-H "Authorization: Bearer $NUON_API_TOKEN" -H "X-Nuon-Org-ID: $NUON_ORG_ID") k6 run smoke.js \ && curl -X PUT "$BASE/smoke-tests" "${AUTH[@]}" \ -d '{"status":"healthy","message":"42 checks passed"}' \ || curl -X PUT "$BASE/smoke-tests" "${AUTH[@]}" \ -d '{"status":"unhealthy","message":"checkout flow failed"}' ``` Push the failure too, rather than staying silent — a reported failure fails the deploy immediately with a reason attached, where silence just waits out the window and fails with "never reported". The same pattern works with anything that can make an HTTP call: a Datadog monitor webhook, a Grafana alert, a GitHub Action, an Argo workflow step, a nightly audit job. Nuon doesn't care what produced the answer — only that it arrived after the apply and inside the window. ## Alerting Health transitions fan out through webhooks and Slack like any other Nuon event. Subscribe with a single per-resource flag: * **`component_health`** on `components` — delivers both directions, so a channel that hears about a failure always hears the recovery. * **`install_degraded`** on `installs` — the install-level rollup crossing. Usually you don't need this as well: when a component's failure is what moved the install, the component alert reports both in one message. Two events below: the component degrading, then recovering a couple of minutes later. The failing resource and the reason travel with the alert, so the first thing you read is already the diagnosis. Notice that each message covers the component **and** the install. When a component's crossing is what moved the install rollup, the headline says `install degraded` and an **Install health** field carries `healthy → degraded` — rather than a second, separate install message arriving alongside it. Two Slack messages from Nuon Cloud. The first is titled Component degraded, install degraded, ctl_api, and shows the component, its previous healthy verdict, the failing Deployment and a readiness probe connection-refused error. The second is titled Component recovered, install recovered, and shows all 52 resources healthy. Three things are deliberately quiet: * **`unknown` never alerts.** A runner going offline is already reported as runner inactivity, and would otherwise page you once per component. * **Only root causes alert.** When a component fails because something it depends on failed, the dependents are labelled `downstream of ` and stay silent. One outage, one alert. * **Recoveries are always paired** with the failure that preceded them. See [Webhooks](/guides/webhooks) for the full subscription model. ## Verified deploys By default a deploy finishes when the apply succeeds. Set `block_deploy` and it finishes only once the component has held `healthy` for `stabilization_window` afterwards. ```toml theme={null} [components.api.health] stabilization_window = "3m" # default 3m, max 1h block_deploy = true # default false ``` The gate appears as its own **verify health** step, showing what it's watching and what's still outstanding. Only observations from inside the window count, so a component that was already healthy still has to prove it survived the change — and a deploy that *fixes* a broken component passes as soon as its own observations come back healthy. This is off by default, and it's the only way health can affect whether a deploy passes. A component Nuon can't observe never blocks a deploy. **A gate must only assert what the component itself provides.** Put a probe for a public endpoint on the load balancer component that exposes it, not on the app behind it. If a component's gate depends on a *downstream* component, a first install deadlocks: the app's gate can't pass until the load balancer exists, and the load balancer never deploys because the app's gate is holding the workflow. ## Canary and bake periods The pieces above compose into progressive delivery. The primitive is a fleet health summary scoped by install label: ```bash CLI theme={null} nuon installs health --labels tier:canary --output agent ``` ```bash API theme={null} curl "https://api.nuon.co/v1/installs/health?labels=tier%3Dcanary" \ -H "Authorization: Bearer $NUON_API_TOKEN" -H "X-Nuon-Org-ID: $NUON_ORG_ID" ``` ```jsonc theme={null} { "total": 3, "healthy": 3, "degraded": 0, "unhealthy": 0, "unknown": 0, "unset": 0, "all_healthy": true, "installs": [ { "install_id": "…", "health": "healthy", "unhealthy_components": 0 } ] } ``` A rollout then reads: deploy to `tier=canary`, hold until `all_healthy` has stayed true for your bake period, then continue to the rest of the fleet. ```bash theme={null} # bake for 10 minutes, requiring health the whole way for i in $(seq 1 20); do ok=$(nuon installs health --labels tier:canary --output agent | jq -r '.data.all_healthy') [ "$ok" = "true" ] || { echo "canary unhealthy, halting rollout"; exit 1; } sleep 30 done ``` `all_healthy` is never true unless at least one install was actually evaluated, and installs that have never been evaluated are counted separately in `unset`. A rollout must never read "no data" as a pass, so the endpoint won't let it. ## Uptime and incidents Every debounced transition is kept for 90 days, giving each component and install a timeline and an uptime percentage, drawn as status-page-style daily bars. The badge and the percentage answer different questions: the badge is the verdict **right now**, the percentage is the **last 90 days**. A component reading `Healthy` at 97.57% is currently fine and had a bad spell earlier — and because an install is degraded whenever any of its components is, that one component is what sets the install's number. A 90-day health card showing 97.57% uptime, a row of daily status bars, and a list of components each with a Healthy badge and its own uptime percentage. One component, nginx_gateway, reads 97.57% while the rest read 100%. **Time in `unknown` is excluded from uptime** rather than counted as up or down — Nuon won't claim availability it didn't observe. An install with no observations reports zero observed time instead of a misleading 100%. There's also an incident bundle per component, pulling together the failing transition, the captured diagnosis (Kubernetes events, restart counts, termination reasons like `OOMKilled`) and the deploy it followed — useful as input to a runbook or an agent. ## Troubleshooting Nuon can't reach the cluster. This is almost always an install that hasn't deployed since component health was enabled. Click **Refresh cluster access** on the install's health card, or run any deploy. Resources should appear within a minute or two. That's `not-applicable` — either the component has no observable runtime footprint (a Terraform component with no probe, a build-only component), or it has never deployed. Add a [probe or a custom check](#choosing-a-check) to give it a verdict. Kinds are read from what your chart or manifest actually ships, and are picked up on deploy. If you've just added a CRD, deploy the component once — or wait for its next drift check, which also refreshes them without applying anything. Its window expired. Set `stale_after` to match how often you actually report, up to `60m`. That's the debounce: three bad observations to flip, two good to recover, at roughly one observation a minute. It's what stops a single restart paging anyone. It has to be pushed *after* the apply finishes, and be healthy when the window closes. A value pushed before the deploy started doesn't count for that deploy. # Component Overrides Source: https://docs.nuon.co/guides/component-overrides Override Helm values and Terraform variables on a per-install basis, without changing your app config. Component overrides let you customize a single install's [Helm](/guides/helm-chart-components) values or [Terraform](/guides/terraform-components) variables without touching your app config. They're defined per-install, so one customer can get a different chart configuration or a different set of Terraform variables while every other install keeps the app defaults. Use them for one-off customizations that don't belong in your app config, such as bumping a replica count for a customer under load, pinning a region for a specific install, or applying a temporary support tweak, where authoring a new input or editing the shared component would be overkill. Overrides are configured on the **install**, not the component. The component's app config stays the source of truth for every other install. ## Configuring overrides Overrides live in the install config under a `[components.]` block, keyed by the component's name. Each component supports exactly one override field, matching its type: | Component type | Field | Format | Behavior | | ---------------- | ------------- | -------------------- | ----------------------------------------------------------------------------- | | Helm chart | `helm_values` | YAML | Deep-merges over the component's app-config values; wins on overlapping keys. | | Terraform module | `tf_vars` | `.tfvars` (HCL/JSON) | Appended as the final, highest-precedence `-var-file`. | ### Helm values `helm_values` is a raw YAML string that deep-merges over the Helm component's app-config values. Overlapping keys win, and keys you don't mention are left untouched. ```toml install.toml theme={null} name = "customer-acme" [components.clickhouse] helm_values = """ replicas: 3 resources: limits: memory: 4Gi """ ``` ### Terraform variables `tf_vars` is a raw `.tfvars` string (HCL or JSON) appended as the final `-var-file` at deploy time, so it wins over the component's `vars` map and any `var_files`. The variables must already be declared in the module. ```toml install.toml theme={null} name = "customer-acme" [components.vpc] tf_vars = """ cidr = "10.0.0.0/16" """ ``` You can set overrides for multiple components in the same install config: ```toml install.toml theme={null} name = "customer-acme" [components.vpc] tf_vars = """ cidr = "10.0.0.0/16" """ [components.clickhouse] helm_values = """ replicas: 3 """ ``` ## Precedence Overrides are the highest-precedence layer. For Helm, they deep-merge on top of the app-config values and win on any key they set. For Terraform, they're applied as the last `-var-file`, so they win over the module's `vars` and `var_files`. ## Applying overrides Overrides are applied by syncing the install config with the CLI: ```bash theme={null} nuon installs sync --file install.toml --app-id --confirm ``` The CLI validates override syntax locally before making any API calls: `helm_values` must be valid YAML and `tf_vars` must be valid HCL. Because an override changes a component's deploy config, syncing it triggers a redeploy of the affected component. ## Clearing an override To revert a component to its app-config values, remove its `[components.]` block (or the individual field) from the install config and sync again. The next sync clears the override and redeploys the component with the app defaults. # Configuring Inputs & Secrets Source: https://docs.nuon.co/guides/configuring-inputs-and-secrets Define and use inputs, variables, and secrets in your app. ## Inputs Inputs are defined in an `inputs.toml` file in the root of your app directory: ```toml inputs.toml theme={null} [[group]] name = "dns" description = "DNS configuration" display_name = "DNS" [[input]] name = "root_domain" description = "The root domain for the install." default = "app.example.com" display_name = "Root Domain" group = "dns" required = true ``` Use inputs in sandbox or component config: ```toml sandbox.toml theme={null} [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" ``` ### Customer-Facing Inputs By default, inputs are vendor-facing and set through the dashboard. To allow customers to set an input directly through the install stack (CloudFormation, Azure Resource Manager (Bicep), Terraform), mark it as `user_configurable`: ```toml inputs.toml theme={null} [[input]] name = "sub_domain" display_name = "Sub Domain" description = "The sub domain for the service." default = "whoami" user_configurable = true ``` Customer-facing inputs can only be modified by the customer through the install stack. Changes trigger redeployment of dependent components. ### Input Groups Inputs can be organized into groups to control how they are displayed in the dashboard during install creation: ```toml inputs.toml theme={null} [[group]] name = "db" description = "Database configuration" display_name = "Database" [[input]] name = "db_password" description = "Password for the database." sensitive = true display_name = "Password" group = "db" ``` ## Variables Variables are managed through the CLI: ```sh theme={null} nuon apps variables create --name=external_api_key --value=your-api-key nuon apps variables list nuon variables delete --id=app_variable_id ``` Variables are accessed in config using `{{ .nuon.app.variables. }}`. If you add or update a variable after an install has been created, the install's state must be refreshed before the new value is available. Running a deploy or sandbox provision triggers a state update. ## Secrets Secret metadata is defined in a `secrets.toml` file. The actual values are entered by the customer when deploying the install stack: ```toml secrets.toml theme={null} [[secret]] name = "github_app_key" display_name = "GitHub App Key" description = "Base64 encoded GitHub App Key" required = true format = "base64" [[secret.kubernetes_sync_targets]] namespaces = ["api", "workers"] name = "github" key = "app-key" ``` Setting `[[secret.kubernetes_sync_targets]]` syncs the secret as a Kubernetes Secret object after sandbox provisioning, using the details provided. This approach allows for easy definition of secrets in whatever shape is desired and enables reflection of secrets across different namespaces. We support multiple `[[secret.kubernetes_sync_targets]]` entries. We also support the following method which is less flexible: ```toml secrets.toml theme={null} [[secret]] name = "github_app_key" display_name = "GitHub App Key" description = "Base64 encoded GitHub App Key" required = true format = "base64" kubernetes_sync = true kubernetes_secret_namespace = "control-plane" kubernetes_secret_name = "github-app-key" ``` This approach creates or upserts the secret into a secret with the secret in a `value` key. ### Using Secrets in Components Secrets are referenced as outputs from the install stack: ```toml components/database.toml theme={null} [vars] secret_arn = "{{ .nuon.install_stack.outputs.rds_secret_arn }}" ``` In Helm charts, reference the synced Kubernetes secret: ```yaml deployment.yaml theme={null} env: - name: API_KEY valueFrom: secretKeyRef: name: vendor-license-key key: value ``` ### Changing Secrets If you change a secret value outside of Nuon (e.g., directly in AWS Secrets Manager), Nuon will not detect the change. You will need to reprovision the install or manually redeploy dependent components. If secrets are configured to sync with Kubernetes, use the "sync secrets" option in the dashboard. # Configuring Policies Source: https://docs.nuon.co/guides/configuring-policies Step-by-step guide to adding compliance and security policies to your Nuon app. For background on policy concepts, types, and engines, see the [Policies concept page](/concepts/policies). ## Prerequisites Before starting, ensure you have: * An existing Nuon app with at least one component (terraform, helm, or container\_image) * The [Nuon CLI](/cli) installed and authenticated * Basic familiarity with [OPA Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) syntax ## Create the Policy Directory Structure Create a `policies/` directory in your app root to store policy files: ```sh theme={null} mkdir policies ``` Your final directory structure will look like this: ``` myapp/ ├── policies/ │ ├── require-signed-images.rego │ ├── require-encryption.rego │ └── require-resource-limits.rego ├── policies.toml ├── components/ │ ├── api_image.toml │ ├── database.toml │ └── api.toml └── metadata.toml ``` ## Add a Container Image Policy (Build-time) Container image policies validate external images during the build phase. This example requires all images to be cryptographically signed. Create `policies/require-signed-images.rego`: ```rego policies/require-signed-images.rego theme={null} package nuon default allow := false allow if { input.metadata.signed == true } deny contains msg if { not input.metadata.signed msg := sprintf("Image %s:%s must be signed", [input.image, input.tag]) } ``` For detailed container image policy patterns including SBOM validation and attestation checks, see the [External Image Policies guide](/guides/external-image-policies). ## Add a Terraform Policy (Deploy-time) Terraform policies validate the Terraform plan before applying changes. This example requires S3 bucket encryption and warns about missing tags. Create `policies/require-encryption.rego`: ```rego policies/require-encryption.rego theme={null} package nuon # Deny unencrypted S3 buckets deny contains msg if { some resource in input.plan.resource_changes resource.type == "aws_s3_bucket" resource.change.actions[_] in ["create", "update"] not resource.change.after.server_side_encryption_configuration msg := sprintf("S3 bucket '%s' must have encryption enabled", [resource.address]) } # Warn about missing Environment tag warn contains msg if { some resource in input.plan.resource_changes resource.change.actions[_] in ["create", "update"] not resource.change.after.tags.Environment msg := sprintf("Resource '%s' is missing Environment tag", [resource.address]) } ``` ## Add a Helm Chart Policy (Deploy-time) Helm chart policies validate rendered Kubernetes manifests. This example requires CPU and memory limits on all containers. Create `policies/require-resource-limits.rego`: ```rego policies/require-resource-limits.rego theme={null} package nuon # Deny containers without resource limits deny contains msg if { input.review.kind.kind == "Pod" some container in input.review.object.spec.containers not container.resources.limits.cpu msg := sprintf("Container '%s' must have CPU limits defined", [container.name]) } deny contains msg if { input.review.kind.kind == "Pod" some container in input.review.object.spec.containers not container.resources.limits.memory msg := sprintf("Container '%s' must have memory limits defined", [container.name]) } # Warn about missing resource requests warn contains msg if { input.review.kind.kind == "Pod" some container in input.review.object.spec.containers not container.resources.requests msg := sprintf("Container '%s' should have resource requests defined", [container.name]) } ``` ## Configure policies.toml Create `policies.toml` at your app root to register each policy: ```toml policies.toml theme={null} # Container image policy - evaluated at build time [[policy]] type = "container_image" engine = "opa" components = ["api_image"] contents = "./policies/require-signed-images.rego" # Terraform policy - evaluated at deploy time [[policy]] type = "terraform_module" engine = "opa" components = ["*"] contents = "./policies/require-encryption.rego" # Helm chart policy - evaluated at deploy time [[policy]] type = "helm_chart" engine = "opa" components = ["api"] contents = "./policies/require-resource-limits.rego" ``` ### Policy configuration fields | Field | Required | Description | | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `type` | Yes | Policy type: `container_image`, `helm_chart`, `kubernetes_manifest`, `terraform_module`, `kubernetes_cluster`, or `sandbox` | | `engine` | No | Policy engine: `opa` or `kyverno` (default) | | `name` | No | Human-readable name. If not specified, derived from the filename | | `components` | Yes | List of component names this policy applies to. Use `["*"]` for all components of the specified type | | `contents` | Yes | Inline policy content or path to policy file (e.g., `./policies/require-encryption.rego`) | The `contents` field supports multiple source types. Relative paths are resolved from the `policies/` directory: ```toml theme={null} # Relative file path (relative to policies/ directory) contents = "./require-encryption.rego" # HTTP/HTTPS URL contents = "https://example.com/policies/security.rego" # Git repository (the `//` separates the repo from the path to the file) contents = "git::https://github.com/org/policies//terraform/encryption.rego" # Git repository, pinned to a ref (branch, tag, or commit) contents = "git::https://github.com/org/policies//terraform/encryption.rego?ref=v1.2.3" ``` ## Sync Your App Upload your policies by syncing your app: ```sh theme={null} nuon apps sync ``` Policies are synced along with components and other configuration. You should see output confirming the policies were uploaded: ``` Syncing app... ✓ Synced 3 policies ✓ Synced 3 components ✓ App sync complete ``` ## Observe Build-Time Evaluation Container image policies are evaluated when you create a build. Trigger a build for your image component: ```sh theme={null} nuon builds create -c api_image ``` If the image is not signed, the build fails with `policy_failed` status: ``` Creating build for api_image... ✗ Build failed: policy check failed - require-signed-images: Image nginx:latest must be signed ``` If the image passes all policy checks, the build succeeds: ``` Creating build for api_image... ✓ Build created: bld_abc123 ✓ Policy checks passed ``` ## Observe Deploy-Time Evaluation Terraform and Helm policies are evaluated during deployment. Deploy your components to an install: ```sh theme={null} nuon installs deploy -i ``` After the deployment starts, list the workflow steps to see policy results: ```sh theme={null} nuon installs workflows steps list -w ``` The output shows policy status for each step: ``` Step Status Policy deploy-database error ✗ 1 deploy-api success ⚠ 2 ``` * `✗` indicates deny violations that blocked the step * `⚠` indicates warnings that were logged but allowed the step to continue * `✓` indicates all policies passed Get detailed violation messages for a specific step: ```sh theme={null} nuon installs workflows steps get -w -s ``` ``` Step: deploy-database Status: error Policy Violations: ✗ require-encryption: S3 bucket 'module.db.aws_s3_bucket.backup' must have encryption enabled Policy Warnings: ⚠ require-encryption: Resource 'module.db.aws_rds_cluster.main' is missing Environment tag ``` ## View Results in Dashboard Policy results are also visible in the Nuon Dashboard: 1. Navigate to your install's **Workflows** tab 2. Select the workflow run 3. Click on a workflow step to view details 4. The **Policy Report** card shows: * **Passed**: Green checkmark if all policies passed * **Denies**: Red indicators with violation messages * **Warnings**: Orange indicators with warning messages Each violation displays the policy name and the specific message from your `deny` or `warn` rule. Workflow policy violations You can also view policy results via CLI using `nuon installs workflows steps get -w -s `. ## Fix Policy Violations To resolve policy violations, update your components to comply with the policies: **For container images**: Sign your images using [cosign](https://github.com/sigstore/cosign) or another signing tool before pushing to your registry. **For Terraform**: Add the required configuration to your module: ```hcl theme={null} resource "aws_s3_bucket" "backup" { bucket = "my-backup-bucket" tags = { Environment = "production" } } resource "aws_s3_bucket_server_side_encryption_configuration" "backup" { bucket = aws_s3_bucket.backup.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } ``` **For Helm charts**: Add resource limits to your pod specs: ```yaml theme={null} containers: - name: api resources: limits: cpu: "500m" memory: "512Mi" requests: cpu: "100m" memory: "128Mi" ``` After making changes, sync and redeploy: ```sh theme={null} nuon apps sync nuon installs deploy -i ``` Confirm the policies now pass: ``` Step Status Policy deploy-database success ✓ deploy-api success ✓ ``` ## Next Steps * [External Image Policies](/guides/external-image-policies) - Advanced patterns for container image validation including SBOM, attestations, and signature verification * [Example Policies Repository](https://github.com/nuonco/policies) - Ready-to-use policy examples for Terraform and Kubernetes * [Policies Configuration Reference](/config-ref/policies) - Complete schema reference for policy configuration # Configuring Sandboxes Source: https://docs.nuon.co/guides/configuring-sandboxes Configure sandbox infrastructure for your app. A sandbox is configured in a `sandbox.toml` file in the root of your app directory. It points to a Terraform module (public or private repo) and passes variables to configure the infrastructure. ## Basic Configuration ```toml sandbox.toml theme={null} terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-sandbox" branch = "main" [vars] cluster_version = "1.33" cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" [[var_file]] contents = "./sandbox.tfvars" ``` ```toml sandbox.tfvars theme={null} additional_namespaces = ["grafana"] min_size = 2 max_size = 3 desired_capacity = 2 ``` You can use either a public repo (`public_repo` block) or a private GitHub repo (`connected_repo` block). Read more about VCS configuration [here](/guides/vcs). ## Using Default Values Add a `vars` block to `sandbox.toml` or use the `sandbox.tfvars` file for default values shared across all installs: ```toml sandbox.toml theme={null} [vars] cluster_name = "n-{{.nuon.install.id}}" enable_nuon_dns = "true" public_root_domain = "{{ .nuon.install.id }}.nuon.run" internal_root_domain = "internal.{{ .nuon.install.id }}.nuon.run" [[var_file]] contents = "./sandbox.tfvars" ``` ## Using Inputs Define an input in `inputs.toml` and reference it in `sandbox.toml`: ```toml inputs.toml theme={null} [[group]] name = "dns" description = "DNS configuration" display_name = "DNS" [[input]] name = "root_domain" description = "The root domain for the install." default = "app.example.com" display_name = "Root Domain" group = "dns" ``` ```toml sandbox.toml theme={null} [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" ``` ## Sandbox Outputs Sandbox outputs are accessible to all components through [variables](/guides/using-variables): ```toml components/app.toml theme={null} [values] account_region = "{{.nuon.install.sandbox.outputs.account.region}}" ``` Common outputs include: * `account.id` - The AWS Account ID * `account.region` - The AWS Region * `vpc.id` - The VPC ID * `cluster.cluster_name` - The EKS Cluster name * `cluster.cluster_endpoint` - The EKS Cluster endpoint * `ecr.repository_url` - The ECR repository URL * `nuon_dns.public_domain` - The public root domain * `nuon_dns.internal_domain` - The internal root domain * `namespaces` - Namespaces created in the cluster See the [outputs.tf](https://github.com/nuonco/aws-eks-sandbox/blob/main/outputs.tf) for the full list. ## Custom Sandboxes You can fork a [managed sandbox](https://github.com/nuonco) or create your own. Point your `sandbox.toml` to your repo: ```toml sandbox.toml theme={null} terraform_version = "1.11.3" [public_repo] repo = "your-org/your-sandbox" directory = "sandbox" branch = "main" ``` If you are considering a custom sandbox, we would love to hear about your use case. Please [contact us](https://nuon.co/contact-us). # Container Actions Source: https://docs.nuon.co/guides/container-actions Execute actions inside a user-managed container image. By default, actions run on the install [runner](/concepts/runners). Setting `image` executes the action inside a container image you provide. Container actions are behind the `image-backed-actions` org feature flag and are off by default. [Reach out to Nuon](https://nuon.co/demo-request) to enable it for your org. ## Configure it Add `image` to the action and configure a step with `inline_contents`: ```toml actions/db_migrate.toml theme={null} # action name = "db_migrate" timeout = "10m" image = "ghcr.io/acme/migrate-tools:v1.4.0" [[triggers]] type = "manual" [[steps]] name = "migrate" inline_contents = """ #!/usr/bin/env sh set -eu migrate -database "$DATABASE_URL" -path /migrations up """ [steps.env_vars] DATABASE_URL = "{{.nuon.install.sandbox.outputs.database_url}}" ``` Sync it the same way as any other action: ```sh theme={null} nuon apps sync ``` Everything else about the action is unchanged: triggers, `timeout` (30 minutes maximum), `role`, `enable_kube_config`, `env_vars`, and templating all behave exactly as they do when the action runs on the runner. See [Configure actions](/guides/actions). ## Choosing an image You can use publically hosted images or component images from your Nuon app config. ### A public reference ```toml actions/db_migrate.toml theme={null} image = "ghcr.io/acme/migrate-tools:v1.4.0" ``` ### A private image Publish the image as a [container image component](/guides/container-image-components) and point `image` at its `image.ref` output: ```toml actions/db_migrate.toml theme={null} image = "{{.nuon.components.migrate_tools.outputs.image.ref}}" ``` Use the component's `image.ref` output, not `image.repository` and `image.tag`. A bare `repository:tag` is rejected with `not digest-pinned`. ## Building the image Nuon does not modify the image. It must include every tool and dependency the action needs; the runner toolchain is not available inside the container. # Container Image Components Source: https://docs.nuon.co/guides/container-image-components Container image components allow you to use prebuilt container images. Container images allow you to import prebuilt container images from public sources, private AWS ECR repositories, private GCP Artifact Registry (GAR) repositories, and private Azure Container Registry (ACR) repositories. Container images are used to import *prebuilt* images, that have already been pushed to either a public registry, a private AWS ECR repository, a private GCP Artifact Registry repository, or a private Azure Container Registry repository. If you have private prebuilt images in a registry other than AWS ECR, GCP Artifact Registry, or Azure Container Registry, [please get in touch](https://nuon.co/contact-us)! ## Configuring a container image component ### Using a Public Image To use a public image from any container registry, configure the `public` block. ```toml components/public_image.toml theme={null} # container-image name = "public" type = "container_image" [public] image_url = "kennethreitz/httpbin" tag = "latest" ``` ### Using a Private AWS ECR Image To use an image from a private ECR registry, configure the `aws_ecr` block. ```toml components/ecr_image.toml theme={null} # container-image name = "ecr" type = "container_image" [aws_ecr] image_url = "123927561584.dkr.ecr.us-west-2.amazonaws.com/repo-name" tag = "latest" region = "us-west-2" iam_role_arn = "arn:aws:iam::123927561584:role/nuon-container-image-access" ``` To use an AWS ECR image, [follow the access setup directions](#granting-nuon-access-to-pull-images). Both Nuon-hosted (AWS) and self-hosted-on-GCP customers can pull from the same role. ### Using a Private GCP GAR Image To use an image from a private Google Artifact Registry, configure the `gcp_gar` block. ```toml components/gar_image.toml theme={null} # container-image name = "gar" type = "container_image" [gcp_gar] image_url = "us-central1-docker.pkg.dev/my-project/my-repo/my-image" tag = "latest" region = "us-central1" gcp_project_id = "my-project" # Reader SA that Nuon impersonates to pull. Optional for self-hosted-on-GCP # customers if the customer's ctl-api SA already has artifactregistry.reader # directly on the repo; required for Nuon-hosted (AWS) customers. service_account_email = "nuon-puller@my-project.iam.gserviceaccount.com" # Required for Nuon-hosted (AWS) customers. Workload Identity Provider that # Nuon's AWS ctl-api federates against before impersonating the reader SA. workload_identity_provider = "projects/123456789/locations/global/workloadIdentityPools/nuon-aws/providers/aws-prod" ``` To use a GAR image, [follow the access setup directions](#gcp-gar-access). Both Nuon-hosted (AWS) and self-hosted-on-GCP customers can pull from the same GAR repository. ### Using a Private Azure ACR Image To use an image from a private Azure Container Registry, configure the `azure_acr` block. ```toml components/acr_image.toml theme={null} # container-image name = "acr" type = "container_image" [azure_acr] image_url = "vendoracr.azurecr.io/private-app" tag = "latest" registry_url = "vendoracr.azurecr.io" tenant_id = "00000000-0000-0000-0000-000000000000" client_id = "11111111-1111-1111-1111-111111111111" # Exactly one of client_secret_name or client_certificate_name. Both take the # NAME of an app secret, never the credential itself. client_secret_name = "acr_pull_secret" ``` Access to a private ACR repository is granted through a service principal in your own tenant, not through credentials in your app config. The service principal's secret (or certificate) value is stored as an app secret and referenced by name; the value itself never appears in app config or git. To set this up, [follow the access setup directions](#azure-acr-access). ACR has no registry-level password. Authentication always goes through Azure AD: Nuon exchanges an Azure AD token for the service principal for an ACR refresh token, then uses that to pull the image. This is why `azure_acr` takes a tenant, client, and secret/certificate rather than a username and password. This credential is used only when Nuon's build runs — it authenticates to your registry once, at build time, from Nuon's control plane. It never lands in a customer cloud account, and it is shared across all installs of the app rather than configured per install. ## Verifying signed images Container image components can require the resolved image digest to be signed by a trusted Sigstore keyless identity or Cosign public key. Verification happens before Nuon copies the image, and a failed verification fails the component build. See [Verify Container Image Signatures](/guides/image-signature-verification) for keyless and key-based configuration, authority matching, and guidance on combining verification with OPA policies. ## Deployments Container image components *cannot* be deployed directly in a customer install. When an image is released, it will be *synced* into the customer install and made available to other components via [variables](/guides/using-variables). To deploy a Container image component, reference the image from a Helm component or Terraform component: ```toml components/eks_deployment.toml theme={null} # helm name = "eks_deployment" type = "helm_chart" chart_name = "" [connected_repo] repo = "" directory = "components/helm-chart" branch = "main" [values] "env.IMAGE_TAG" = "{{.nuon.components.app_image.image.tag}}" "env.IMAGE_REPOSITORY" = "{{.nuon.components.app_image.image.repository.uri}}" ``` ```toml components/ecs_service.toml theme={null} # terraform name = "ecs_service" type = "terraform_module" terraform_version = "v1.6.3" [connected_repo] repo = "" directory = "components/ecs-service" branch = "main" [vars] "image_tag" = "{{.nuon.components.app_image.image.tag}}" "image_repository" = "{{.nuon.components.app_image.image.repository.uri}}" ``` ```toml components/eks_job.toml theme={null} # action name = "eks_job" type = "job" image_url = "{{.nuon.components.app_image.image.repository.uri}}" tag = "{{.nuon.components.app_image.image.tag}}" ``` ## Image Syncing When a Container image component is released, Nuon will automatically sync the image into the end customer account. This image is stored in a local registry that is provisioned in the customer account. OCI referrers and legacy Cosign digest tags are copied with the image so signatures, SBOMs, provenance, and other attestations remain available in the destination registry. Nuon image syncing allows you to sync images into accounts, without worrying about cross account permissions, registry authentication or publishing public images. Any `Dockerfile` in a repo can be built, and synced. The sync process works by creating a 1-time authentication flow that grants the install runner access to pull the image from the org data plane, and copy it into the local registry. ## Granting Nuon Access to Pull Images Nuon-hosted runs in AWS, and we also offer a [self-hosted BYOC deployment on GCP](/guides/self-hosted/gcp). Pick the section that matches your registry and the deployment kind your customers are using. | Image registry | Customer deployment | Setup | | ------------------------ | -------------------- | ----------------------------------------------------------------------------------------- | | AWS ECR | Nuon-hosted (AWS) | [AWS ECR Access IAM Role](#aws-ecr-access-iam-role) | | AWS ECR | Self-hosted on GCP | [AWS ECR from a Self-Hosted-on-GCP Customer](#aws-ecr-from-a-self-hosted-on-gcp-customer) | | GCP Artifact Registry | Nuon-hosted (AWS) | [GAR for Nuon-hosted (AWS) customers](#gar-for-nuon-hosted-aws-customers) | | GCP Artifact Registry | Self-hosted on GCP | [GCP GAR Access](#gcp-gar-access) | | GCP Artifact Registry | Self-hosted on Azure | [GAR for Nuon self-hosted (Azure) customers](#gar-for-nuon-self-hosted-azure-customers) | | Azure Container Registry | Any | [Azure ACR Access](#azure-acr-access) | If you have customers on both deployments, you can set both inputs on the same role or service account. Both modules add the new principals alongside any existing ones. ### AWS ECR Access IAM Role To use a private AWS ECR image, you must create an IAM role that grants Nuon access to pull your container image. When building your component, `nuon` will automatically assume the role and pull the image from your build runner. The easiest way to setup an IAM role to configure your component with is using our [Terraform Module](https://registry.terraform.io/modules/nuonco/ecr-access/aws). ```hcl theme={null} module "nuon_ecr_access" { source = "nuonco/ecr-access/aws" repository_arns = [""] } output "iam_role_arn" { value = module.nuon_ecr_access.iam_role_arn } ``` If you are having trouble finding the repository ARN in the AWS console, you can run `aws ecr describe-repositories` to print the ARNs of all repositories in your current context. ### AWS ECR from a Self-Hosted-on-GCP Customer If your customer is running a self-hosted Nuon deployment on GCP, pass the customer's GCP service account unique IDs via `gcp_principals` so they can assume the role via Workload Identity Federation. Two SAs need to be listed: * The org runner SA, which pulls source for builds. Display name: `Nuon org runner `. * The ctl-api SA, which fetches image metadata for `external_image` components. Display name: `ctl-api for `. Requires `nuonco/ecr-access/aws` version `0.1.9` or later. ```hcl theme={null} module "nuon_ecr_access" { source = "nuonco/ecr-access/aws" repository_arns = [""] gcp_principals = [ { service_account_unique_id = "123456789012345678901" # org runner SA UID service_account_email = "@.iam.gserviceaccount.com" }, { service_account_unique_id = "987654321098765432109" # ctl-api SA UID service_account_email = "ctl-api-@.iam.gserviceaccount.com" }, ] } ``` Don't construct the SA emails by hand. Nuon truncates the org and install IDs to fit GCP's 30-character SA name limit, and the truncation length varies. Look the SAs up directly in the customer's project. The full org and install IDs are preserved in each SA's display name. Look them up with: ```bash theme={null} gcloud iam service-accounts list \ --project= \ --filter='displayName~"Nuon org runner" OR displayName~"ctl-api for"' \ --format='table(email,uniqueId,displayName)' ``` The default Nuon-hosted (AWS) trust principal stays in place. Adding `gcp_principals` is additive, so the same role works for both Nuon-hosted and self-hosted-on-GCP customers. ### GCP GAR Access You grant access to a private GAR repository by creating a reader service account on the repo and allowing each customer's identity to impersonate it. The same module covers both customer-deployment kinds. Use `customer_principals` for self-hosted-on-GCP customers, `aws_principals` for Nuon-hosted (AWS) customers, or both. The easiest way to set this up is using our [Terraform Module](https://registry.terraform.io/modules/nuonco/gar-access/google): ```hcl theme={null} module "nuon_gar_access" { source = "nuonco/gar-access/google" project_id = "" repository_location = "us-central1" repositories = [""] customer_principals = [ "serviceAccount:ctl-api-@.iam.gserviceaccount.com", ] } output "gar_access_sa_email" { value = module.nuon_gar_access.service_account_email } ``` Then set `service_account_email` in your component's `gcp_gar` block to the SA email the module emits. `repositories` accepts multiple entries, and entries ending in `*` grant access to every repository matching that prefix (e.g. `repositories = ["shared-images", "team-*"]`) — including repositories created later. Requires `nuonco/gar-access/google` version `0.3.0` or later; older versions take a single `repository_id` string. #### GAR for Nuon-hosted (AWS) customers If some of your customers pull from this GAR repo via Nuon-hosted ctl-api running on AWS, pass each customer's AWS account ID via `aws_principals`. You can use it alongside `customer_principals` if you also have self-hosted-on-GCP customers. The module sets up a Workload Identity Pool and AWS Provider per account, and lets the federated principal impersonate the GAR-access SA. ```hcl theme={null} module "nuon_gar_access" { source = "nuonco/gar-access/google" project_id = "" repository_location = "us-central1" repositories = [""] aws_principals = [ { aws_account_id = "123456789012" }, ] } output "workload_identity_provider_paths" { value = module.nuon_gar_access.workload_identity_provider_paths } ``` Requires `nuonco/gar-access/google` version `0.2.0` or later. In the customer's component config, set `gcp_gar.service_account_email` to the GAR-access SA email, and set `gcp_gar.workload_identity_provider` to the provider path for that customer's AWS account from the module's `workload_identity_provider_paths` output. #### GAR for Nuon self-hosted (Azure) customers If some of your customers run Nuon self-hosted on Azure, pass each customer's Azure user-assigned managed identity via `azure_principals`. You can use it alongside `customer_principals` and `aws_principals`. The module sets up a Workload Identity Pool and an OIDC provider per identity — trusting the Entra ID issuer for its tenant — and lets the federated identity impersonate the GAR-access SA. ```hcl theme={null} module "nuon_gar_access" { source = "nuonco/gar-access/google" project_id = "" repository_location = "us-central1" repositories = [""] azure_principals = [ { tenant_id = "" principal_id = "" }, ] } output "azure_workload_identity_provider_paths" { value = module.nuon_gar_access.azure_workload_identity_provider_paths } ``` `principal_id` is the managed identity's principal (object) ID — the `sub` claim Nuon's Azure ctl-api presents. In the customer's component config, set `gcp_gar.service_account_email` to the GAR-access SA email, and set `gcp_gar.workload_identity_provider` to the provider path for that customer's identity from the module's `azure_workload_identity_provider_paths` output. See the full set of `gcp_gar` fields in the [container-image config reference](/config-ref/container-image#gcp_gar). ### Azure ACR Access Unlike AWS ECR and GCP GAR, ACR has no cross-tenant identity federation, so access is granted the way most third parties access an Azure resource: you create a service principal (an app registration) in your own tenant, grant it `AcrPull` scoped to your registry, and give Nuon the tenant ID, client ID, and a secret or certificate for that service principal. Only your build needs this. The install runner that deploys into a customer's cloud account pulls the image from Nuon's own registry, never from your ACR — so no access needs to be granted to any customer's Azure identity. One service principal, scoped to your tenant, covers every install of the app. **1. Create the app registration and service principal:** ```bash theme={null} az ad app create --display-name "nuon-acr-pull" # note the "appId" field from the output — this is your client_id az ad sp create --id ``` **2. Grant `AcrPull`, scoped to just this registry:** ```bash theme={null} az role assignment create \ --assignee \ --role AcrPull \ --scope $(az acr show --name vendoracr --query id --output tsv) ``` **3. Create a credential.** Use a client secret, or a client certificate (the better practice of the two, since it needs no plaintext secret at rest): ```bash theme={null} # Option A: client secret az ad app credential reset \ --id \ --display-name "nuon-acr-pull-secret" \ --years 1 # outputs a JSON object with "password" — this is the secret value ``` ```bash theme={null} # Option B: client certificate openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout acr-pull.key -out acr-pull.crt -subj "/CN=nuon-acr-pull" az ad app credential reset --id --cert @acr-pull.crt --append # Nuon needs the private key too, so bundle both into one PEM and base64 # encode it before storing it as an app secret cat acr-pull.key acr-pull.crt | base64 > acr-pull-bundle.b64 ``` **4. Store the value as an app secret**, never in app config or git: ```bash theme={null} # for a client secret nuon apps variables create \ --app-id \ --name acr_pull_secret \ --value "" # for a client certificate nuon apps variables create \ --app-id \ --name acr_pull_cert \ --value "$(cat acr-pull-bundle.b64)" ``` **5. Reference the secret by name** in the component's `azure_acr` block — see [Using a Private Azure ACR Image](#using-a-private-azure-acr-image). Set `tenant_id` to your tenant, `client_id` to the `appId` from step 1, and `client_secret_name` or `client_certificate_name` (never both) to the app secret name from step 4. Rotating the credential is a plain app secret update — run `nuon apps variables create` again with the same `--name` to overwrite it. Nothing re-runs automatically, so a stale or expired credential surfaces as a build failure the next time the component builds, not immediately at rotation time. See the full set of `azure_acr` fields in the [container-image config reference](/config-ref/container-image#azure_acr). ## Importing Images Outside of Supported Registries We currently support public container images, private AWS ECR repositories, private GCP Artifact Registry repositories, and private Azure Container Registry repositories. If your registry is supported by AWS ECR's [pull through cache](https://docs.aws.amazon.com/AmazonECR/latest/userguide/pull-through-cache-working.html), the easiest way to import the images into Nuon is to setup a pull through cache and configure your container image to use it. This works for [Docker Hub](https://hub.docker.com/), [Github Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) and [Microsoft Azure Container Registry](https://azure.microsoft.com/en-us/products/container-registry). If you would like to use a different private container registry, we would love to know more. Please [get in touch](https://nuon.co/contact-us) to tell us more about your use case. # Add BYOC installation to your app Source: https://docs.nuon.co/guides/control-plane-integration Enable customers to manage their installs through your UI All the features of the Nuon platform are exposed via a REST API, allowing for fully programmatic operation. We also maintain a Golang SDK, which we use ourselves for the Dashboard and CLI. This guide walks through how to implement a complete install lifecycle using the API so your customers can manage their installs directly from your own product. ## Provision an Install At a high-level, provisioning a new install consists of 3 steps: 1. Creating the install in the Nuon control plane. 2. Installing the runner in the customer's cloud account. 3. Approving the provision of the app components into the customer's cloud account. ### Create an Install ```bash curl theme={null} curl -X POST https://api.nuon.co/v1/installs \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "app_id": "app...", "name": "acme-corp-production", "aws_account": { "region": "us-west-2" }, "inputs": { "domain": "acme.example.com" } }' ``` ```go Go theme={null} import ( "context" "os" nuon "github.com/nuonco/nuon-go" "github.com/nuonco/nuon-go/models" ) func main() { client, _ := nuon.New( nuon.WithAuthToken(os.Getenv("NUON_API_TOKEN")), nuon.WithOrgID(os.Getenv("NUON_ORG_ID")), ) name := "acme-corp-production" install, workflowID, err := client.CreateInstall( context.Background(), "app...", &models.ServiceCreateInstallRequest{ Name: &name, AwsAccount: &models.ServiceCreateInstallRequestAwsAccount{ Region: "us-west-2", }, Inputs: map[string]string{ "domain": "acme.example.com", }, }, ) } ``` ### Install the Runner Installing the runner happens out of band, entirely on the customer's side. You just need to provide them with directions. We provide these for each install as part of the provision workflow. Once the runner is installed, it will phone home automatically. ### Approve the Provision Once the runner is installed, it will be ready to provision the sandbox and app components. The platform supports approving each step individually or approving all steps at once. Typically, the customer will want to approve resources being created in their account, so we recommend exposing this to them — mapping a single "Approve" button in your UI to "Approve All" keeps the experience simple. The `workflowID` is returned when creating the install. You will need to store this to approve the provision workflow. ```bash curl theme={null} curl -X PATCH https://api.nuon.co/v1/workflows/$WORKFLOW_ID \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"approval_option": "approve-all"}' ``` ```go Go theme={null} approveAll := models.AppInstallApprovalOptionApproveDashAll workflow, err := client.UpdateWorkflow( context.Background(), workflowID, &models.ServiceUpdateWorkflowRequest{ ApprovalOption: &approveAll, }, ) ``` ## Maintain an Install Even though the goal of using Nuon it to provide a SaaS-like experience, where you can maintain the install for your customer, there are still a few things they will need to be able to do themselves. Common use cases are: * Accepting a new version of the app * Allowing you to break glass to triage an issue * Updating an input value that was incorrect * Updating a secret that was incorrect * Inspecting audit trails for compliance reasons We will cover the API calls needed for these use-cases here. ### Update Inputs ```bash curl theme={null} curl -X PATCH https://api.nuon.co/v1/installs/$INSTALL_ID/inputs \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "domain": "new-domain.example.com", "replicas": "3" } }' ``` ```go Go theme={null} inputs, workflowID, err := client.UpdateInstallInputs( context.Background(), installID, &models.ServiceUpdateInstallInputsRequest{ Inputs: map[string]string{ "domain": "new-domain.example.com", "replicas": "3", }, }, ) ``` ### Update Secrets ```bash curl theme={null} curl -X POST https://api.nuon.co/v1/installs/$INSTALL_ID/sync-secrets \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"plan_only": false}' ``` ```go Go theme={null} // SyncSecrets is not yet exposed in the public SDK. // Use the REST API directly or contact support for SDK updates. ``` ## Delete an Install Uninstalling an app requires performing the same operations as a provision, but in reverse. 1. Deprovision the app resources from your customer's cloud account. 2. Uninstall the runner. 3. Delete the install from the control plane. ### Deprovision the App To deprovision the app resources and sandbox, you will need to run an install deprovision workflow. Just like the provision workflow, this will require approvals — typically mapped to a single "Approve" button in your UI for simplicity. ```bash curl theme={null} curl -X POST https://api.nuon.co/v1/installs/$INSTALL_ID/deprovision \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"plan_only": false}' ``` ```go Go theme={null} err := client.DeprovisionInstall(context.Background(), installID) ``` ### Uninstall the Runner Just like installing the runner, uninstalling it happens out of band. The instructions are provided in the deprovision workflow. ### Delete the Install Once all the resources have been removed from your customer's cloud account, you can delete the install from the control plane. ```bash curl theme={null} curl -X DELETE https://api.nuon.co/v1/installs/$INSTALL_ID \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" ``` ```go Go theme={null} deleted, err := client.DeleteInstall(context.Background(), installID) ``` ## Forget an Install Sometimes, an install can get into a bad enough state that you cannot even run deprovision operations. Usually, this means something catastrophic has happened out of band in the customer's account, and it would be easier for Nuon to simply forget the current install and re-install the app from scratch. ```bash curl theme={null} curl -X POST https://api.nuon.co/v1/installs/$INSTALL_ID/forget \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{}' ``` ```go Go theme={null} forgotten, err := client.ForgetInstall(context.Background(), installID) ``` # Custom Domains Source: https://docs.nuon.co/guides/custom-domains Manage custom domains for an install. By default, each Nuon install will get configured with a `.nuon.run` root domain, which allows any component to create or update DNS records. While the `nuon.run` domain works out of the box, it can be disabled or customized to support different types of domains. By default, both a public and private DNS zone are created. ## Vendor Managed Domain If you would like each of your installs to have a common root domain, such as `installs..com`, you can set up a DNS zone and delegate each install's nameservers. First, configure your sandbox to use a custom root domain using: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3s" [public_repo] directory = "azure-aks-byovpn" repo = "nuonco/sandboxes" branch = "main" [vars] public_root_domain = "{{.nuon.install.id}}.your-domain.com" ``` Now, when an install is provisioned it will automatically create the DNS zone named `install-id.your-domain.com`. Next, the root domain must *delegate* to the install's DNS zone. This can be done by adding `NS` records on the root domain. First, find the nameservers for the public domain by inspecting the sandbox outputs: ```sh theme={null} nuon installs sandbox-outputs ``` Next, set up a DNS record on your root domain that delegates the install subdomain. This can be done via your provider's console our if you are managing DNS using Terraform you can use a record similar to this: ```hcl theme={null} resource "aws_route53_record" "docs" { zone_id = aws_route53_zone.main.zone_id name = "install-id" type = "NS" ttl = 3600 records = [ "ns-1", "ns-2", "ns-3", "ns-4" ] } ``` ## Customer Managed Domain You can allow a customer to bring their own domain by using an app input. Once the install is provisioned, the customer will be responsible for delegating the DNS subdomain. Please refer to the previous section for more details on how to delegate the domain. To offer custom install domains, add the following to your config file: ```toml inputs.toml theme={null} # inputs [[input]] name = "root_domain" description = "Root domain for install" default = "" sensitive = false required = true display_name = "Root" ``` Next, set the sandbox root domain to the input value: ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "azure-aks-byovpn" repo = "nuonco/sandboxes" branch = "main" [vars] public_root_domain = "{{.nuon.install.inputs.root_domain}}" ``` ## Disabling DNS Zones You can completely disable DNS in any sandbox by adding the following to your sandbox config: ```toml sandbox.toml theme={null} # sandbox [vars] enable_private_dns = "false" enable_public_dns = "false" ``` # Custom Install Stacks Source: https://docs.nuon.co/guides/custom-nested-stacks Provision additional AWS, Azure, or GCP resources with the install stack. 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 | Capability | AWS | Azure | GCP | | --------------------------------------- | -------------- | ----------------- | ----------------------------------------- | | App-level and per-install configuration | Yes | Yes | Yes | | Install-input parameters | Yes | Yes | Yes | | Outputs available to app templates | Yes | Yes | Yes | | Relative source uploaded during sync | Yes | Yes | Not applicable | | Sequential stacks and output wiring | Yes | Yes | Unique `index` only; modules do not chain | | User-supplied implementation | CloudFormation | Compiled ARM JSON | Reviewed module only | | Managed deletion with the install stack | Yes | Yes | Yes, except retained KMS keys | 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 ```toml theme={null} type = "aws-cloudformation" name = "payments-{{.nuon.install.id}}" description = "Application install stack." vpc_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/.../byo-vpc.yaml" runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/.../runner-asg.yaml" [[custom_nested_stacks]] name = "storage" template_url = "./cloudformation/storage.yaml" index = 0 [custom_nested_stacks.parameters] BucketName = "{{.nuon.install.id}}-storage" ``` Relative templates are uploaded by Nuon during app sync. Existing public S3 URLs continue to work. ### Azure ```toml theme={null} type = "azure-bicep" name = "payments-{{.nuon.install.id}}" description = "Application install stack." vpc_nested_template_url = "https://raw.githubusercontent.com/nuonco/install-stacks/main/azure/vnet.json" runner_nested_template_url = "https://raw.githubusercontent.com/nuonco/install-stacks/main/azure/runner.json" [[custom_nested_stacks]] name = "storage" template_url = "./arm/storage.json" index = 0 [custom_nested_stacks.parameters] accountName = "{{.nuon.install.id | substr 0 18}}store" ``` 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: ```bash theme={null} az bicep build --file arm/storage.bicep --outfile arm/storage.json nuon apps sync ``` Relative ARM templates are uploaded during sync. A public HTTPS URL also works. ### GCP ```toml theme={null} type = "gcp-terraform" name = "payments-{{.nuon.install.id}}" description = "Application install stack." [[custom_nested_stacks]] name = "storage" template_url = "github.com/nuonco/install-stacks//gcp/modules/bucket" index = 0 [custom_nested_stacks.parameters] location = "US" versioning = "{{.nuon.install.inputs.bucket_versioning}}" ``` For a fork, replace the repository prefix but preserve `//gcp/modules/`. The module must exist in that fork's GCP install stack. The built-in collection supports `bucket`, `kms`, `service_account`, and `dns`. ### Properties | Property | Type | Required | Description | | -------------- | ------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `name` | `string` | Yes | Stable, unique key used for deployment naming and output lookup. | | `template_url` | `string` | Yes | AWS template URL/path, Azure ARM JSON URL/path, or GCP curated module path. | | `index` | `int` | Yes | Unique ordering key. AWS and Azure execute stacks in ascending order. | | `parameters` | `map[string]string` | No | Explicit parameter values. Supports Go templating. See [Parameter Mapping](#parameter-mapping). | 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. ```hcl theme={null} provider "stack" {} module "install_stack" { source = "nuonco/stack/azure" # use nuonco/stack/gcp for GCP version = "~> 1.0" install_id = var.install_id } ``` The Stack provider reads `NUON_API_TOKEN` and `NUON_API_URL` from the environment. See [The Nuon Stack Terraform Provider](/guides/nuon-stack-terraform-provider) for authentication and complete examples. ## Use Custom Stack Outputs All clouds report outputs under the same path: ```text theme={null} .nuon.install_stack.outputs.custom_nested_stacks..outputs. ``` For example, a sandbox or component can consume the `name` output from the `storage` stack: ```toml theme={null} [vars] bucket_name = "{{.nuon.install_stack.outputs.custom_nested_stacks.storage.outputs.name}}" ``` 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 | Module | Parameters | Outputs | | ----------------- | -------------------------------------------------------- | ----------------------------------------- | | `bucket` | `location`, `versioning`, `force_destroy` | `name`, `url`, `self_link` | | `kms` | `location`, `rotation_period` | `id`, `key_ring`, `name` | | `service_account` | `display_name`, `description` | `email`, `unique_id`, `name` | | `dns` | `dns_name`, `visibility`, `description`, `force_destroy` | `name`, `name_servers`, `managed_zone_id` | 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 | Parameter | Value | | --------------- | -------------- | | `NuonInstallID` | The install ID | | `NuonAppID` | The app ID | | `NuonOrgID` | The org ID | 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 | Parameter | Value | | ------------------------- | ------------------------------- | | `EnableRunnerProvision` | Runner Provision Role enabled | | `EnableRunnerMaintenance` | Runner Maintenance Role enabled | | `EnableRunnerDeprovision` | Runner Deprovision Role enabled | 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: ```yaml theme={null} AWSTemplateFormatVersion: '2010-09-09' Parameters: NuonInstallID: Type: String EnableRunnerProvision: Type: String Default: 'true' AllowedValues: - 'true' - 'false' Conditions: ProvisionEnabled: !Equals [!Ref EnableRunnerProvision, 'true'] Resources: # Resources that depend on the provision role being enabled MyResource: Type: Custom::Resource Condition: ProvisionEnabled Properties: InstallId: !Ref NuonInstallID ``` ### Azure Reserved Parameters On `azure-bicep`, the reserved set follows ARM's camelCase convention and adds two more: | Parameter | Value | | ----------------- | -------------------------- | | `nuonInstallID` | The install ID | | `nuonAppID` | The app ID | | `nuonOrgID` | The org ID | | `location` | The install's Azure region | | `deployTimestamp` | Deployment timestamp | 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. ```json theme={null} "outputs": { "bauleiterIdentityPrincipalId": { "type": "string", "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'my-identity'), '2023-01-31').principalId]" } } ``` 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: ```yaml theme={null} Parameters: VPC: Description: VPC ID from the VPC stack Type: String RunnerSubnet: Description: Subnet from the VPC stack Type: String ``` 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..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. ```yaml theme={null} # Stack A (index 0) - produces outputs Outputs: SharedSubnetID: Value: !Ref MySubnet # Stack B (index 1) - consumes outputs from Stack A Parameters: SharedSubnetID: Description: Automatically wired from Stack A Type: String ``` ## 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. ```toml theme={null} [[custom_nested_stacks]] name = "k8s_namespaces" template_url = "https://my-bucket.s3.amazonaws.com/templates/k8s-namespaces.yaml" index = 0 [custom_nested_stacks.parameters] Namespaces = "{{.nuon.install.inputs.namespaces}}" ``` 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](https://masterminds.github.io/sprig/) functions available, so you can compose literals, branch on an optional input, or transform a value: ```toml theme={null} [custom_nested_stacks.parameters] # literal Environment = "production" # composed BucketName = "myapp-{{ .nuon.install.id }}-state" # conditional, with a derived fallback RootDomain = "{{ if .nuon.install.inputs.root_domain }}{{ .nuon.install.inputs.root_domain }}{{ else }}{{ .nuon.install.id | substr 0 15 }}.installs.example.com{{ end }}" ``` 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: ```yaml theme={null} AWSTemplateFormatVersion: '2010-09-09' Description: My custom nested stack Parameters: ClusterName: Type: String Description: Name of the EKS cluster Resources: # Your resources here Outputs: # Optional outputs ``` 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: | `name` value | Logical ID | | -------------------- | ------------------ | | `k8s_namespaces` | `K8sNamespaces` | | `eks_access_entries` | `EksAccessEntries` | | `my-custom-stack` | `MyCustomStack` | 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 ```yaml theme={null} Resources: MyFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: CustomAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - eks:DescribeCluster - eks:AccessKubernetesApi Resource: !Sub arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName} MyFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.12 Handler: index.handler Timeout: 120 Role: !GetAtt MyFunctionRole.Arn Code: ZipFile: | # Your Lambda handler here def handler(event, context): ... MyCustomResource: Type: Custom::MyResource Properties: ServiceToken: !GetAtt MyFunction.Arn ``` ### 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. ```python theme={null} if event["RequestType"] == "Delete": send_cfn_response(event, context, "SUCCESS") return ``` ## 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: ``` https://my-bucket.s3.amazonaws.com/templates/my-stack.yaml ``` 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](https://github.com/nuonco/example-app-configs/tree/main/byo-eks) 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. # Customizing Terraform Stack Templates Source: https://docs.nuon.co/guides/customizing-terraform-stack-templates Fork or wrap an open-source Terraform install-stack module. The Terraform install-stack modules are open source: * [AWS](https://github.com/nuonco/terraform-aws-stack) * [Azure](https://github.com/nuonco/terraform-azure-stack) * [GCP](https://github.com/nuonco/terraform-gcp-stack) The defaults work out of the box in most cases. Fork or wrap a module when you need different networking, compliance, or naming behavior. 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](/guides/nuon-stack-terraform-provider), then layers the customer-provided inputs and secrets over that. ```hcl theme={null} data "stack_config" "this" { install_id = var.install_id } locals { runner_id = data.stack_config.this.runner_id runner_api_url = data.stack_config.this.runner_api_url phone_home_url = data.stack_config.this.phone_home_url nuon_support_iam_role_arns = data.stack_config.this.aws.nuon_support_iam_role_arns # ... } ``` If you fork a module, keep this pattern: the customer supplies the `install_id`, cloud provider configuration, and any input or secret overrides. The Stack provider authenticates the read; the install ID is not a credential. The attribute reference for every value the data source exposes is in [The Nuon Stack Terraform Provider](/guides/nuon-stack-terraform-provider). ## AWS The AWS stack lives in [`terraform-aws-stack`](https://github.com/nuonco/terraform-aws-stack). ### 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 `-*`). Required for components that consume secrets. * `logs:CreateLogGroup`, `CreateLogStream`, `PutLogEvents`, `DescribeLogStreams` on `/nuon//*` 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](/guides/nuon-stack-terraform-provider#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.`. The required keys (matching the CloudFormation phone-home Lambda payload exactly): | Key | Notes | | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `request_type` | Always `"Create"` for the initial phone-home. | | `phone_home_type` | `"aws"`. | | `account_id`, `region` | AWS account and region the install lives in. | | `vpc_id` | The runner's VPC. | | `runner_subnet` | Single subnet ID where the runner lives. | | `public_subnets`, `private_subnets` | **Comma-joined strings**, not JSON arrays. ctl-api decodes these with `StringToSliceHookFunc(",")`. Sending a JSON list lands in postgres as the string `"[subnet-x subnet-y]"` and decodes to an empty list. | | `runner_security_group_id` | Runner SG — vendor sandboxes commonly add ingress rules pointing at this. | | `runner_iam_role_arn`, `runner_instance_profile` | Used for kube-runner / EKS access entries. | | `runner_asg_name`, `runner_log_group_name` | Used by the runner management UI. | | `provision_iam_role_arn`, `maintenance_iam_role_arn`, `deprovision_iam_role_arn` | Empty string when the corresponding role isn't declared. | | `break_glass_role_arns`, `custom_role_arns` | Maps of role-name → ARN. | | `install_inputs` | Echo of the install inputs (`data.stack_config.this.install_inputs`). | | `_arn` | One key per secret declared in `stack.toml`, flattened into the top-level payload. | ## GCP The GCP stack lives in [`terraform-gcp-stack`](https://github.com/nuonco/terraform-gcp-stack). ### 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 with the token returned by `data.stack_config.this.gcp.runner_api_token`. Protect Terraform state as sensitive because the token is stored in instance 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). * Report each role under its exact configured role name. Resource names may use deterministic hashes to satisfy GCP's 30-character service-account ID limit. ### 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: | Key | Notes | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `request_type` | Always `"Create"` for the initial phone-home. | | `phone_home_type` | `"gcp"`. | | `project_id`, `region` | GCP project and region. | | `network_name`, `network_id` | The runner's VPC network. | | `public_subnet_name`, `private_subnet_name`, `runner_subnet_name` | Subnet names (not IDs — GCP subnets are referenced by name in most APIs). | | `runner_service_account_email` | Used for sandbox impersonation grants. | | `provision_sa_email`, `maintenance_sa_email`, `deprovision_sa_email` | Empty string when the corresponding role isn't declared. | | `break_glass_sa_emails`, `custom_sa_emails` | Maps of role-name → SA email. | | `gke_node_pool_sa_email` | Pre-existing GKE node pool SA, or one created by the stack when `var.gke_node_pool_sa_email` is empty. | | `install_inputs` | Echo of the install inputs (`data.stack_config.this.install_inputs`). | | `` | One key per secret declared in `stack.toml`, flattened into the top-level payload (Secret Manager secret names, not ARNs). | ## Azure The Azure stack lives in [`terraform-azure-stack`](https://github.com/nuonco/terraform-azure-stack). ### Runner virtual machine * A Linux virtual machine in a subnet with outbound HTTPS to the Nuon API and artifact hosts. * Metadata passed to cloud-init must include the runner ID and runner API URL from `stack_config`. * The runner identity needs the role assignments required to assume configured operation, break-glass, and custom roles. ### Phone-home payload Report the subscription, resource group, VNet, runner subnet, runner identity, configured role IDs, install inputs, and custom-stack outputs through `stack_phone_home`. Keep the payload keys emitted by the published module even if a fork changes the underlying Azure resources; ctl-api and app templates treat those keys as the stable contract. Azure custom stacks use an Azure Deployment Stack backed by the uploaded linked ARM template. Preserve its `actionOnUnmanage` and delete query parameters unless intentionally changing whether removed resources are deleted or detached. See [Custom Install Stacks](/guides/custom-nested-stacks) for the cross-cloud custom-stack contract. ## 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 registry module from a parent Terraform configuration instead of forking: ```hcl theme={null} provider "stack" {} module "nuon" { source = "nuonco/stack/azure" version = "~> 1.0" install_id = var.install_id } ``` The module reads the rest of its configuration from the control plane via the `stack_config` data source, so you pass only the install ID and supported overrides. Use each module's README for its current source, version, required cloud provider configuration, and supported overrides. ## Contributing Contributions to the AWS, Azure, and GCP repositories above are welcome. # Deployment Options Source: https://docs.nuon.co/guides/deployment-options Choose how to run Nuon Nuon is available in three deployment models. **All three deploy your app into your customers' clouds on AWS, Azure, or Google Cloud. The choice is about where Nuon's own control plane runs.** See [Platform Support](/platform-support/introduction) for the per-cloud feature matrix. The fastest way to get started. Nuon Cloud is a fully managed, multi-tenant service hosted by Nuon. No infrastructure to provision. Sign up and start deploying to customers on AWS, Azure, and GCP immediately. Run a single-tenant Nuon control plane in your own AWS or Google Cloud account, managed remotely by Nuon. Deploy to customers on AWS, Azure, and GCP. You own the infrastructure and data; Nuon handles upgrades and operations via its own platform. Deploy and operate Nuon entirely within your own infrastructure, with no dependency on Nuon Cloud. Run the control plane on AWS, Azure, or GCP, and deploy to customers on any of the three. Open source; a paid license is required only if you need Nuon support. # Export Runner Audit Logs Source: https://docs.nuon.co/guides/export-runner-audit-logs Forward Nuon runner audit events to your own OTLP-compatible logging backend. Nuon runners can forward audit events directly from your cloud account to an OTLP-compatible logging backend. This gives your security and operations teams a customer-owned record of deployment, action workflow, and sandbox activity. Only records marked with `nuon.audit="true"` are forwarded. Application logs, general runner logs, and other infrastructure logs are not included. ## Supported environments Runner audit log export supports AWS, Azure, and GCP install runners: | Provider | Supported install stack | Configuration secret | | -------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | AWS | Generated CloudFormation stack or [Nuon Terraform install stack](https://github.com/nuonco/install-stacks) | `nuon//telemetry-export-config` | | Azure | Generated Azure install stack | `telemetry-export-config` in the install Key Vault | | GCP | [Nuon Terraform install stack](https://github.com/nuonco/install-stacks) | `-telemetry-export-config` | The AWS and GCP stacks create the configuration secret and grant the runner read access. On Azure, create the install Key Vault before deploying the stack; the stack grants its runner managed identity read access. In every case, update the secret after the stack is provisioned. Backend credentials stay in your cloud account and are not stored by the Nuon control plane or in Terraform state. ## Prerequisites You will need: * A current Nuon runner on AWS, Azure, or GCP * The current install stack, applied to configure runner access to the cloud secret manager * An OTLP/HTTP logs endpoint available over HTTPS * Any headers required to authenticate with your logging backend * Permission to add a secret value in your cloud account ## Create the configuration Save the following configuration as `telemetry-export-config.yaml`: ```yaml telemetry-export-config.yaml theme={null} version: v1 telemetry: logs: audit: enabled: true exporters: otlphttp: endpoint: https://otlp.example.com headers: Authorization: Bearer ``` ## Configuration reference | Field | Required | Description | | ------------------------------ | ------------------------------ | --------------------------------------------------------------------- | | `version` | Yes | Configuration format version. Set to `v1`. | | `telemetry.logs.audit.enabled` | No | Set to `true` to enable runner audit log export. Defaults to `false`. | | `exporters.otlphttp.endpoint` | Yes, when telemetry is enabled | HTTPS URL for the OTLP/HTTP backend. | | `exporters.otlphttp.headers` | No | Headers used to authenticate with the backend. | The endpoint cannot contain credentials, a query string, a fragment, or environment-variable expansion. Omit `headers` if your backend does not require them. ## Update the configuration secret Apply or update the install stack first, then use the command for your provider to update the configuration secret. ### AWS Generated CloudFormation stacks and Nuon Terraform install stacks both create the same secret. Update it after the stack finishes provisioning: ```sh theme={null} aws secretsmanager put-secret-value \ --secret-id "nuon//telemetry-export-config" \ --secret-string file://telemetry-export-config.yaml \ --region "" ``` For an existing CloudFormation install, reprovision the install to generate the current template and update the stack before updating the secret. For a Terraform install, update the install stack module and run `terraform apply` first. ### Azure The install Key Vault name is the first 24 characters of the install ID. Update the secret after the Azure stack finishes provisioning: ```sh theme={null} az keyvault secret set \ --vault-name "" \ --name "telemetry-export-config" \ --file "telemetry-export-config.yaml" \ --encoding utf-8 ``` ### GCP The Nuon Terraform install stack creates the secret. Update it after `terraform apply` finishes: ```sh theme={null} gcloud secrets versions add "-telemetry-export-config" \ --data-file="telemetry-export-config.yaml" \ --project="" ``` The runner checks for changes every 30 seconds and applies a changed valid configuration without restarting the runner. ## Runner access to the configuration Nuon-generated Stacks grant the runner the required read access: | Provider | Runner identity | Access | | -------- | -------------------------------------------- | ------------------------------------------------------------------- | | AWS | Runner instance role | `secretsmanager:GetSecretValue` and `secretsmanager:DescribeSecret` | | Azure | Runner VMSS system-assigned managed identity | Key Vault Secrets User | | GCP | Runner service account | Secret Manager Secret Accessor | ## Exported attributes Every exported record includes attributes that identify it as a Nuon runner audit event: | Attribute | Value | | --------------------- | ----------------------------------- | | `nuon.audit` | `true` | | `nuon.audit.event` | The audited operation type | | `nuon.audit.outcome` | `started`, `succeeded`, or `failed` | | `service.namespace` | `nuon` | | `service.name` | `runner` | | `service.version` | The runner version, when available | | `service.instance.id` | The runner ID, when available | Records also include relevant identifiers such as `org.id`, `install.id`, `runner_job.group`, `runner_job.operation`, and entity-specific component, deploy, action workflow, or sandbox identifiers when available. ## Verify export 1. Update the secret with a valid configuration. 2. Check the runner logs for `runner telemetry export collector enabled` with `audit_export.enabled=true`. 3. Trigger a deployment, action workflow, or sandbox operation. 4. Query your logging backend for records where `service.namespace="nuon"` and `nuon.audit="true"`. Audit events are produced when supported runner operations occur. A newly enabled exporter may remain quiet until one of these operations runs. ## Update, rotate, or disable export Edit `telemetry-export-config.yaml` and run the same provider upload command to change the endpoint or rotate authentication headers. The runner detects the new secret value automatically. To disable audit export while leaving the telemetry collector available for future telemetry types, set `telemetry.logs.audit.enabled` to `false` and update the secret. The collector runs without an audit log pipeline. To stop the telemetry collector entirely, remove or disable the secret value or remove runner access to it. Normal runner operations and Nuon-managed logging continue unchanged. ## Failure behavior Audit export does not block runner jobs. A missing, empty, disabled, or inaccessible secret stops the telemetry collector. An invalid changed value or a transient read or startup failure does not replace a previously active valid configuration. Once a valid configuration becomes available, the runner detects it automatically. Delivery is best effort. The collector queues and retries exports, but records may be dropped during a prolonged backend outage. Runner jobs continue operating normally while the backend is unavailable. ## Troubleshooting ### The configuration secret does not exist Reprovision the install and apply the latest generated CloudFormation or Terraform stack. On Azure, create the Key Vault shown in the dashboard before deploying the install stack. ### AWS reports that the secret is scheduled for deletion If a secret with the same name is already scheduled for deletion, the Stack cannot create it. If the value does not need to be retained, permanently delete the scheduled secret, wait for deletion to complete, and retry the Stack update: ```sh theme={null} aws secretsmanager delete-secret \ --secret-id "nuon//telemetry-export-config" \ --force-delete-without-recovery \ --region "" ``` If the value must be retained, restore and copy it before permanently deleting the old secret. ### The runner reports that audit export is disabled Confirm that the provider-specific secret shown above contains a current value and that the runner identity can read it. Check the runner logs for `telemetry_export.reason`, which distinguishes a missing, empty, or inaccessible secret. ### The configuration is rejected Confirm that: * The secret contains valid YAML matching the configuration example * The YAML has `version: v1` * `telemetry.logs.audit.enabled` is a boolean * The YAML contains `exporters.otlphttp.endpoint` and optional `exporters.otlphttp.headers` when audit export is enabled * The endpoint uses HTTPS and does not contain credentials, a query string, a fragment, or environment-variable expansion * Header names are valid HTTP header names and header values do not contain line breaks ### Export is enabled, but no records appear * Trigger a deployment, action workflow, or sandbox operation to produce an audit event. * Query for both `service.namespace="nuon"` and `nuon.audit="true"`. * Confirm that the OTLP endpoint accepts logs over OTLP/HTTP. * Check the runner logs for TLS, proxy, authentication, or exporter errors. # External Image Policies Source: https://docs.nuon.co/guides/external-image-policies Write OPA policies to enforce security requirements on external container images External image policies allow you to enforce security requirements on container images pulled from public or private registries before they are deployed to customer installs. Nuon automatically fetches rich metadata about container images including SBOMs, signatures, attestations, and in-toto statements, making this data available to your OPA policies. OPA policies inspect the metadata Nuon discovers, but they do not cryptographically verify a signature. To prove that a trusted identity or key signed the resolved image digest, configure [native container image signature verification](/guides/image-signature-verification). Use OPA for additional requirements such as SBOM presence, provenance contents, registry allowlists, and tag rules. ## Policy Input Structure When evaluating external image policies, Nuon provides the following input structure: ```json theme={null} { "image": "nginx", "tag": "latest", "digest": "sha256:abc123...", "metadata": { "image": "nginx", "tag": "latest", "digest": "sha256:abc123...", "signed": true, "sbom": { "present": true, "format": "spdx" }, "signatures": [...], "attestations": [...], "index": {...}, "attestation_manifests": [...] } } ``` ## Quick Reference | Field | Type | Description | | -------------------------------------- | ------ | -------------------------------------------------------------------------------------------- | | `input.image` | string | Image name (e.g., `nginx`, `gcr.io/project/app`) | | `input.tag` | string | Image tag (e.g., `latest`, `v1.2.3`) | | `input.digest` | string | Image digest (e.g., `sha256:abc...`) | | `input.metadata.signed` | bool | Whether signature metadata was discovered; this does not indicate cryptographic verification | | `input.metadata.sbom.present` | bool | Whether an SBOM is present | | `input.metadata.sbom.format` | string | SBOM format: `spdx`, `cyclonedx`, or `unknown` | | `input.metadata.signatures` | array | List of signature details | | `input.metadata.attestations` | array | List of attestation types from OCI referrers | | `input.metadata.index` | object | Raw OCI image index (manifest list) | | `input.metadata.attestation_manifests` | array | Full attestation manifest data with layers | *** ## Basic Image Requirements ### Require Signature Metadata Require Nuon to discover signature metadata for an image. This is useful as a metadata-presence rule, but it does not replace native cryptographic verification: ```rego theme={null} package nuon default allow := false allow if { input.metadata.signed == true } deny contains msg if { not input.metadata.signed msg := sprintf("Image %s:%s must include signature metadata", [input.image, input.tag]) } ``` ### Require SBOM Presence Ensure all images include a Software Bill of Materials: ```rego theme={null} package nuon default allow := false allow if { input.metadata.sbom.present == true } deny contains msg if { not input.metadata.sbom.present msg := sprintf("Image %s:%s must include an SBOM", [input.image, input.tag]) } ``` ### Require Specific SBOM Format Enforce a specific SBOM format (SPDX or CycloneDX): ```rego theme={null} package nuon default allow := false allow if { input.metadata.sbom.present == true input.metadata.sbom.format == "spdx" } deny contains msg if { input.metadata.sbom.present input.metadata.sbom.format != "spdx" msg := sprintf("Image %s:%s has SBOM format '%s', but 'spdx' is required", [input.image, input.tag, input.metadata.sbom.format]) } deny contains msg if { not input.metadata.sbom.present msg := sprintf("Image %s:%s must include an SPDX SBOM", [input.image, input.tag]) } ``` *** ## Signature Inspection ### Inspect Signature Algorithm Require discovered signature metadata to identify a specific signing format: ```rego theme={null} package nuon default allow := false allow if { some sig in input.metadata.signatures contains(sig.algorithm, "cosign") } deny contains msg if { count(input.metadata.signatures) == 0 msg := sprintf("Image %s:%s has no signatures", [input.image, input.tag]) } deny contains msg if { count(input.metadata.signatures) > 0 not has_cosign_signature msg := sprintf("Image %s:%s must include Cosign signature metadata", [input.image, input.tag]) } has_cosign_signature if { some sig in input.metadata.signatures contains(sig.algorithm, "cosign") } ``` ### Inspect Signature Issuer Check the issuer recorded in discovered keyless signature metadata. This does not validate the certificate chain or signature; use [native signature verification](/guides/image-signature-verification#verify-a-keyless-signature) to trust the issuer cryptographically. ```rego theme={null} package nuon default allow := false trusted_issuers := { "https://accounts.google.com", "https://token.actions.githubusercontent.com" } allow if { some sig in input.metadata.signatures sig.issuer in trusted_issuers } deny contains msg if { not has_trusted_signature msg := sprintf("Image %s:%s must include signature metadata from an allowed issuer: %v", [input.image, input.tag, trusted_issuers]) } has_trusted_signature if { some sig in input.metadata.signatures sig.issuer in trusted_issuers } ``` *** ## Attestation Policies ### Require SLSA Provenance Ensure images have SLSA provenance attestations: ```rego theme={null} package nuon default allow := false allow if { has_slsa_provenance } has_slsa_provenance if { some att in input.metadata.attestations contains(att.type, "slsa.dev/provenance") } has_slsa_provenance if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers contains(layer.predicate_type, "slsa.dev/provenance") } deny contains msg if { not has_slsa_provenance msg := sprintf("Image %s:%s must have SLSA provenance attestation", [input.image, input.tag]) } ``` ### Check Specific Predicate Types Inspect attestation layers for specific predicate types: ```rego theme={null} package nuon default allow := false required_predicates := { "https://slsa.dev/provenance/v1", "https://spdx.dev/Document" } allow if { found_predicates := {p | some manifest in input.metadata.attestation_manifests some layer in manifest.layers p := layer.predicate_type } count(required_predicates - found_predicates) == 0 } deny contains msg if { found_predicates := {p | some manifest in input.metadata.attestation_manifests some layer in manifest.layers p := layer.predicate_type } missing := required_predicates - found_predicates count(missing) > 0 msg := sprintf("Image %s:%s missing required attestations: %v", [input.image, input.tag, missing]) } ``` *** ## Advanced: Decoded Attestation Content When attestation layers are fetched with content decoding enabled, you can inspect the decoded in-toto statements. ### Inspect In-Toto Statement Type ```rego theme={null} package nuon default allow := false allow if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers layer.decoded._type == "https://in-toto.io/Statement/v1" } deny contains msg if { not has_valid_intoto msg := sprintf("Image %s:%s must have valid in-toto v1 statements", [input.image, input.tag]) } has_valid_intoto if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers layer.decoded._type == "https://in-toto.io/Statement/v1" } ``` ### Check the Declared Subject Digest Check whether decoded attestation metadata declares the image digest as its subject. This is not proof that the attestation itself has a valid signature: ```rego theme={null} package nuon default allow := false allow if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers some subject in layer.decoded.subject subject.digest.sha256 == trim_prefix(input.digest, "sha256:") } deny contains msg if { not subject_matches_image msg := sprintf("Image %s:%s attestation subjects do not match image digest", [input.image, input.tag]) } subject_matches_image if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers some subject in layer.decoded.subject subject.digest.sha256 == trim_prefix(input.digest, "sha256:") } ``` ### Inspect SLSA Provenance Predicate Access the full provenance predicate for advanced validation: ```rego theme={null} package nuon default allow := false # Require builds from a specific GitHub repository allowed_repos := {"github.com/myorg/myapp"} allow if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers contains(layer.predicate_type, "slsa.dev/provenance") # Access the predicate content predicate := layer.decoded.predicate # Check build source (structure varies by SLSA version) some repo in allowed_repos contains(predicate.buildDefinition.externalParameters.source.uri, repo) } deny contains msg if { has_provenance not from_allowed_repo msg := sprintf("Image %s:%s must be built from allowed repositories: %v", [input.image, input.tag, allowed_repos]) } has_provenance if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers contains(layer.predicate_type, "slsa.dev/provenance") } from_allowed_repo if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers contains(layer.predicate_type, "slsa.dev/provenance") predicate := layer.decoded.predicate some repo in allowed_repos contains(predicate.buildDefinition.externalParameters.source.uri, repo) } ``` *** ## Platform-Specific Policies ### Require Multi-Architecture Support Ensure images support specific platforms: ```rego theme={null} package nuon default allow := false required_platforms := { {"os": "linux", "architecture": "amd64"}, {"os": "linux", "architecture": "arm64"} } allow if { input.metadata.index != null platforms := {{"os": m.platform.os, "architecture": m.platform.architecture} | some m in input.metadata.index.manifests m.platform != null not m.is_attestation } count(required_platforms - platforms) == 0 } deny contains msg if { input.metadata.index == null msg := sprintf("Image %s:%s must be a multi-platform image", [input.image, input.tag]) } deny contains msg if { input.metadata.index != null platforms := {{"os": m.platform.os, "architecture": m.platform.architecture} | some m in input.metadata.index.manifests m.platform != null not m.is_attestation } missing := required_platforms - platforms count(missing) > 0 msg := sprintf("Image %s:%s missing required platforms: %v", [input.image, input.tag, missing]) } ``` *** ## Image Registry Policies ### Allowlist Trusted Registries Only allow images from approved registries: ```rego theme={null} package nuon default allow := false trusted_registries := { "gcr.io/myproject", "us-docker.pkg.dev/myproject", "123456789.dkr.ecr.us-west-2.amazonaws.com" } allow if { some registry in trusted_registries startswith(input.image, registry) } # Allow Docker Hub official images allow if { not contains(input.image, "/") # Single-name images like "nginx" } allow if { startswith(input.image, "library/") } deny contains msg if { not from_trusted_registry msg := sprintf("Image %s is not from a trusted registry. Allowed: %v", [input.image, trusted_registries]) } from_trusted_registry if { some registry in trusted_registries startswith(input.image, registry) } from_trusted_registry if { not contains(input.image, "/") } from_trusted_registry if { startswith(input.image, "library/") } ``` ### Block Latest Tag Prevent use of mutable tags: ```rego theme={null} package nuon default allow := false blocked_tags := {"latest", "main", "master", "dev", "develop"} allow if { not input.tag in blocked_tags } deny contains msg if { input.tag in blocked_tags msg := sprintf("Image %s uses blocked tag '%s'. Use immutable version tags.", [input.image, input.tag]) } ``` *** ## Combining Multiple Requirements ### Image Metadata Policy A policy combining several image metadata requirements. Pair this with native signature verification when signer trust is required: ```rego theme={null} package nuon default allow := false # Image must meet ALL requirements allow if { has_signature_metadata has_sbom has_provenance not uses_blocked_tag } # Check whether signature metadata was discovered has_signature_metadata if { input.metadata.signed == true } # Check for SBOM (via referrers or attestation layers) has_sbom if { input.metadata.sbom.present == true } # Check for provenance attestation has_provenance if { some manifest in input.metadata.attestation_manifests some layer in manifest.layers contains(layer.predicate_type, "slsa.dev/provenance") } has_provenance if { some att in input.metadata.attestations contains(att.type, "provenance") } # Block mutable tags blocked_tags := {"latest", "main", "master"} uses_blocked_tag if { input.tag in blocked_tags } # Generate specific denial messages deny contains msg if { not has_signature_metadata msg := sprintf("Image %s:%s must include signature metadata", [input.image, input.tag]) } deny contains msg if { not has_sbom msg := sprintf("Image %s:%s must include an SBOM", [input.image, input.tag]) } deny contains msg if { not has_provenance msg := sprintf("Image %s:%s must have SLSA provenance", [input.image, input.tag]) } deny contains msg if { uses_blocked_tag msg := sprintf("Image %s uses blocked tag '%s'", [input.image, input.tag]) } ``` *** ## Configuring Image Policies Add external image policies to your Nuon configuration: ```toml policies/external-images.toml theme={null} [[policy]] type = "container_image" engine = "opa" # Apply to specific components components = ["app_image", "sidecar_image"] # Or apply to all container image components # components = ["*"] contents = """ package nuon default allow := false allow if { has_signature_metadata input.metadata.sbom.present == true } has_signature_metadata if { input.metadata.signed == true } deny contains msg if { not has_signature_metadata msg := sprintf("Image %s:%s must include signature metadata", [input.image, input.tag]) } deny contains msg if { not input.metadata.sbom.present msg := sprintf("Image %s:%s must include an SBOM", [input.image, input.tag]) } """ ``` ### Using External Policy Files Reference policies from your repository: ```toml policies/external-images.toml theme={null} [[policy]] type = "container_image" engine = "opa" components = ["*"] contents = "file://policies/rego/container-image.rego" ``` *** ## Metadata Field Reference ### `input.metadata.sbom` | Field | Type | Description | | --------- | ------ | --------------------------------------------------------------- | | `present` | bool | `true` if SBOM detected via OCI referrers or attestation layers | | `format` | string | `spdx`, `cyclonedx`, or `unknown` | | `uri` | string | URI to SBOM artifact (when available) | **SBOM Detection**: Nuon detects SBOMs from two sources: 1. OCI referrers with SBOM artifact types 2. Attestation layers with predicate types: * `https://spdx.dev/Document` → format: `spdx` * `https://cyclonedx.org/bom` → format: `cyclonedx` ### `input.metadata.signatures` | Field | Type | Description | | ----------- | ------ | --------------------------------------------- | | `key_id` | string | Discovered key identifier (for keyed signing) | | `issuer` | string | Discovered OIDC issuer (for keyless signing) | | `subject` | string | Discovered OIDC subject identity | | `algorithm` | string | Discovered signature algorithm/media type | ### `input.metadata.attestations` Attestations discovered via OCI referrers: | Field | Type | Description | | ----------- | ------ | ------------------------------- | | `type` | string | Attestation artifact type | | `predicate` | string | Predicate type (when available) | ### `input.metadata.attestation_manifests` Full attestation manifest data including layers: | Field | Type | Description | | ------------- | ------ | ----------------------------------------------- | | `digest` | string | Manifest digest | | `media_type` | string | Manifest media type | | `platform` | object | Platform spec (`os`, `architecture`, `variant`) | | `ref_digest` | string | Referenced image digest | | `annotations` | object | OCI annotations | | `layers` | array | Attestation layer blobs | ### `input.metadata.attestation_manifests[].layers` | Field | Type | Description | | ---------------- | ------ | ------------------------------------------ | | `digest` | string | Layer blob digest | | `media_type` | string | Layer media type | | `size` | int | Layer size in bytes | | `predicate_type` | string | In-toto predicate type | | `decoded` | object | Decoded in-toto statement (when available) | | `truncated` | bool | `true` if layer was too large to fetch | ### `input.metadata.attestation_manifests[].layers[].decoded` Decoded in-toto statement: | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------- | | `_type` | string | Statement type (e.g., `https://in-toto.io/Statement/v1`) | | `subject` | array | Statement subjects with name and digest | | `predicateType` | string | Predicate type URI | | `predicate` | object | Full predicate content (format varies by type) | ### `input.metadata.index` OCI image index (manifest list): | Field | Type | Description | | ------------ | ------ | ------------------------ | | `digest` | string | Index digest | | `media_type` | string | Index media type | | `manifests` | array | List of manifest entries | ### `input.metadata.index.manifests` | Field | Type | Description | | ---------------- | ------ | ----------------------------------------- | | `digest` | string | Manifest digest | | `media_type` | string | Manifest media type | | `platform` | object | Platform spec | | `annotations` | object | OCI annotations | | `is_attestation` | bool | `true` if this is an attestation manifest | # GitHub Actions Source: https://docs.nuon.co/guides/github-actions Integrate with Nuon from your GitHub Actions workflows. If you need to integrate with Nuon from your GitHub Actions workflows, we recommend using our [official CLI action](https://github.com/nuonco/actions-nuon). This will handle installing the CLI and authenticating with Nuon for you, making it easy to run any CLI command as a GitHub Actions step. This guide walks through a common use case: triggering an [app branch](/concepts/app-branches) run from a repo where you do not have webhook permissions. ## Prerequisites * An app with a branch configured and synced. See the [app branches guide](/guides/app-branches) for details. * Org admin access, to create the trust policy or API token the workflow authenticates with. ## Using OIDC We strongly recommend configuring GitHub Actions to authenticate with Nuon using OIDC, so no long-lived API tokens need to be stored in GitHub. Create a trust policy for the repository and its protected branch on the **Manage OIDC** page in the Dashboard. Select the repo you want to trust and the form will be filled out for you. You can also use the CLI. ```sh theme={null} nuon orgs oidc-trust-policies create \ --name github-actions \ --issuer https://token.actions.githubusercontent.com \ --audience https://api.nuon.co \ --role org_admin \ --ttl 900 \ --claim sub=repo:acme/app:ref:refs/heads/main ``` Keep the `sub` condition limited to a protected branch and the `--ttl` short. The exchanged token only needs to outlive the one command it runs. See [claim matching](/concepts/oidc-federation#claim-matching) for other supported patterns. Triggering an App Branch run currently requires `org_admin` permissions. We are in the process of adding more roles so such broad permissions are not required. ## Trigger a branch run from your workflow Grant the job permission to request an OIDC token by setting the `id-token: write` permission on the workflow. Then configure a job step to use the Nuon CLI action. The CLI will detect that it's running in GitHub Actions and perform the token exchange automatically. ```yaml .github/workflows/rollout.yaml theme={null} on: push: branches: - main permissions: contents: read id-token: write jobs: rollout: runs-on: ubuntu-latest name: Roll out steps: - name: Checkout code uses: actions/checkout@v4 - name: Run your tests run: make test - name: Trigger branch run uses: nuonco/actions-nuon@v0.4.1 with: org_id: ${{ vars.nuon_org_id }} app_id: ${{ vars.nuon_app_id }} command: "apps branches trigger --branch-id main --no-wait" ``` `--no-wait` will tell the CLI to return as soon as the App Branch run has been successfully created, allowing the GitHub Actions workflow to complete. Depending on how your App Branch is configured, there may still be manual approvals required in the workflows that are triggered. ### Variations `--preview` makes the run plan-only: every group is planned, nothing is applied, and you read the per-install diffs from the completed run: ```yaml theme={null} command: "apps branches trigger --branch-id main --no-wait --preview" ``` `--force` rebuilds every component instead of only what changed since the previous run: ```yaml theme={null} command: "apps branches trigger --branch-id main --no-wait --force" ``` ### Further usage The `command` input takes any CLI command, so the same action covers whatever else your pipeline needs — for example, syncing install config files from your repo: ```yaml theme={null} command: "installs sync -a ${{ vars.nuon_app_id }} -d ./installs --confirm --approve-all" ``` ### Action inputs | Input | Required | Description | | --------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `org_id` | yes | Your Nuon org ID. | | `command` | no | The `nuon` command to run, without the leading `nuon`. Omit it to only install and configure the CLI. | | `app_id` | no | Default app for commands that take one. | | `api_token` | no | A static API token. Omit it to authenticate with OIDC. | | `api_url` | no | The control plane API URL. Defaults to `https://api.nuon.co`. | | `oidc_audience` | no | Audience for the OIDC token, if your trust policy's audience differs from `api_url`. | | `nuon_version` | no | CLI version to install. Defaults to the version your control plane reports. | For a self-hosted control plane, pass its API URL as the `api_url` input and use the same URL as the trust policy audience. If the two must differ, set `oidc_audience` to match the policy. ## Using static API tokens If you can't use OIDC — for example, your organization disallows the `id-token: write` permission — you can authenticate using a long-lived [API token](/concepts/api-tokens). Create the token and save it in a GitHub Actions secret named `NUON_API_TOKEN`. ```sh theme={null} nuon orgs api-tokens create --name github-actions --role org_admin ``` Then configure it on the workflow job step. The `id-token: write` permission is not required if you are using a token. ```yaml theme={null} - name: Trigger branch run uses: nuonco/actions-nuon@v0.4.1 with: api_token: ${{ secrets.NUON_API_TOKEN }} org_id: ${{ vars.nuon_org_id }} app_id: ${{ vars.nuon_app_id }} command: "apps branches trigger --branch-id main --no-wait" ``` When using static tokens, we strongly recommend rotating them as frequently as is practical. ## Next steps Write the branch config, group your installs, and approve rollouts. Run types, plan and approval flow, and install version history. Trust policies, claim matching, and other CI providers. React to branch run events from your own systems. # Helm Chart Components Source: https://docs.nuon.co/guides/helm-chart-components Helm chart components allow you to connect an existing Helm chart and deploy it into your customer's cloud account. Helm chart components allow you to deploy any helm from a public or connected repo. ## Configuring a Helm component To configure a Helm component, specify a repo, the required configuration values and version to deploy it with. ```toml components/helm_chart.toml theme={null} # helm name = "helm_chart" type = "helm_chart" chart_name = "" [connected_repo] directory = "components/helm-chart" repo = "/" branch = "main" [[values_file]] contents = """ image: tag: "{{.nuon.components.app_image.image.tag}}" """ # alternatively, you can read from disk [[values_file]] source = "./values.yaml" ``` You can also put the Helm chart source inside of the same repo as the app, and point to it. If you include the Helm chart source in the app repo, you must push the entire repo and do a manual build of the component in the dashboard or CLI. The CLI's `nuon apps sync` will not recognize that the component has changed so will not do the build. ## Multiple ways to use Helm with components You can configure Helm components to use either a Helm repository (using a `helm_repo` block), a public repo (using a `public_repo` block), or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). ## Using Public Helm Repositories Helm repositories are the standard way to share and discover Helm charts across the Kubernetes ecosystem. You can deploy charts directly from public Helm repositories without needing to store them in your own Git repository. To use a public Helm chart, specify the repository URL and chart name: ```toml components/prometheus.toml theme={null} # helm name = "prometheus" type = "helm_chart" chart_name = "prometheus" [helm_repo] repo_url = "https://prometheus-community.github.io/helm-charts" chart = "kube-prometheus-stack" ``` This is equivalent to the Helm CLI command: ```bash theme={null} helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install prometheus prometheus-community/kube-prometheus-stack ``` You can find public Helm charts on [Artifact Hub](https://artifacthub.io/), which indexes thousands of open-source Helm charts. Each chart page lists its repository URL, which you can use in the `repo_url` field. ## Open Source and Private Charts Nuon supports any Helm chart that can be accessed using git. It is common for apps to have both a combination of public, open source helm charts for deploying standard components and private helm charts for application specific configuration. ## Helm Value Configuration One of the most important parts of deploying a Helm chart into a customer account, includes setting values properly. Values can be used for everything from accessing infrastructure, setting images or scaling pods. [Variables](/guides/using-variables) allow you to set default values for a Nuon app, expose customer configuration options, reference infrastructure from other components or access an image that was synced into the customer account. Examples for accessing common template variables: ```toml components/helm_chart.toml theme={null} # helm name = "helm_chart" type = "helm_chart" chart_name = "" [connected_repo] directory = "components/helm-chart" repo = "/" branch = "main" [values] # access a synced image image_repository = "{{.nuon.components.image.image.repository.uri}}" image_tag = "{{.nuon.components.image.image.tag}}" # access outputs from a terraform component output_value = "{{.nuon.components.terraform.outputs.output_value}}" # access outputs from the sandbox aws_region = "{{.nuon.install.sandbox.outputs.account.aws_region}}" vpc_id = "{{.nuon.install.sandbox.outputs.vpc.id}}" # access information about the install domain public_root_domain = "{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" internal_root_domain = "{{.nuon.sandbox.outputs.nuon_dns.internal_domain.name}}" ``` ## Using a Helm Values File You can add a values file and locate it in the app repository. ```toml components/grafana.toml theme={null} # helm name = "grafana" type = "helm_chart" chart_name = "grafana" namespace = "grafana" storage_driver = "configmap" dependencies = ["prometheus", "grafana_secrets", "exampledb_secrets"] [public_repo] repo = "grafana/helm-charts" directory = "charts/grafana" branch = "main" [[values_file]] contents = "./values/grafana/values.yaml" ``` ## Nuon Managed Sandbox Components and DNS The `aws-eks-sandbox` [managed sandbox](/concepts/sandboxes#nuon-managed-sandboxes) ships with standard Helm components + DNS zones to solve for common use cases, such as exposing a public or private https service. This includes: * [AWS ALB Ingress Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller) * [External DNS](https://github.com/kubernetes-sigs/external-dns) * [Cert Manager](https://cert-manager.io/) * [Nginx Ingress](https://docs.nginx.com/nginx-ingress-controller/) * [EBS CSI](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) The Sandboxes are [open source](/concepts/sandboxes#nuon-managed-sandboxes) and can be [customized](/concepts/sandboxes#create-a-custom-sandbox), if these components do not work for your application. ## Using Domains The [Nuon managed Sandboxes](/concepts/sandboxes#nuon-managed-sandboxes) automatically deploy the components required to provision DNS, Certificate and Load Balancer resources using Helm. There are multiple ways to use these components. Here are examples of the two most common. ### AWS NLB with AWS ACM If you want to leverage AWS services, you can provision an NLB that uses an ACM certificate. Your chart would contain a Service resource, configured so that the operator will create an NLB. ```yaml nlb_public.tpl theme={null} --- apiVersion: v1 kind: Service metadata: name: nlb-public namespace: {{ .Release.Namespace }} labels: {} annotations: service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: preserve_client_ip.enabled=false service.beta.kubernetes.io/aws-load-balancer-ssl-cert: {{ .Values.api.nlbs.public_domain_certificate_arn }} service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp external-dns.alpha.kubernetes.io/hostname: {{ .Values.api.nlbs.public_domain }} spec: type: LoadBalancer loadBalancerClass: service.k8s.aws/nlb allocateLoadBalancerNodePorts: false externalTrafficPolicy: Local internalTrafficPolicy: Local selector: {} ports: - name: https port: 443 targetPort: http ``` You would parametrize the Service with the following values in your chart's `values.yaml` file, to accept public and private domains and an ACM certificate ARN. ```yaml theme={null} nlbs: public_domain: nlb.INSTALL_PUBLIC_DOMAIN internal_domain: nlb.internal.INSTALL_INTERNAL_DOMAIN public_domain_certificate: nbl.PUBLIC_DOMAIN_CERTIFICATE ``` Then, you could set those values per install with the following component config. ```toml components/nlb.toml theme={null} # helm name = "nlb" type = "helm_chart" chart_name = "nlb" [connected_repo] directory = "components/nlb" repo = "/" branch = "main" [values] "nlbs.public_domain" = "nlb.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" "nlbs.public_domain_certificate_arn" = "{{.nuon.components.infra.outputs.public_domain_certificate_arn}}" ``` ### Nginx Ingress with Cert Manager If you would prefer to manage resources within Kubernetes instead, you could use the Nginx ingress and have the `cert-manager` operator provision a certificate. In your chart, you would define the Ingress and Certificate resources. ```yaml components/api_public.tpl theme={null} --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api-public namespace: {{ .Release.Namespace }} labels: {} annotations: external-dns.alpha.kubernetes.io/hostname: {{ .Values.nginx.public_domain }} kubernetes.io/ingress.class: nginx spec: tls: - hosts: - {{ .Values.nginx.public_domain }} secretName: e2e-ingress-public-tls rules: - host: {{ .Values.nginx.public_domain}} http: paths: - path: / pathType: Prefix backend: service: name: api port: number: 80 --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: api-nginx namespace: {{ .Release.Namespace }} labels: {{- include "common.apiLabels" . | nindent 4 }} spec: secretName: e2e-ingress-public-tls dnsNames: - {{ .Values.nginx.public_domain }} issuerRef: name: public-issuer kind: ClusterIssuer ``` Then, allow configuring the domain in the chart's `values.yaml` file. ```yaml theme={null} nginx: public_domain: nlb.INSTALL_PUBLIC_DOMAIN ``` You could then set the domain per install in your Component config. ```toml components/nlb.toml theme={null} # helm name = "nlb" type = "helm_chart" chart_name = "nlb" [connected_repo] directory = "components/nlb" repo = "/" branch = "main" [values] "nlbs.public_domain" = "nlb.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" ```
By default, Nuon provisions a custom domain for each install id under the `nuon.run` domain (eg: inl8ybt7gajofh33e0h3pqyw1t.nuon.run). If you would like to use a customer provided domain, you can accept customer domains using [inputs](/concepts/app-inputs.mdx). We are planning to improve support for this, and would love to [hear about](https://nuon.co/contact-us) your use case. ## Per-install value overrides To customize a Helm chart's values for a single install without changing your app config, use [component overrides](/guides/component-overrides). # Verify Container Image Signatures Source: https://docs.nuon.co/guides/image-signature-verification Require container images to be signed by trusted Sigstore identities or Cosign keys before Nuon copies them. Nuon can require a container image to have a valid signature before copying it into Nuon's registry. Verification runs when the container image component builds, after Nuon resolves the configured tag to an immutable digest and before it copies that digest. Add a `verification` block to any `container_image` component. If the image does not satisfy a configured authority, the component build fails and the image is not copied. ## Verify a keyless signature Use `keyless` for images signed with a certificate issued through Sigstore. Pin both the OIDC issuer and the certificate subject so a valid signature from an unrelated identity is not accepted. ```toml components/api.toml theme={null} name = "api" type = "container_image" [public] image_url = "ghcr.io/acme/api" tag = "v1.4.0" [verification] require_signature = true [[verification.authorities]] type = "keyless" issuer = "https://token.actions.githubusercontent.com" subject = "https://github.com/acme/api/.github/workflows/release.yaml@refs/heads/main" ``` For identities that intentionally vary, use `subject_regexp` instead of `subject`. Exactly one of the two fields is required. ```toml theme={null} [[verification.authorities]] type = "keyless" issuer = "https://token.actions.githubusercontent.com" subject_regexp = '^https://github\.com/acme/api/\.github/workflows/release\.yaml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' ``` Prefer an exact subject when one identity signs all releases. Keep regular expressions narrow enough that they cannot match pull request workflows, forks, or other repositories. ## Verify with a Cosign public key Use `public_key` for images signed with a managed key pair. Commit the public key with the app configuration and reference it by relative path. Nuon reads the public key into the configuration during sync; the private key is never provided to Nuon. ```toml components/api.toml theme={null} name = "api" type = "container_image" [public] image_url = "ghcr.io/acme/api" tag = "v1.4.0" [verification] require_signature = true [[verification.authorities]] type = "public_key" public_key = "./cosign.pub" ``` The path is relative to the app's `components` directory. You can also provide the PEM-encoded public key directly. ## Trust more than one authority Multiple authorities use **OR** semantics. A build succeeds when any one authority verifies the resolved image digest. This supports key rotation or a transition between key-based and keyless signing without an unsigned window. ```toml theme={null} [verification] require_signature = true [[verification.authorities]] type = "public_key" public_key = "./current-cosign.pub" [[verification.authorities]] type = "public_key" public_key = "./next-cosign.pub" ``` Remove the old authority after all published images are signed by the replacement authority. ## What Nuon copies After verification succeeds, Nuon copies the resolved image and its attached OCI metadata. This includes recursively linked OCI referrers such as signatures, SBOMs, provenance, and other attestations. Nuon also copies legacy Cosign digest tags ending in `.sig`, `.att`, and `.sbom`. This preserves the metadata so the copied image can be inspected and verified in its destination registry. Registry credentials configured on the component are used for both image discovery and signature verification. ## Signature verification and OPA policies Native signature verification and [external image policies](/guides/external-image-policies) solve different problems: | Requirement | Use | | ------------------------------------------------------------------------ | --------------------------- | | Prove that a trusted identity or key signed the exact image digest | Native `verification` block | | Require an SBOM, provenance predicate, approved registry, or allowed tag | OPA external image policy | | Inspect fields inside an attestation | OPA external image policy | OPA policies evaluate image metadata. They do not perform Cosign's certificate-chain, transparency-log, payload, or cryptographic signature checks. Use the native `verification` block whenever trust in the signer is required, then add OPA only for metadata or attestation rules that provide additional value. # Track image patches with update policies Source: https://docs.nuon.co/guides/image-update-policies Use a semver constraint so each app sync picks up the latest matching patch tag without editing your app config. A container image component normally pins to a literal tag (`v1.10.0`). When upstream ships `v1.10.1`, you have to edit your app config, change the tag, and resync. For images you want to stay current on within a version range (patches inside a minor, minors inside a major), you can set `update_policy` instead. When `update_policy` is set, every time the component is rebuilt the runner lists tags from the source registry, filters them to the semver tags that satisfy your constraint, and selects the highest matching tag. Tags that aren't valid semver (`latest`, `stable`, branch names) are skipped. Update policies are evaluated **at build time**. There is no background scheduler that polls upstream. You pick up new tags the next time a build runs for the component, which today means running `nuon apps sync` (or anything else that triggers a build for that component). ## Scenario You have an app with two components: * `img_whoami` — a `container_image` component pulling from Docker Hub. * `whoami` — a `helm_chart` component that consumes `img_whoami` in its pod spec. You want `img_whoami` to stay on the latest `1.10.x` patch automatically. Major and minor bumps still go through a deliberate config change, but patches should be picked up the next time you sync. ## Step 1: Configure the image component with `update_policy` Drop the literal `tag` (or leave it as a starting hint) and add `update_policy`: ```toml components/img_whoami.toml theme={null} # container-image name = "img_whoami" type = "container_image" [public] image_url = "containous/whoami" update_policy = "~1.10.0" ``` `~1.10.0` means "any `1.10.x`": the runner will pick the highest `1.10.*` tag and refuse to roll to `1.11.0` or `2.0.0`. See the [container image config reference](/config-ref/container-image) for every supported constraint shape (tilde, caret, ranges, OR, exact match). ## Step 2: Wire the helm component to consume the resolved image In your helm component, reference the image outputs by digest. Using the digest-pinned `repository` form means each install is locked to the exact bytes that were resolved at build time: ```toml components/whoami.toml theme={null} # helm-chart name = "whoami" type = "helm_chart" chart_name = "whoami" dependencies = ["img_whoami"] [values] # digest-pinned ref, e.g. "containous/whoami@sha256:abc..." image_repository = "{{ .nuon.components.img_whoami.outputs.image.repository }}" # human-friendly tag for labels only; do NOT use in the image field image_display_tag = "{{ .nuon.components.img_whoami.outputs.image.display_tag }}" ``` ## Step 3: Sync and deploy ```bash theme={null} nuon apps sync ``` The runner builds `img_whoami`. Assume the highest tag in `containous/whoami` matching `~1.10.0` is `v1.10.0`: ``` update_policy "~1.10.0" selected tag "v1.10.0" from 47 source tags resolving image source containous/whoami:v1.10.0 copying image from containous/whoami:v1.10.0 to ``` The resulting build records: | Field | Value | | --------------- | --------------------------- | | `source_ref` | `containous/whoami:~1.10.0` | | `resolved_tag` | `v1.10.0` | | `source_digest` | `sha256:abc…` | Deploy installs as usual. Pods come up running `v1.10.0`. ## Step 4: Upstream ships v1.10.1 — pick it up on the next sync A few days later upstream releases `v1.10.1`. You don't touch your app config. Next time you run: ```bash theme={null} nuon apps sync ``` A new build for `img_whoami` runs: ``` update_policy "~1.10.0" selected tag "v1.10.1" from 48 source tags resolving image source containous/whoami:v1.10.1 copying image from containous/whoami:v1.10.1 to ``` The new build is recorded with `resolved_tag: v1.10.1` and a fresh `source_digest`. Because `whoami` depends on `img_whoami`, the deploy flow detects that the install's currently-deployed `img_whoami` build no longer matches the latest active build, and prepends an image sync before redeploying `whoami`. The pod rolls to `v1.10.1`. ## Step 5: Upstream ships v2.0.0 — your installs stay put When `containous/whoami:v2.0.0` lands, the next sync sees it but rejects it: it falls outside `~1.10.0`. The runner stays on the highest matching `1.10.x` tag. Major bumps still require you to update the constraint deliberately — for example `update_policy = "~1.11.0"` or `update_policy = "^1.0.0"` if you want to track any `1.x`. ## When nothing changed: no-op builds If you sync and upstream hasn't moved since the last build, the runner resolves the source ref, sees the manifest digest matches the previous build's recorded digest, and skips the copy entirely. The new build row is marked **no-op** and no new install deploy is queued. You'll see this on the build header in the dashboard and on the build timeline. This means rerunning `nuon apps sync` is always safe — it never re-pushes bytes the install registry already has. ## Reference: common policy shapes | Constraint | Meaning | | ---------------- | ---------------------------------- | | `~1.10.0` | `>=1.10.0, <1.11.0` (patches only) | | `^1.10.0` | `>=1.10.0, <2.0.0` (minor + patch) | | `>=1.0.0,<2.0.0` | comma-separated comparators (AND) | | `1.x` / `1.2.x` | wildcard ranges | | `1.0.0 - 2.0.0` | inclusive hyphen range | | `^1.0 \|\| ^2.0` | OR | | `=1.25.5` | exact match | Full reference: [container-image config](/config-ref/container-image). ## When `update_policy` is **not** the right tool * **Pinning a specific build for compliance / reproducibility.** Use a literal `tag` (or pin by digest) so nothing changes between syncs. * **Polling upstream continuously.** `update_policy` only fires when a build runs. There is no background poller. If you need scheduled image refreshes today, wire `nuon apps sync` into your CI on a cron. * **Tracking non-semver tags.** Tags like `latest`, `stable`, or `release-2024` are silently ignored — the highest semver match wins. # Syncing Install Configs from Git Source: https://docs.nuon.co/guides/install-config-syncing Connect a Git repo of install config files to your app so pushes keep every install's config in sync Point your app at a Git repository that holds one config file per install, and Nuon keeps those installs in sync with the repo. When you push a change, every install's config is updated to match; when you add a file for an install that doesn't exist yet, Nuon proposes creating it and waits for your approval. This is the app-wide counterpart to [creating installs from config files](/guides/install-configs) by hand — the same file format, applied continuously from a repo instead of one command at a time. Install config syncing is behind the `app-install-syncing` org feature flag and is off by default. Contact Nuon support to enable it for your org. ## Prerequisites * A [GitHub connection](/guides/vcs) (if using `connected_repo`) * Familiarity with [install config files](/guides/install-configs) ## Configuration There are two ways to configure install config syncing: in your app config or as a standalone file. Both accept the same properties — see the [Installs Config Reference](/config-ref/installs-config) for the full schema. ### In app config (`[installs_config]`) Add an `[installs_config]` section to your app config: ```toml nuon.toml theme={null} [installs_config.connected_repo] repo = "org/repo" branch = "main" directory = "installs" ``` Or with a public repo: ```toml nuon.toml theme={null} [installs_config.public_repo] repo = "https://github.com/org/repo.git" branch = "main" directory = "installs" ``` ### Standalone `installs.toml` Create an `installs.toml` file in your app config directory: ```toml installs.toml theme={null} [connected_repo] repo = "org/repo" branch = "main" directory = "installs" ``` `connected_repo` and `public_repo` are mutually exclusive — use one or the other. Use `connected_repo` when your install repo is private, or when you don't want the repo location to be public. You can also configure the repo from the dashboard, on the app's **Install configs** tab, without changing your app config. ## Install Config Files Each file in the configured `directory` defines one install. The `name` field is what ties a file to an install — Nuon matches on it to decide whether to update an existing install or propose a new one, so renaming a file is harmless but renaming the `name` is not. ```toml installs/staging.toml theme={null} name = "staging" approval_option = "approve-all" [labels] env = "staging" region = "us-east-1" [aws_account] region = "us-east-1" [[inputs]] instance_type = "t3a.small" debug_mode = "false" [[inputs]] domain = "staging.example.com" ``` ```toml installs/production.toml theme={null} name = "production" approval_option = "prompt" [labels] env = "production" region = "us-east-1" [aws_account] region = "us-east-1" [[inputs]] instance_type = "t3a.large" debug_mode = "false" [[inputs]] domain = "prod.example.com" ``` See the [Install Config Reference](/config-ref/install) for the full schema. To move an install you created by hand under config management, export it: ```sh theme={null} nuon installs generate-config -i > installs/.toml ``` ## What Happens During a Sync Each sync runs as a workflow with three steps, visible on the app's **Install configs** tab: 1. **Fetch commit** — resolves the commit to sync and clones the repo at that revision. 2. **Parse install configs** — reads every config file in the directory and compares it against the app's existing installs. If any file names an install that doesn't exist, the step pauses for approval and lists the installs it proposes creating. 3. **Sync installs** — dispatches a config sync to each matching install, which applies the new config and starts any resulting workflow. Approving the second step creates the missing installs. Denying it skips creation and ends the sync — existing installs are left untouched, and the next sync will propose them again. A sync never deletes installs. Removing a config file from the repo stops that install from being updated; it does not tear it down. ## Sync Triggers There are two ways a sync starts: * **Git push** — a push to the configured branch of the connected repo triggers a sync automatically. * **Manual** — the **Sync now** button on the app's **Install configs** tab, or a `POST` to `/v1/apps/{app_id}/install-syncs`. `nuon apps sync` does not trigger an install config sync. It registers the `installs_config` block from your app config so pushes and manual syncs can use it — the first sync still comes from a push or from **Sync now**. ## Syncing Config Files Directly `nuon installs sync` is a separate mechanism: it pushes install config files from your working copy straight to the API, without going through a connected repo. It's useful for iterating on a config before committing it, or for managing installs from CI without connecting a repo at all. Sync a single file: ```sh theme={null} nuon installs sync --file installs/staging.toml --app-id ``` Sync a whole directory: ```sh theme={null} nuon installs sync --app-id --file installs/ ``` Preview the changes without applying them: ```sh theme={null} nuon installs sync --app-id --file installs/ --dry-run ``` Add `--yes` to approve the resulting diffs and workflows automatically, and `--wait` to block until those workflows finish — both are useful in CI. To stop an individual install from being updated by either mechanism: ```sh theme={null} nuon installs toggle-sync -i --disable ``` ## Viewing Sync History The app's **Install configs** tab lists recent syncs with their status, the commit each one ran against, and the per-install config syncs it dispatched. Opening a sync shows its step timeline, the proposed installs awaiting approval, and the approve and deny actions. ## See Also * [Creating Installs](/guides/install-configs) — manual install config management * [Install Config Reference](/config-ref/install) — JSON Schema reference * [Installs Config Reference](/config-ref/installs-config) — repo connection schema * [Configuration Files](/configuration-files) — full TOML reference * [App Branches](/concepts/app-branches) — coordinated multi-install deployments # Creating Installs Source: https://docs.nuon.co/guides/install-configs Configuring the installs for your customers When you ship an app to customers through Nuon, you create one install per customer. This page covers configuring those installs, labeling them so they're easy to identify, and monitoring their workflows once they're running. ## Install Configs If you create installs at scale — for example, one per customer in an automated onboarding flow — you can generate install configs programmatically instead of authoring them by hand. Install configs are defined by a [JSON Schema](/config-ref/install) that lists every field, its type, and its validation rules. Feed the schema to a code generator, validator, or template engine and produce install configs from your own data. Apply a generated config with the Nuon CLI: ```bash theme={null} nuon installs sync --file .toml --app-id --confirm ``` For the full property list, see the [Install configuration reference](/config-ref/install). ## Labeling installs Installs support arbitrary key/value `labels` set via the `labels` field in the install config. Labels are useful for tagging installs with metadata like environment, region, or version, and they're surfaced in the dashboard alongside the install. ```toml install.toml theme={null} name = "customer-acme" [labels] env = "production" region = "us-east-1" tier = "enterprise" ``` See [Label badges](/guides/using-readmes#label-badge) for how labels render in install READMEs. ### Dynamic labels Label values can use [Nuon templating](/guides/using-variables) to render from install state: ```toml install.toml theme={null} name = "customer-acme" [labels] env = "production" region = "{{ .nuon.cloud_account.aws.region }}" tier = "{{ .nuon.install.inputs.tier }}" ``` Dynamic values are rendered when the install install state changes — deploys, input updates, and config updates all trigger a refresh. The rendered value is displayed in the dashboard and what [webhook and Slack label selectors](/guides/webhooks) match against. If a template references state that doesn't exist yet (for example, a component output before its first deploy), the label is skipped rather than failing the sync; it appears once the referenced state is available. ### Default labels Labels defined in the app's [`default_labels`](/guides/managing-apps#default-labels) apply to every install and are owned by the app config — an install config cannot override or remove those keys, and a sync that tries to set a different value for one fails with an error. ## Toggling components If your app has [toggleable components](/guides/toggleable-components), the install config decides which ones are enabled. Set the state per component in a `[component_toggles]` section, keyed by component name: ```toml install.toml theme={null} name = "customer-acme" [component_toggles] observability = true certificate = false ``` See the [Toggleable Components guide](/guides/toggleable-components) for how toggles reconcile on sync, dependency validation, and imperative dashboard/CLI toggles. ## Monitoring in-progress workflows Once an install kicks off a deploy — or any other workflow — you can monitor progress from the dashboard, via outbound webhooks, or via Slack notifications. Connect your workspace to get real-time deploy notifications — see the [Slack integration guide](/guides/slack). For programmatic integrations, [webhooks](/guides/webhooks) deliver the same workflow lifecycle events as CloudEvents over HTTP. ## Syncing Install Configs from Git Instead of applying these files one command at a time, you can point your app at a Git repository that holds them. Pushes to that repo then keep every install in sync, and new files propose new installs for approval. Configure it with an `installs.toml` file or an `[installs_config]` section in your app config. See the [Syncing Install Configs from Git](/guides/install-config-syncing) guide for setup and usage. # Kubernetes Manifest Components Source: https://docs.nuon.co/guides/kubernetes-manifest-components Kubernetes manifest components allow you to deploy raw Kubernetes resources directly into your customer's cloud account. Kubernetes manifest components allow you to deploy raw Kubernetes resources using YAML manifests. ## Configuring a Kubernetes Manifest component To configure a Kubernetes manifest component, specify a namespace and the manifest files to deploy. ```toml components/kubernetes_manifest.toml theme={null} # kubernetes-manifest name = "kubernetes_manifest" type = "kubernetes_manifest" namespace = "nuon-sample" manifest = """ apiVersion: v1 kind: ConfigMap metadata: name: demo data: sample_data: "3" """ ``` ## Multiple Resources in a Single Manifest Kubernetes manifest components support multiple resources separated by the standard YAML document separator `---`: ```yaml components/deployment.yaml theme={null} apiVersion: apps/v1 kind: Deployment metadata: name: my-app namespace: default spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: app image: "alpine:latest" ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: my-app-service namespace: default spec: selector: app: my-app ports: - port: 80 targetPort: 8080 type: ClusterIP ``` ## Using Nuon Context Variables Kubernetes manifest components support Nuon's templating system, allowing you to access variables and outputs from other components: ```yaml components/configmap.yaml theme={null} --- apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: default data: # access a synced image image_repository: "{{.nuon.components.image.image.repository.uri}}" image_tag: "{{.nuon.components.image.image.tag}}" # access outputs from a terraform component database_url: "{{.nuon.components.terraform.outputs.database_url}}" # access outputs from the sandbox aws_region: "{{.nuon.install.sandbox.outputs.account.aws_region}}" vpc_id: "{{.nuon.install.sandbox.outputs.vpc.id}}" # access information about the install domain public_domain: "{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" internal_domain: "{{.nuon.sandbox.outputs.nuon_dns.internal_domain.name}}" ``` ## Common Use Cases ### Deploying with Custom Images ```yaml components/app-deployment.yaml theme={null} --- apiVersion: apps/v1 kind: Deployment metadata: name: "{{.nuon.app.name}}" namespace: default spec: replicas: 2 selector: matchLabels: app: "{{.nuon.app.name}}" template: metadata: labels: app: "{{.nuon.app.name}}" spec: containers: - name: app image: "{{.nuon.components.app_image.image.repository.uri}}:{{.nuon.components.app_image.image.tag}}" env: - name: DATABASE_URL value: "{{.nuon.components.infra.outputs.database_url}}" - name: AWS_REGION value: "{{.nuon.install.sandbox.outputs.account.aws_region}}" ports: - containerPort: 8080 ``` ### Creating Ingress Resources ```yaml components/ingress.yaml theme={null} --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: default annotations: external-dns.alpha.kubernetes.io/hostname: "app.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: "public-issuer" spec: tls: - hosts: - "app.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" secretName: app-tls rules: - host: "app.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" http: paths: - path: / pathType: Prefix backend: service: name: app-service port: number: 80 ``` ### Using AWS Load Balancer Controller ```yaml components/nlb-service.yaml theme={null} --- apiVersion: v1 kind: Service metadata: name: app-nlb namespace: default annotations: service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "{{.nuon.components.infra.outputs.public_domain_certificate_arn}}" service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https external-dns.alpha.kubernetes.io/hostname: "app.{{.nuon.sandbox.outputs.nuon_dns.public_domain.name}}" spec: type: LoadBalancer loadBalancerClass: service.k8s.aws/nlb selector: app: my-app ports: - name: https port: 443 targetPort: 8080 ``` ## Kustomize In addition to inline manifests, Kubernetes manifest components can be sourced from a [Kustomize](https://kustomize.io) overlay in a Git repo. Use this when you want to assemble your manifests from a base + overlays, layer in patches, or share manifests across multiple components. ### Pointing a component at a Kustomize overlay A Kustomize-backed component references the repo holding the overlay (either `public_repo` for a public GitHub repo, or `connected_repo` for a Nuon-connected private repo) and a `kustomize.path` relative to the source root. ```toml components/kustomize-demo.toml theme={null} name = "kustomize-demo" type = "kubernetes_manifest" namespace = "kustomize-demo" [public_repo] repo = "nuonco/example-app-configs" directory = "gke-simple/src/components/kustomize-demo" branch = "main" [kustomize] path = "." ``` The directory at `directory` must contain a standard `kustomization.yaml`. Nuon runs `kustomize build` on it during deploy. ```yaml gke-simple/src/components/kustomize-demo/kustomization.yaml theme={null} apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - namespace.yaml - configmap.yaml - deployment.yaml ``` ### Template variables in Kustomize trees Kustomize-backed components support the same `{{.nuon.*}}` template variables as inline manifests. Placeholders are rendered on the runner, against the live install state, **after** `kustomize build` produces the final manifest. This means you can reference install IDs, app metadata, install stack outputs, and other component outputs directly from any YAML file inside your Kustomize overlay. ```yaml gke-simple/src/components/kustomize-demo/configmap.yaml theme={null} apiVersion: v1 kind: ConfigMap metadata: name: install-info namespace: kustomize-demo annotations: nuon.co/rendered-install-id: "{{.nuon.install.id}}" data: install_id: "{{.nuon.install.id}}" install_name: "{{.nuon.install.name}}" app_id: "{{.nuon.app.id}}" project_id: "{{.nuon.install_stack.outputs.project_id}}" ``` After a deploy, the rendered ConfigMap on the cluster will have the placeholders replaced with the install's actual values — for example, `install_id: inl...`, `project_id: nuon-gcp-support`. Kustomize-backed manifests are templated on the runner, not in the planner. This keeps the Temporal workflow payload small even for large Kustomize trees. Inline manifests, by contrast, are still rendered in the planner. ## Nuon Managed Sandbox Components The `aws-eks-sandbox` [managed Sandbox](/concepts/sandboxes#nuon-managed-sandboxes) ships with standard components that your Kubernetes manifests can leverage: * [AWS ALB Ingress Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller) * [External DNS](https://github.com/kubernetes-sigs/external-dns) * [Cert Manager](https://cert-manager.io/) * [Nginx Ingress](https://docs.nginx.com/nginx-ingress-controller/) * [EBS CSI](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) The Sandboxes are [open source](/concepts/sandboxes#nuon-managed-sandboxes) and can be [customized](/concepts/sandboxes#create-a-custom-sandbox), if these components do not work for your application. ## Best Practices ### Namespace Management Always specify namespaces explicitly in your manifests. If not provided, it defaults to the namespace specified in the config. If both the component configuration and manifest are missing namespace specifications, the component deployment will fail: ```yaml components/namespace.yaml theme={null} --- apiVersion: v1 kind: Namespace metadata: name: "{{.nuon.app.name}}" labels: app: "{{.nuon.app.name}}" --- apiVersion: apps/v1 kind: Deployment metadata: name: app namespace: "{{.nuon.app.name}}" spec: # deployment spec... ```
Kubernetes manifest components are processed in the order they appear in your repository. If you have dependencies between resources, ensure they are ordered appropriately or use proper Kubernetes resource dependencies. # Managing Apps Source: https://docs.nuon.co/guides/managing-apps Create, configure, and manage apps using the CLI and dashboard. The [CLI](/cli) is the primary interface for managing apps. The dashboard also provides limited app management. To see all available commands, run `nuon apps --help`. ## Creating an App Create a directory for your app config files, then create the app: ```sh theme={null} mkdir your-app cd your-app nuon apps create --name=your-app ``` The app name should match the directory name. ## Pinning an App Pin an app to set its ID as the default for all app commands, avoiding the need to pass `-a` each time: ```sh theme={null} nuon apps select nuon apps current # show the currently pinned app ``` Examples below assume you have run `nuon apps select` first. ## Common Commands ```sh theme={null} nuon apps list # list all apps nuon apps get # get info about the pinned app nuon apps rename --name=new-name # rename an app ``` ## Syncing Configuration Sync uploads your TOML config files to the Nuon control plane and triggers builds for any changed components: ```sh theme={null} nuon apps sync ``` If component TOML files or related files (e.g., Helm `values.yaml`) have changed, a new build is triggered during sync. If component source files have changed, push those changes to your git repo first, then build the component manually using the dashboard or CLI. ## Viewing Configuration ```sh theme={null} nuon apps configs # list all config versions nuon apps runner-config # print runner config nuon apps sandbox-config # print sandbox config nuon apps input-config # print input config ``` The output of `runner-config`, `sandbox-config`, and `input-config` is JSON. Pipe through `jq` or `python -m json.tool` for readability. ## Metadata Add metadata to your app via a `metadata.toml` file in the app root directory. This metadata is used in installers and the dashboard: ```toml metadata.toml theme={null} display_name = "Your App" description = "A short description of your app." readme = "./README.md" ``` The `readme` field points to a Markdown file rendered in the dashboard for each install. See the [Using READMEs](/guides/using-readmes) guide for details. ### Label colors Give labels custom colors in the dashboard with a `label_colors` table in `metadata.toml`. Keys are label key names; values are hex color codes: ```toml metadata.toml theme={null} [label_colors] env = "#FF5733" region = "#33FF57" customer = "#3357FF" ``` The colors apply wherever those label keys appear in the dashboard — on apps, installs, and components. You can also set label colors directly in the dashboard; either source can be updated later, and a sync will reconcile the values from your config. ### Default labels Apply labels to every install of an app with a `default_labels` table in `metadata.toml`: ```toml metadata.toml theme={null} [default_labels] service_tier = "{{ .nuon.inputs.service_tier }}" ``` Values can use [Nuon templating](/guides/using-variables) to render per install from install state, and re-render automatically as that state changes. Default labels are owned by the app config. They locked on each install and cannot be updated manually or via install config. To change or remove a default label, you must edit `default_labels` in the app config. The change will propagate to each install when the new version of that app is released. # Managing Components Source: https://docs.nuon.co/guides/managing-components List, build, and manage components using the CLI. The [CLI](/cli) provides commands to manage components and builds. To see all available commands, run `nuon components --help`. Select an app using `nuon apps select` before using component commands, or pass the `-a` flag. ## Common Commands ```sh theme={null} nuon components list # list components nuon components get -c # get a component nuon components latest-config -c # print latest config ``` To delete a component, remove its TOML file from the app directory and run `nuon apps sync` rather than using the CLI. ## Builds All components must be built before creating or updating an install. `nuon apps sync` triggers builds automatically when component TOML files or related files (e.g., Helm `values.yaml`) change. If component source files change in git, push those changes first, then build manually: ```sh theme={null} nuon builds create -c # create a build nuon builds list -c # list builds nuon builds get -b -c # get build details nuon builds logs -b -c # show build logs ``` Omit the `-c` flag to list builds for all components in the selected app. # Nuon Cloud Source: https://docs.nuon.co/guides/nuon-cloud The fastest way to get started with Nuon Nuon Cloud is the fully managed, multi-tenant version of Nuon hosted by Nuon. There is no infrastructure to provision or operate. Create an account, configure your app, and start deploying software to your customers. To get started, [sign up at app.nuon.co](https://app.nuon.co). ## What's Included * Fully managed control plane: no EKS, RDS, Temporal, or ClickHouse to operate * Automatic upgrades as Nuon releases new versions * Shared infrastructure with logical tenant isolation * Full access to the Nuon dashboard, CLI, and API ## White-Label If you need to white-label the Nuon runner API so your customers only see your domain (not Nuon's) in their cloud infrastructure, that's available as an add-on. All runner traffic, including heartbeats, job polling, logs, and install callbacks, flows through a proxy you control at your own domain. [Contact sales](https://nuon.co/contact-sales) to learn more. ## Next Steps Once your account is created, head to [Getting Started](/get-started/quickstart) to connect your first app. # Nuon Stack Terraform Provider Source: https://docs.nuon.co/guides/nuon-stack-terraform-provider Read install configuration and report Terraform install-stack results. The [`nuonco/stack`](https://registry.terraform.io/providers/nuonco/stack/latest) provider gives an install-stack module two authenticated operations: * `stack_config` reads the rendered configuration for an install. * `stack_phone_home` reports create, update, and delete results to Nuon. Published Nuon AWS, Azure, and GCP modules already include this wiring. Use the provider directly when wrapping or forking one of those modules. ## Configure Authentication ```hcl theme={null} terraform { required_providers { stack = { source = "nuonco/stack" version = ">= 0.7.0" } } } provider "stack" {} ``` Set credentials outside Terraform so they are not written into the plan: ```bash theme={null} export NUON_API_TOKEN="" export NUON_API_URL="https://runner.nuon.co" ``` `NUON_API_URL` is optional for the hosted control plane. The provider also supports ambient OIDC with `NUON_ORG_ID` and either `NUON_OIDC_TOKEN`, `NUON_OIDC_TOKEN_FILE`, or a GitHub Actions ID token. The install ID identifies the configuration to read; it does not authorize access. Never replace provider authentication with the install ID. ## Read Install Configuration ```hcl theme={null} variable "install_id" { type = string } data "stack_config" "this" { install_id = var.install_id } ``` The data source returns common values such as: * `runner_id`, `runner_api_url`, and `phone_home_url` * `install_inputs`, `required_input_names`, and `secrets` * `aws`, `azure`, or `gcp` cloud-specific runner and permission configuration * `custom_stacks` and `custom_stacks_template_url` Secrets and cloud runner tokens are sensitive. Use encrypted remote state with tightly scoped access. ## Report Results `stack_phone_home` maps Terraform resource lifecycle events to Nuon's `Create`, `Update`, and `Delete` request types. ```hcl theme={null} resource "stack_phone_home" "this" { install_id = data.stack_config.this.install_id phone_home_url = data.stack_config.this.phone_home_url phone_home_type = "azure" payload = jsonencode({ resource_group = azurerm_resource_group.this.name custom_nested_stacks = local.custom_stack_outputs }) } ``` Use `aws`, `azure`, or `gcp` for `phone_home_type`. Keep cloud-specific payload keys compatible with the published module because Nuon exposes them to app templates as `.nuon.install_stack.outputs`. The `inputs` argument may update only customer-source app inputs. Vendor and computed inputs are rendered by Nuon and must remain in the payload rather than being submitted as customer overrides. ## Custom Stack Fields Each `custom_stacks` entry includes its stable `name`, rendered `parameters`, customer `input_parameters`, and output mapping. AWS and Azure additionally use `custom_stacks_template_url` for the generated custom-only template. GCP uses the curated module name derived from its module path. See [Custom Install Stacks](/guides/custom-nested-stacks) for app configuration and output consumption. # Operation Roles Source: https://docs.nuon.co/guides/operation-roles Assign different roles to different operations across sandbox, components, and actions. Operation roles let you assign a specific role to each individual operation the Nuon runner performs, such as provisioning your sandbox, deploying a component, or running an action, instead of relying on a single all-purpose maintenance role. The result is granular, least-privilege access control with no changes required to your customers' cloud accounts. ## Why Use Operation Roles? Your deploy workflow might need `lambda:CreateFunction` and `lambda:UpdateFunctionCode`. Your teardown workflow only needs `lambda:DeleteFunction`. With a shared maintenance role both permissions are always present; with operation roles each workflow gets exactly what it needs. Some concrete use cases: * **Separate deploy vs teardown:** prevent accidental deletion by ensuring your deploy role cannot delete resources * **Tighter sandbox permissions:** provision with a broad Terraform role and deprovision with a narrow destroy-only role * **Emergency break-glass:** run a privileged action role for incident response without elevating all action runs * **Audit-friendly access:** separate CloudTrail events per role make it easy to see which operation touched which resource ## Role Selection Precedence When a run starts, the runner selects a role by walking this chain from highest to lowest priority: | Priority | Source | Scope | | ----------- | --------------------------------------------------------------------------------------------- | ------------- | | 1 (highest) | Runtime override (`--role` flag / dashboard selection) | Any operation | | 2 | Break-glass role (`break_glass_role` field on action config, deprecated) | Actions only | | 3 | Entity role (inline `operation_roles` block or `role` field) | Single entity | | 4 | Matrix rule (`operation_roles.toml` principal + operation) | App-wide | | 5 (lowest) | Default role (`permissions.toml` `provision_role`, `deprovision_role`, or `maintenance_role`) | App-wide | The first match wins. If nothing matches the run fails. ## Defining Custom Roles Any role used in an operation role assignment must be declared in `permissions.toml` with `type = "custom"` so it is provisioned in the customer's CloudFormation stack: ```toml permissions.toml theme={null} [[roles]] type = "custom" name = "{{.nuon.install.id}}-deploy-role" description = "Role for Lambda deployments" [[roles.policies]] type = "inline" contents = """ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["lambda:CreateFunction", "lambda:UpdateFunctionCode"], "Resource": "*" } ] } """ ``` ## Configuring Entity Roles Entity roles are defined inline on the individual config file. They take precedence over matrix rules. ### Sandbox (`sandbox.toml`) Use an `[[operation_roles]]` array to map sandbox operations to roles. ```toml sandbox.toml theme={null} [[operation_roles]] operation = "provision" role = "{{.nuon.install.id}}-sandbox-deploy-role" [[operation_roles]] operation = "reprovision" role = "{{.nuon.install.id}}-sandbox-update-role" [[operation_roles]] operation = "deprovision" role = "{{.nuon.install.id}}-sandbox-teardown-role" ``` Valid operations for sandbox: `provision`, `reprovision`, and `deprovision`. ### Components (`components/.toml`) Use the same `[[operation_roles]]` array on any component config file. ```toml components/my_lambda.toml theme={null} [[operation_roles]] operation = "deploy" role = "{{.nuon.install.id}}-deploy-role" [[operation_roles]] operation = "teardown" role = "{{.nuon.install.id}}-update-role" ``` Valid operations for components: `deploy` and `teardown`. ### Actions (`actions/.toml`) Actions use a single top-level `role` field (not an array). `break_glass_role` is deprecated and should no longer be used. ```toml actions/db_migration.toml theme={null} name = "db_migration" timeout = "10m" role = "{{.nuon.install.id}}-migration-role" [[triggers]] type = "manual" [[steps]] name = "run-migration" command = "./migrate.sh" ``` ## Configuring Matrix Rules (`operation_roles.toml`) The `operation_roles.toml` file defines app-level rules that apply across all installs. Rules are matched by principal (which entity is performing the operation) and operation name. Create a file named `operation_roles.toml` at the root of your app config: ```toml operation_roles.toml theme={null} type = "matrix" [[rules]] principal = "nuon::component:my_lambda" operation = "deploy" role = "{{.nuon.install.id}}-lambda-deploy-role" [[rules]] principal = "nuon::component:my_lambda" operation = "teardown" role = "{{.nuon.install.id}}-lambda-teardown-role" [[rules]] principal = "nuon::sandbox" operation = "provision" role = "{{.nuon.install.id}}-provision-role" [[rules]] principal = "nuon::sandbox" operation = "deprovision" role = "{{.nuon.install.id}}-destroy-role" [[rules]] principal = "nuon::action:db_migration" operation = "trigger" role = "{{.nuon.install.id}}-migration-role" ``` ### Principal Format | Entity | Principal | | ------------------ | ------------------------ | | Specific component | `nuon::component:` | | All components | `nuon::component:*` | | Sandbox | `nuon::sandbox` | | Specific action | `nuon::action:` | | All actions | `nuon::action:*` | The wildcard `*` matches any entity of that type, making it easy to set a default role for all components or all actions without listing each one. ## Role Name Templating Role names support the same Go template syntax used elsewhere in Nuon config: ```toml theme={null} role = "{{.nuon.install.id}}-deploy-role" ``` Common template variables: | Variable | Description | | ---------------------------------------------- | -------------------------------------------------- | | `{{.nuon.install.id}}` | Install ID, ensures each install gets its own role | | `{{.nuon.install.sandbox.outputs..name}}` | Sandbox stack output values | The rendered role name must match a role defined in `permissions.toml` (either a standard role or a `custom_roles` entry). ## Syncing with the CLI After adding or changing operation role config, sync your app to push the updated config to Nuon: ```sh theme={null} nuon apps sync ``` This syncs all the configs with the control plane, including `operation_roles.toml` and any inline `operation_roles` blocks. ## Runtime Role Override You can override the role for a specific run without changing any config file. ### CLI Pass `--role ` when triggering a sandbox or component operation: ```sh theme={null} # Override role for a manual action run nuon actions create-run -w -i --role my-custom-role ``` ### Dashboard On the install page, before triggering a run, use the role selector in the run options panel to choose an alternative role from those available in the install's CloudFormation stack. ## Viewing Configured Roles In the Nuon dashboard, navigate to your install and open the **Components** or **Actions** tab. Each entity shows the currently configured role. ## Next Steps * [Operation Roles Concepts](/concepts/operation-roles): understand the mental model and precedence chain * [Permissions Config Reference](/config-ref/permissions): defining `provision_role`, `maintenance_role`, and `custom_roles` # Programmable READMEs Source: https://docs.nuon.co/guides/programmable-readmes READMEs that render each install's live values and embed real-time dashboard components.
A per-install README rendered in the dashboard, with runnable runbooks
For static formatting, see [Using READMEs](/guides/using-readmes). ## Variables READMEs support variables, rendered server-side per install (the same as component and action fields). Use them to surface install info and keep the README in sync with the app: ```toml metadata.toml theme={null} # metadata name = "My App" readme = """ # {{.nuon.app.name}} This is the README for {{.nuon.app.name}} in install {{.nuon.install.name}}. ## Sandbox This app uses the {{.nuon.sandbox.type}} sandbox. ## Components This app consists of the following components. - api - deployment ``` Variables use [Go templating](https://pkg.go.dev/text/template), so you get conditionals, ranges, and functions. Here, live component statuses: ```toml metadata.toml theme={null} # metadata name = "My App" readme = """ # {{.nuon.app.name}} This is the README for {{.nuon.app.name}} in install {{.nuon.install.name}}. ## Sandbox This app uses the {{.nuon.sandbox.type}} sandbox. ## Components {{ if .nuon.components }} This install is running the following app components. | Name | Status | |------|------| {{- range $name, $component := .nuon.components }} | `{{ $name }}` | `{{ $component.status }}` | {{- end }} {{ else }} __No app components are active in this install. You may need to run "Deploy Components" for this install.__ {{ end }} ``` The dashboard polls the control plane, so component statuses update in near-real-time. `.nuon.components` is a map keyed by component name. Iterate it directly with `range`. Each value carries that component's fields (`status`, `outputs`, etc.). Unlike `.nuon.sandbox` or `.nuon.install_stack`, it has no top-level `populated` flag, so use `{{ if .nuon.components }}` to check whether any components exist. ## Live data components These `` tags render live install data from the dashboard. They require install context. When viewed at the app level (before selecting an install), they render as inline code showing the raw tag. ### Config graph Shows the install's dependency graph of components and infrastructure. ```markdown README.md theme={null} ``` ### View state Renders a button that opens the install state viewer. ```markdown README.md theme={null} ``` ### Runner card Shows the status card for the install's runner. ```markdown README.md theme={null} ``` ### Sandbox card Shows the status card for the install's sandbox. ```markdown README.md theme={null} ``` ### Component card Shows the status card for a specific component. Reference by `name` or `id`. ```markdown README.md theme={null} ``` Attributes: `name`, `id`. ### Stack card Shows the status card for the install's stack (grouped deploy history). ```markdown README.md theme={null} ``` ### Action card Shows the status card for a specific action. Reference by `name` or `id`. ```markdown README.md theme={null} ``` Attributes: `name`, `id`. ### Run runbook Renders a card with a button that opens the run runbook confirmation modal. Reference a [runbook](/guides/runbooks) by `name` or `id`. ```markdown README.md theme={null} ``` The card shows the runbook name (linked to its detail page), the number of steps, and a run button. Clicking the button opens a confirmation modal listing the steps before executing. Attributes: `name`, `id`. Live data components are most useful in the referenced `README.md` file format, where you can build rich install dashboards combining live status cards with documentation. ## A complete example For a runnable example, see the [`programmable-runbook-and-readme`](https://github.com/nuonco/example-app-configs/tree/main/programmable-runbook-and-readme) app config in `example-app-configs`. It has a per-install app README plus runbook READMEs that render live values and embed runnable runbooks. # Provision Stacks with CloudFormation Source: https://docs.nuon.co/guides/provision-stacks-with-cloudformation Provision Nuon install stacks on AWS with CloudFormation. Nuon supports provisioning install stacks to AWS using [CloudFormation](https://aws.amazon.com/cloudformation/). A CloudFormation stack template is generated for every install, along with a quick-create link and CLI commands. Which one you use will depend on whether your customer wants to use the AWS web console or the AWS CLI. * **[Quick-create](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-create-stacks-quick-create-links.html)** — open the quick-create link and create the stack in the AWS web console. * **[AWS CLI](https://aws.amazon.com/cli/)** — run the generated `aws cloudformation create-stack` command. CloudFormation is AWS-only. ## How it works Provisioning with CloudFormation is a hand-off between you and your customer: 1. You create the install. The install workflow pauses at the **await install stack** step. 2. On that step, you copy the quick-create link or the CLI commands. 3. You send them to your customer. 4. Your customer launches the stack in their AWS account. 5. The stack boots up the runner. Once the runner connects, the await install stack step completes. ## Prerequisites Your customer will need the following in order to launch the stack. * Access to the target AWS account with permission to create CloudFormation stacks. * If using the CLI, the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and configured. ## Using the quick-create link Follow these steps to provision a stack using a quick-create link. Create an install as you usually would, and wait for the provision workflow to get to the **await install stack** step. On the **await install stack** step, copy the **Quick launch in AWS console** link and send it to your customer. The remaining steps are for your customer. Open the link while signed in to the target AWS account. It opens CloudFormation's quick-create flow with the template URL and stack name prefilled. Fill in the required inputs and secrets. Override any default values you want to change. The secret values will be written directly to AWS Secrets Manager. They will not be saved in plaintext anywhere. Check the boxes at the bottom of the page and click **Create stack**. This will provision the Nuon stack. Once the runner phones home, the await install stack step in Nuon will complete. ## Using the AWS CLI Follow these steps to provision a stack using the AWS CLI. On the **await install stack** step, copy the generated **Create stack** command and send it to your customer. The remaining steps are for your customer. Run the command using credentials for the target AWS account. Use the `--parameters` flag to set input and secret values. ```bash theme={null} aws cloudformation create-stack \ --stack-name \ --template-url \ --capabilities CAPABILITY_NAMED_IAM \ --region ``` This will provision the Nuon stack. Once the runner phones home, the await install stack step in Nuon will complete. # Provision Stacks with Spacelift Source: https://docs.nuon.co/guides/provision-stacks-with-spacelift Provision stacks using a Spacelift Blueprint or the Terraform provider. Spacelift support for install stacks is gated behind a feature flag. [Reach out to Nuon](https://nuon.co/demo-request) to enable it for your org. If a customer wants to manage their install stack using Spacelift, Nuon can generate code for them to use. There are two ways to install a Spacelift stack. Which one your customer uses depends on whether they want to use the Spacelift Web UI or Terraform. * **[Blueprint](https://docs.spacelift.io/concepts/blueprint)** — publish a Spacelift blueprint and use the web UI to provision the stack. * **[Terraform](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs)** — apply a generated `spacelift.tf` and use the Terraform provider to provision the stack. Both paths use the [`nuonco/install-stacks`](https://github.com/nuonco/install-stacks) modules over **raw Git**, so no Spacelift VCS integration or GitHub App install is required. ## How it works Provisioning with Spacelift is a hand-off between you and your customer: 1. You create the install. The install workflow pauses at the **await install stack** step. 2. On that step's **Spacelift** tab, you copy the blueprint YAML or download the Terraform files. 3. You send them to your customer. 4. Your customer provisions the stack in their Spacelift account, into their cloud account. 5. The stack boots up the runner. Once the runner connects, the await install stack step completes and the install proceeds — no further action needed on your side. ## Prerequisites Your customer will need the following in order to use Spacelift. * A [Spacelift](https://spacelift.io/) account with permission to create blueprints and stacks. * The [cloud integration](https://docs.spacelift.io/integrations/cloud-providers) set up for the cloud account they want to install your app in. ## Using a blueprint Follow these steps to provision a stack using a Spacelift Blueprint. Create an install as you usually would, and wait for the provision workflow to get to the **await install stack** step. On the **await install stack** step, open the **Spacelift** tab and copy or download the blueprint YAML. Send this to your customer. The remaining steps are for your customer. In Spacelift, go to **Blueprints → Create blueprint** and paste the YAML from your vendor as the template body. The template is configured to put the stack in the `root` space by default. Edit the `space:` line while it's a draft if the stack should live elsewhere. Click **Publish** to move the blueprint from draft to published. Once published, the blueprint cannot be edited. On the published blueprint, click **Create stack** and fill in the inputs and secrets. Click **Create** to create the stack. Open the new stack's **Settings → Integrations**. Attach the [cloud integration](https://docs.spacelift.io/integrations/cloud-providers) to grant Spacelift access to the cloud account. Trigger a run. This will provision the Nuon stack. Once the runner phones home, the await install stack step in Nuon will complete. ## Using terraform Follow these steps to provision a Nuon install stack using the Spacelift Terraform provider. Click **Download all (.zip)** on the **Spacelift** tab to get `spacelift.tf`, `inputs.auto.tfvars`, and `secrets.auto.tfvars` as a single zip (or download them individually), then send them to your customer. The remaining steps are for your customer. Unpack all three files into one directory. In `spacelift.tf`, set the `space_id` variable's `default` to the Spacelift space this stack should live in. By default, the config attaches Spacelift's GCP integration to the stack — a dedicated service account created during the apply. If you already manage your own GCP integration, set `attach_gcp_service_account`'s default to `false` in `spacelift.tf`. Fill in the inputs in the `inputs.auto.tfvars` file. Leave this file alongside the stack if you want to manage these values in version control. Alternatively, you can save the contents to a mounted, non-secret file in Spacelift. If saving this as a secret mounted file, fill in the secrets in `secrets.auto.tfvars`. Alternatively, store the secret values in a secret store and pass them to terraform using environment variables. The [`spacelift-io/spacelift`](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs) provider needs Spacelift API credentials. If the terraform is being run within Spacelift, then these will automatically be populated in the environment. Otherwise, [configure authentication for the Spacelift provider](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs#authentication). Initialize the terraform workspace and apply. ```bash theme={null} terraform init && terraform apply ``` Trigger the first run. This will provision the Nuon stack. Once the runner phones home, the await install stack step in Nuon completes. # Provision Stacks with Terraform Source: https://docs.nuon.co/guides/provision-stacks-with-terraform Provision the Nuon install stacks with Terraform. For AWS installs, the [Terraform module flow](/guides/provision-stacks-with-terraform-module) is the recommended path: your customer applies the published `nuonco/stack/aws` module with an API token, and no tfvars files change hands. This page describes the legacy flow based on generated tfvars files. Nuon supports provisioning install stacks using Terraform. We maintain a collection of [open-source terraform modules](https://github.com/nuonco/install-stacks) that can be used directly, or forked and customized. In this legacy flow, you download generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files from the dashboard and send them to your customer. ## How it works Provisioning with Terraform is a hand-off between you and your customer: 1. You create the install. The install workflow pauses at the **await install stack** step. 2. On that step, you copy or download the generated `inputs.auto.tfvars` and `secrets.auto.tfvars`. 3. You send them to your customer. 4. Your customer clones the install stack module and applies it in their cloud account. 5. The stack boots up the runner. Once the runner connects, the await install stack step completes. ## Prerequisites Your customer will need the following in order to apply the stack. * The [Terraform](https://developer.hashicorp.com/terraform/install) CLI installed, or set up in their chosen automation platform. * Credentials for the target cloud account, so Terraform can provision resources. * Outbound network access to the Terraform Registry (to install the `nuonco/stack` provider) and to the Nuon API (so the provider can fetch the stack configuration at apply time). ## Provisioning the stack Follow these steps to provision a stack using Terraform. Create an install as you usually would, and wait for the provision workflow to get to the **await install stack** step. On the **await install stack** step, copy or download `inputs.auto.tfvars` and `secrets.auto.tfvars`, then send them to your customer. The remaining steps are for your customer. Clone the repo and enter the directory for your cloud (`aws` or `gcp`): ```bash theme={null} git clone https://github.com/nuonco/install-stacks.git cd install-stacks/ ``` If you have not already, set up a state store and configure it as a remote backend. See the [Terraform backend docs](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) for details. If you do not have a dedicated terraform state store, you can store the state in the cloud account alongside the stack. ```hcl AWS (S3) theme={null} terraform { backend "s3" { bucket = "" key = "nuon//terraform.tfstate" region = "" } } ``` ```hcl GCP (GCS) theme={null} terraform { backend "gcs" { bucket = "" prefix = "nuon/" } } ``` The `inputs.auto.tfvars` file can be stored in version control alongside the terraform module. It can also be stored in the environment configuration of your chosen automation platform. `secrets.auto.tfvars` should be stored separately as a secret value. Alternatively, the secret values can be saved in a secret store and passed to terraform as environment variables. Initialize the terraform workspace and apply. ```bash theme={null} terraform init && terraform apply ``` This will provision the Nuon stack. Once the runner phones home, the await install stack step in Nuon will complete. # Provision Stacks with a Terraform Module Source: https://docs.nuon.co/guides/provision-stacks-with-terraform-module Use the nuon stack provider to provision a stack using a module For customers that prefer to manage their stacks using Terraform, we publish [modules for each platform Nuon supports](https://registry.terraform.io/search/modules?namespace=nuonco\&q=stack). This is the recommended way to provision install stacks using Terraform. The legacy flow, based on generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files, is described in [Provision Stacks with Terraform](/guides/provision-stacks-with-terraform). ## How it works When creating an install, you will be presented with a "TF Module" option for provisioning the stack. This will provide Terraform code your customer can use to import the stack module and authenticate with your Nuon control plane to fetch configuration data. ## Prerequisites In order to use a Terraform module, your customer will need the following. * A Terraform project, with their own state backend configured. * A secret store. * Credentials for the cloud account they want to provision the stack in. * On GCP, the project and region to provision into, if the install has not been provisioned before. AWS records the region when the install is created; on GCP the target is recorded by the first apply, so the first one must pass `project_id` and `region` to the module. * Outbound network access to the Terraform Registry from their CI (to install the module and the `nuonco/stack` provider). * Outbound network access to the Nuon runner API from their CI (so the provider can fetch the stack configuration at plan time). ## Provision a new install stack Create a new install, and retrieve the Terraform code and Nuon credentials from the "Await install stack" step of the provision workflow. You will need to share these with your customer. In your app config, configure the inputs and secrets your customer needs to provide. ```toml inputs.toml {8} theme={null} name = "instance_type" display_name = "Node Instance Size" description = "EC2 instance type for EKS worker nodes" group = "compute" default = "t3a.medium" type = "string" required = false user_configurable = true ``` ```toml secrets.toml {5} theme={null} [[secret]] name = "license_key" display_name = "License Key" description = "License key enabling access" required = true ``` See [Configuring Inputs & Secrets](/guides/configuring-inputs-and-secrets) for more details. Create an install and wait for the **Await install stack** step. The **TF Module** tab will display Terraform code templated for this install, including the install ID, region, and the customer-facing inputs and secrets. A [Service Account](/concepts/service-accounts#stack-service-accounts) has been created and assigned the "Stack" role. This is the identity the Stack SDK will use to perform stack operations for this install. The `stack` provider needs credentials to read the install's configuration. Create a static token, and share it with your customer through a secure channel. The rest of the process is completed by your customer, using the information you provide to them. Your customer must import the module using the Terraform snippet you saved from the provision workflow. ```hcl AWS theme={null} terraform { required_providers { aws = { source = "hashicorp/aws" } stack = { source = "nuonco/stack" } } } provider "aws" { region = "us-west-2" } provider "stack" {} module "aws_stack" { source = "nuonco/stack/aws" version = "~> 1.0" install_id = "" inputs = { instance_type = "t3a.medium" } secrets = { license_key = { value = var.license_key } } } variable "license_key" { type = string sensitive = true description = "License key that activates the product." } ``` ```hcl GCP theme={null} terraform { required_providers { google = { source = "hashicorp/google" } stack = { source = "nuonco/stack" } } } provider "google" { project = "my-project" region = "us-central1" } provider "stack" {} module "gcp_stack" { source = "nuonco/stack/gcp" version = "~> 1.0" install_id = "" inputs = { machine_type = "e2-standard-4" } secrets = { license_key = { value = var.license_key } } } variable "license_key" { type = string sensitive = true description = "License key that activates the product." } ``` ```hcl Azure theme={null} terraform { required_providers { azurerm = { source = "hashicorp/azurerm" } stack = { source = "nuonco/stack" } } } provider "azurerm" { subscription_id = "00000000-0000-0000-0000-000000000000" features { key_vault { purge_soft_delete_on_destroy = true } } } provider "stack" {} module "azure_stack" { source = "nuonco/stack/azure" version = "~> 1.0" install_id = "" inputs = { vm_size = "Standard_D4s_v3" } secrets = { license_key = { value = var.license_key } } } variable "license_key" { type = string sensitive = true description = "License key that activates the product." } ``` On GCP, the module reads the install's project and region from the control plane, so they appear only in the `google` provider block. Before the install's first apply the control plane has nothing recorded, and the plan will fail asking for them — pass `project_id` and `region` to the module for that first apply, then drop them. On Azure, the module reads the install's location from the control plane and takes the subscription from the `azurerm` provider, warning at plan time if the two disagree. `purge_soft_delete_on_destroy` is required: the module leaves purge protection off so a destroyed install's Key Vault name can be reused immediately, and without it Azure holds that name for 90 days. Nuon credentials and app secret values are supplied using environment variables, so they are never written to Terraform files or committed to version control. Ideally, these should be saved by your customer in their CI's secret store. Like any Terraform-managed secret, app secret values are stored in Terraform state once applied. Your customer's state backend must be encrypted and access-controlled. ```bash theme={null} export NUON_API_TOKEN='' export TF_VAR_license_key='' ``` Your customer can simply init and apply the Terraform. If any inputs are missing or invalid, there will be a Terraform validation error. ```bash theme={null} terraform init && terraform apply ``` ## Updating the stack There are two kinds of stack updates your customer may need to apply. * A **new version of the stack** — updates to the inputs, secrets, roles, or permissions in the app config. * A **new version of the Terraform module** — updates to the stack module itself, such as fixes or changes to the network topology or runner VM. The two are independent of each other. A new stack version doesn't require a module upgrade, and vice versa. ### Updating to a new stack version Stack version updates are ordinary Terraform edits followed by `terraform apply`. #### Change an input value Your customer edits the `inputs` map and re-applies. The merged values phone home and become the install's current inputs, triggering redeployment of dependent components. ```hcl theme={null} inputs = { instance_type = "t3a.medium" // [!code --] instance_type = "t3a.large" // [!code ++] } ``` Removing a key from `inputs` falls back to the control plane's current value on the next apply. It does not unset the input. #### Vendor adds a new input When you add a customer-facing input to the app config, or make an existing vendor-side input customer-facing, your customer can add the input to the inputs map and re-apply. ```hcl theme={null} inputs = { instance_type = "t3a.large" domain = "acme-corp.example.com" // [!code ++] } ``` If the new input is `required` and has no default, the apply will fail, and your customer will be shown an error message telling them to provide the input value. #### Vendor adds or removes a customer secret If you add a new secret, your customer should store the value in a secret store, and pass it to Terraform as a `TF_VAR_` environment variable. ```hcl theme={null} module "aws_stack" { source = "nuonco/stack/aws" version = "~> 1.0" // other module config... secrets = { license_key = { value = var.license_key } smtp_password = { value = var.smtp_password } // [!code ++] } } variable "smtp_password" { // [!code ++] type = string // [!code ++] sensitive = true // [!code ++] } // [!code ++] ``` ```bash theme={null} export TF_VAR_smtp_password='' ``` Removing a secret is the reverse: delete the `variable` block, the `secrets` entry, and the export. #### Vendor changes roles or permissions Operation-role permissions, break-glass roles, and custom roles are read from the control plane, and are pulled in automatically by the next `terraform plan`. Whether a break-glass or custom role is enabled is also served by the control plane, but your customer can override it with the module's `roles` map: ```hcl theme={null} roles = { "app-break-glass" = true // [!code ++] } ``` An entry set here wins over the control plane in both directions, so your customer can turn a role off or switch one on ahead of the next apply. Keys are role names — either the full served name or the name without its leading `-` prefix. A `roles` key naming a role the app does not declare fails the plan, with an error listing the valid keys. The `roles` map also accepts three reserved keys — `provision`, `maintenance`, and `deprovision` — to disable an operation role: ```hcl theme={null} roles = { deprovision = false // [!code ++] } ``` This is useful for keeping teardown permissions off until your customer intends to decommission the install. Disabling `provision` or `maintenance` prevents Nuon from performing those operations until the role is re-enabled and applied. #### Vendor removes an input If you remove an input (or make it vendor-facing again) while your customer's `inputs` map still names it, their next plan will fail with an error message telling them to remove it. ### Upgrading the module version To upgrade the Terraform module itself, your customer will update the version pin, run `terraform init -upgrade`, then `terraform apply`. To avoid your customer having to upgrade too frequently, we recommend using a minor version constraint instead of pinning to a specific version. For example, with `version = "~> 1.0"`, running `terraform init -upgrade` will automatically pick up new minor and patch releases within the 1.x series. # Pulumi Components Source: https://docs.nuon.co/guides/pulumi-components Pulumi components allow you to provision infrastructure in your customer's cloud account to power your install. Pulumi components allow you to deploy a Pulumi program from a public or connected repo, to provision infrastructure resources in customer installs. ## Configuring a Pulumi component To configure a Pulumi component, specify a repo, the Pulumi runtime, and any stack configuration values the program requires. ```toml components/.toml theme={null} # pulumi name = "pulumi_infra" type = "pulumi" runtime = "go" [connected_repo] repo = "nuonco/kitchen-sink" directory = "components/pulumi" branch = "main" [config] "aws:region" = "{{.nuon.install_stack.outputs.region}}" "kitchen-sink:install_id" = "{{.nuon.install.id}}" [env_vars] PULUMI_SKIP_UPDATE_CHECK = "true" ``` The `runtime` field selects the language your Pulumi program is written in and must match the `runtime` declared in the project's `Pulumi.yaml`. Supported runtimes are `go`, `nodejs`, `python`, `dotnet`, `java`, and `yaml`. You can configure Pulumi components to use either a public repo (using a `public_repo` block) or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). You can also put the Pulumi source inside of the same repo as the app, and point to it. If you include the Pulumi source in the app repo, you must push the entire repo and do a manual build of the component in the dashboard or CLI. The CLI's `nuon apps sync` will not recognize that the component has changed so will not do the build. By default Nuon uses the latest Pulumi CLI. To pin a specific version, set `pulumi_version`. ```toml components/.toml theme={null} # pulumi name = "pulumi_infra" type = "pulumi" runtime = "go" pulumi_version = "3.100.0" ``` ## Configuring stack config Use the `[config]` block to set Pulumi stack configuration values, equivalent to `pulumi config set`. Keys use the `namespace:key` format — for example, `aws:region` targets the AWS provider, while a project-scoped key such as `kitchen-sink:install_id` is read by your program via `config.New(ctx, "kitchen-sink")`. Config values support [component variables](/guides/using-variables), so they can access inputs, other components, the sandbox, and details about the install itself using templating. ```toml components/.toml theme={null} [config] "aws:region" = "{{.nuon.install_stack.outputs.region}}" "kitchen-sink:install_id" = "{{.nuon.install.id}}" ``` Use the `[env_vars]` block to pass environment variables into the Pulumi execution environment. ## State Management All Pulumi state is managed by the Nuon data plane through the Nuon http-based API. The runner imports the prior state before each operation and uploads the updated state back to the control plane when the operation completes. Each execution of a Pulumi component in an install is visible in the plan and apply steps in the dashboard. The Enterprise edition has two additional features including requiring approvals of the plan and apply steps and showing diffs from previous runs. ## Execution and Permissions The [runner](/concepts/runners) is responsible for executing Pulumi, and is provisioned as part of the initial Install Stack. e.g., in AWS, the CloudFormation stack creates a VPC, networking, ASG and an EC2 instance to start the runner. The runner is separate from the Install's infrastructure e.g., Kubernetes cluster. To learn more about the runner, read the [Nuon Runner Architecture](https://nuon.co/blog/the-nuon-runner-architecture/#nuons-runner-model) blog post. ## Per-install config overrides To override a Pulumi component's config for a single install without changing your app config, use [component overrides](/guides/component-overrides). # Reprovision an install Source: https://docs.nuon.co/guides/reprovision-installs Re-run provisioning against the current app config to recreate an install's stack, sandbox, and components. Reprovisioning triggers a full install workflow against current app config. Used when customer stack app config changes needs to be rolled out, or, when we need to create/update the customer [stack](/concepts/stacks) resources like roles, secrets, policies, inputs etc. Every reprovision runs as a [workflow](/concepts/workflows), so it is planned, approvable, and auditable. ## Only trigger what's needed A **reprovision install** is the full chain: it reprovisions the stack, then the sandbox, then redeploys every component on top. A **reprovision stack** stops at the stack, so it only updates the customer stack and its runner and leaves the sandbox and components alone. If the change you are rolling out is stack-facing only, reprovision the stack. | Flavor | Recreates | Runner downtime | Use when | | -------------- | -------------------------------------------------- | --------------- | -------------------------------------------------------------------- | | **Full** | Stack, then sandbox, then all components | Yes | The install is broken end to end, or the customer deleted resources | | **Stack only** | Stack and runner infrastructure; sandbox untouched | Yes | The runner infrastructure was deleted, or the stack template changed | A stack reprovision recreates the runner. Deployments and actions are unavailable until the new runner is healthy. ## Full reprovision ```sh theme={null} nuon installs reprovision -i $INSTALL_ID ``` Open the install, choose **Settings** from install navigation panel, then **Reprovision install**. Pick an [operation role](/concepts/operation-roles) if the install offers more than one, then confirm. ```sh theme={null} nuon installs workflows watch -i $INSTALL_ID ``` The install's **Workflows** view shows the same run. ## Stack only Recreates the runner and its infrastructure, leaving the sandbox alone. ```sh theme={null} nuon installs reprovision -i $INSTALL_ID --stack-only ``` `nuon installs stacks reprovision -i $INSTALL_ID` is the same operation. In the dashboard, it is **Settings**, then **Reprovision stack**. Add `--skip-components` to stop once the stack is healthy instead of redeploying components: ```sh theme={null} nuon installs reprovision -i $INSTALL_ID --stack-only --skip-components ``` `--skip-components` is only valid with `--stack-only`. The CLI rejects it on a full reprovision. ### On AWS Nuon regenerates the stack version from the current app config before applying it, so a stack reprovision picks up changes such as a new runner instance type. The ASG and EC2 instance are replaced. Resources the runner previously deployed are untouched by the stack run unless changed in app config like new permission additions, in that case we recreate that resource. ### Update a CloudFormation stack Reprovisioning generates the new template, but it does not apply it. The reprovision workflow pauses on the await install stack step, and that step's details panel has everything the customer needs, prefilled with the install's stack name, region, and new template URL: * **CloudFormation template** URL, with copy and download buttons. * **Deploy with AWS CLI**, with ready-made **Create stack** and **Update existing stack** commands. Open the step, copy what you need, and send it to the customer. Customer needs to apply it to their existing root stack. Copy the **Update existing stack** command from the step panel. It looks like this: ```sh theme={null} aws cloudformation update-stack \ --stack-name \ --template-url \ --capabilities CAPABILITY_NAMED_IAM \ --region ``` Templates that are not served from S3 come through as a `curl` plus `--template-body file://template.json` variant of the same command. In the CloudFormation console, select the stack created for the install. Use the root stack, not one of its nested stacks. Choose **Stack actions**, then **Create change set for current stack**. Choose **Replace existing template**, select **Amazon S3 URL**, and paste the **CloudFormation template** link copied from the step panel. Step through the parameters. Keep the existing values unless the reprovision introduced new inputs or secrets. Review the listed changes, then choose **Execute change set**. CloudFormation applies the new template to the existing stack. ### On GCP GCP install stacks are managed with Terraform. A reprovision generates a new stack version and pauses at the **await install stack** step until the customer applies it to the existing stack. Keep the existing Terraform configuration and state backend. The published `nuonco/stack/gcp` module reads the current stack configuration from Nuon during the next plan, including changes to roles and permissions. If the reprovision introduces or removes customer inputs or secrets, update the module's `inputs` and `secrets` maps first. Then have the customer review and apply the changes: ```sh theme={null} terraform plan terraform apply ``` A new stack version does not require a module upgrade. See [Updating the stack](/guides/provision-stacks-with-terraform-module#updating-the-stack) for detailed input, secret, role, and module upgrade instructions. Open the **Terraform** tab on the **await install stack** step and download the new `inputs.auto.tfvars` and `secrets.auto.tfvars`. Replace those files in the existing `install-stacks/gcp` working directory, then have the customer review and apply the changes: ```sh theme={null} terraform plan terraform apply ``` Both paths must use the existing state backend (typically GCS). Reusing the existing state updates the current runner infrastructure instead of creating a separate stack. When the runner reconnects, the workflow continues. ## Hooks that fire | Flavor | Action triggers | | --------------------------------- | ----------------------------------------------------- | | Full and stack only | `pre-reprovision`, `post-reprovision` | | Sandbox leg of a full reprovision | `pre-reprovision-sandbox`, `post-reprovision-sandbox` | `pre-reprovision` fires once the runner is healthy, not at the start of the workflow, because the stack run has to come up first. Subscribers to the `installs` and `sandboxes` `reprovision` [webhook](/guides/webhooks) events are notified for the same runs. ## When to reach for it * The customer deleted the runner infrastructure or other stack resources. * A [secret or input](/guides/configuring-inputs-and-secrets) changed and redeploying individual components is not enough. * The runner reports that the install stack version has expired. * Additions / modifications of roles, permissions, inputs in app config. ## See also * [App and install life cycle](/guides/app-install-life-cycle) * [Customer-controlled runner shutdown](/guides/runner-kill-switch) * [Operation roles](/concepts/operation-roles) * [Configure policies](/guides/configuring-policies) # Roll back an install Source: https://docs.nuon.co/guides/rollback-install-config Re-apply a previous app config version to one install, or to a whole deployment group, with a plan in front of it. Every config change to an install is recorded as an app config version, and any earlier version can be applied again. That is the recovery path when a rollout, or a mistyped input, turns out badly. You will need an API token (create one in the dashboard's org settings), your org ID, and the install's ID from `nuon installs list`. ## Roll back one install ```sh theme={null} curl -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ "https://api.nuon.co/v1/installs/$INSTALL_ID/app-config-versions" ``` Each entry carries the app config it pinned. The install's **Versions** view in the dashboard shows the same history. ```sh theme={null} curl -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ "https://api.nuon.co/v1/installs/$INSTALL_ID/app-config-versions/$VERSION_ID/diff" ``` ```sh theme={null} curl -X POST \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"app_config_id": "'"$OLD_APP_CONFIG_ID"'"}' \ "https://api.nuon.co/v1/installs/$INSTALL_ID/app-config-updates" ``` Send `{"plan_only": true}` alongside it first if you want to see the plan without applying. ## Roll back a deployment group To move a whole deployment group back rather than one install, trigger a branch run against the older app config and skip the build step, since the artifacts for that config already exist: ```sh theme={null} curl -X POST \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" \ -H "Content-Type: application/json" \ -d '{"app_config_id": "'"$OLD_APP_CONFIG_ID"'", "skip_builds": true}' \ "https://api.nuon.co/v1/apps/$APP_ID/branches/$APP_BRANCH_ID/runs" ``` The run still plans each group and still waits for approval, so you see the reverse diff before it is applied. Rollback re-uses the version history and the branch run API: you choose the version, and you approve the plan before anything is applied. ## See also * [App branches](/concepts/app-branches) — how runs, groups, and version history fit together. * [Configure app branches](/guides/app-branches) — set up the branch this recovery path builds on. # Configure runbooks Source: https://docs.nuon.co/guides/runbooks Define named, multi-step procedures and run them against your installs on demand.
A runbook's detail page in the Nuon dashboard, showing its steps
Runbooks turn a multi-step operational procedure into a single, repeatable artifact you run against an install on demand. Define the sequence once; run it consistently across every install instead of doing it by hand each time. Common uses: * version upgrades * database migrations * data backfills * recovery steps Each step has a `type` that determines what it does: * **`component_deploy`** — Deploy a component. Set `deploy_dependents` to also roll out the component's downstream dependents, in dependency order, or `plan_only` to check for drift without applying the plan. (`deploy` is accepted as a legacy alias.) * **`component_tear_down`** — Tear down a component. Set `tear_down_dependents` to also tear down its downstream dependents, with dependents removed first. * **`action`** — Run an existing [action](/guides/actions), or an inline command or script defined directly on the step. * **`sandbox_reprovision`** — Reprovision the install's sandbox infrastructure. Set `skip_component_deploys` to reprovision the infrastructure only, without redeploying components on top, or `plan_only` to check for drift without applying or redeploying components. * **`sandbox_deprovision`** — Deprovision the install's sandbox infrastructure. Steps run in order, and every run is recorded in the install's workflow history: a durable, auditable record of what ran, when, and against which install. ## Where runbooks live Define runbooks as TOML files in a `runbooks/` directory in your app config. Each file is one runbook: ``` my-app/ ├── metadata.toml ├── components/ ├── actions/ └── runbooks/ └── v2.3-update.toml ``` They sync with the rest of your config via `nuon apps sync`, run from the app's working directory in your terminal. Whenever you edit a runbook file locally, re-run `nuon apps sync` to push the changes to Nuon. ## Example ```toml theme={null} name = "v2.3-update" description = "Apply the v2.3 schema migration, then roll out the API." readme = "./runbooks/v2.3-update.md" [[steps]] name = "migrate-db" type = "action" action_name = "database-migration" [[steps]] name = "deploy-api" type = "component_deploy" component_name = "api-server" deploy_dependents = true [[steps]] name = "smoke-test" type = "action" command = "./scripts/smoke-test.sh" timeout = "5m" ``` ## Configuration reference ### Runbook | Field | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Name shown in the **Runbooks** tab and used to identify the runbook during sync. | | `description` | string | No | Short description of what the runbook does. | | `readme` | string | No | Markdown documentation for the runbook. Supports Go templating and external file sources (HTTP(S) URLs, git, or local paths). | | `labels` | map | No | Key/value labels for organizing runbooks. | | `steps` | array | Yes | Ordered list of steps to execute. See below. | ### Step | Field | Type | Required | Description | | ------------------------ | ------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Step name shown in the workflow UI and runbook detail page. | | `type` | string | Yes | `component_deploy`, `component_tear_down`, `action`, `sandbox_reprovision`, or `sandbox_deprovision`. `deploy` is accepted as a legacy alias. | | `component_name` | string | component steps | Component to deploy or tear down. Required when `type = "component_deploy"` or `"component_tear_down"`. | | `deploy_dependents` | bool | No | When true, also deploys the component's transitive dependents (downstream subgraph) in dependency order. Only applies to `component_deploy` steps. | | `tear_down_dependents` | bool | No | When true, also tears down the component's transitive dependents (downstream subgraph), with dependents torn down first. Only applies to `component_tear_down` steps. | | `plan_only` | bool | No | When true, generates the plan without applying it. Applies to `component_deploy` and `sandbox_reprovision`; plan-only sandbox steps also skip component redeployment. | | `skip_component_deploys` | bool | No | When true, only the sandbox infrastructure is reprovisioned; components are not redeployed on top. Only applies to `sandbox_reprovision` steps. | | `action_name` | string | action steps | Name of an existing [action](/guides/actions) to run. Mutually exclusive with the inline fields below. | | `command` | string | action steps | Shell command for an inline action. Supports Go templating. | | `inline_contents` | string | action steps | Inline script contents, or a reference to an external file. Supports templating and external sources. | | `env_vars` | map | No | Environment variables for an inline action step. | | `timeout` | string | No | Maximum execution time for an inline action step (Go duration, e.g. `30s`, `5m`). | | `role` | string | No | IAM role to assume when executing an inline action step. | An action step uses **either** `action_name` (to run a previously defined action) **or** the inline fields (`command` / `inline_contents`) to define an inline action, not both. ## Triggering a runbook Trigger and watch runs from the **Runbooks** tab in the [dashboard](https://app.nuon.co/), or from the CLI: ```sh theme={null} # list the runbooks available on an install nuon runbooks list --install-id ``` Each run is recorded in the install's workflow history, so you can review the steps, logs, and outcome of any past runbook execution. ## A complete example For a runnable example, see the [`programmable-runbook-and-readme`](https://github.com/nuonco/example-app-configs/tree/main/programmable-runbook-and-readme) app config in `example-app-configs`. It has runbooks for an on-call restart and a smoke test, plus rendered READMEs that embed them. # Customer-Controlled Runner Shutdown Source: https://docs.nuon.co/guides/runner-kill-switch How customers can pause or permanently stop the Nuon Runner from their own AWS account. The runner is managed via the stack, and is responsible for all actions. While you can also remove permissions, you can also entirely disable the runner. This is useful for partial-airgapped installs, release windows and customers who want more control. ## How It Works The Runner is created by the customer's [Stack](/concepts/stacks) during install provisioning. The customer has full ownership of this ASG in their AWS account. By changing the ASG capacity settings, the customer controls whether the Runner is up. | Approach | Method | Reversibility | Use case | | --------------------------- | ------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------- | | **Soft kill** (recommended) | Set ASG capacity to 0/0/0 | Set back to 1/1/1 to restore | Temporary pause during audit, incident response, or compliance review | | **Hard kill** | Delete the ASG entirely | Must reprovision from scratch via `nuon installs reprovision` | Permanent removal or vendor offboarding | ## Soft Kill: Scale the ASG to Zero This is the recommended approach for temporarily pausing the Runner. It gracefully terminates the EC2 instance without destroying any ASG configuration. ### Steps 1. Open the **EC2 Console** in the AWS account where the install is provisioned. 2. Navigate to **Auto Scaling Groups** in the left sidebar. 3. Select the Runner's ASG (named after the install). 4. Choose **Edit** on the **Group details** panel. 5. Set all three capacity fields to **0**: * **Desired capacity**: 0 * **Min desired capacity**: 0 * **Max desired capacity**: 0 6. Click **Update**. The Runner EC2 instance will be terminated within a few minutes. While scaled to zero: * No deployments, updates, or actions can be executed against this install. * The vendor cannot reprovision the Runner — `nuon installs reprovision` will fail. * All existing infrastructure deployed by the Runner remains untouched. ### Restoring the Runner To resume normal operation, edit the ASG and set the capacity back to **1/1/1** (Desired: 1, Min: 1, Max: 1). The Runner will restart if you navigate to the Install's Runner and choose **Restart process** on the Runner. ## Hard Kill: Delete the ASG For permanent removal (e.g., offboarding a vendor), the customer can delete the ASG entirely from the AWS Console or CLI. This removes the Runner and its launch configuration. To restore after a hard kill, the vendor must reprovision the install: ```bash theme={null} nuon installs reprovision ``` This recreates the ASG and Runner from the Stack definition. ## Additional Lockdown Options For even more granular control over what the Runner can do, Nuon supports [Policies](/concepts/policies#policies) that let you define approval gates, scoping rules, and conditions on deployments and actions. Policies can be combined with the ASG kill switch for defense-in-depth. ## Related Resources * [Runner Management Mode](/guides/runner-management-mode) — Configure how the Runner process is managed within the EC2 instance. * [Nuon Security & Trust](/guides/vendor-customers) — Overview of Nuon's security model and how to communicate it to customers. * [Customer Cloud Permissions](/guides/vendor-customers#customer-cloud-permissions) — How IAM roles scope what the Runner can do. * [Security Architecture](/security) — Technical overview of Nuon's security properties. # Runner Management Mode Source: https://docs.nuon.co/guides/runner-management-mode Configure runner management mode for new installs or migrate existing installs. ## What is Management Mode? Runner management mode is a standalone process that is deployed *first* in an install's runner VM. Once it has started, it is responsible for configuring, starting, monitoring, and updating a child runner process for the install itself. This install runner process then carries out all of the day to day runner operations. This allows for an additional degree of flexibility when managing updates and paves the way for future runner features. This release introduces runner management mode into GA as an opt-in feature which can be enabled by configuring the `init_script_url` attribute in `runner.toml` in the app. We recommend all new installs make use of it from day-1. ## Migrating an existing Install 1. Update `runner.toml` to use the management mode init script. ```toml runner.toml theme={null} init_script_url = "https://raw.githubusercontent.com/nuonco/runner/refs/heads/main/scripts/aws/init-mng.sh" ``` 2. Update `stack.toml` to use the 1.6 version of the Runner ASG nested CF stack `runner_nested_template_url`. ```toml stack.toml theme={null} runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.1.6/runner/asg/stack.yaml" ``` 3. Sync the app with `nuon apps sync` 4. Reprovision the install and apply the new stack as a CF change-set. ## New Installs 1. Configure `runner.toml` to use the management mode init script. 2. Configure `stack.toml` to use the 1.6 version of the Runner ASG nested CF stack. ```toml runner.toml theme={null} init_script_url = "https://raw.githubusercontent.com/nuonco/runner/refs/heads/main/scripts/aws/init-mng.sh" ``` 2. Update `stack.toml` to use the 1.6 version of the Runner ASG nested CF stack `runner_nested_template_url`. ```toml stack.toml theme={null} runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.1.6/runner/asg/stack.yaml" ``` 3. Carry on as usual. # Sandbox Builds Source: https://docs.nuon.co/guides/sandbox-builds Provision from an immutable artifact instead of cloning Git at apply time. Reproducible reprovisions, no VCS dependency in your customer's account, failures caught before they touch infrastructure. A customer's install provisioned cleanly in March. It's November, and you need to reprovision it. By default, that reprovision clones your sandbox repo *right then* and applies whatever is at the configured branch today. Eight months of module changes, a bumped provider constraint, a repo that someone renamed — all of it lands on infrastructure that has been running fine, and none of it is what the install was built with. Sandbox builds close that gap. Nuon packages your sandbox into an **immutable OCI artifact** — the same mechanism already behind [component builds](/guides/app-install-life-cycle#sync-and-build) — and installs provision from the artifact instead of reaching for Git. Sandbox builds are behind the `sandbox-oci-artifacts` org feature flag and are off by default. [Reach out to Nuon](https://nuon.co/demo-request) to enable it for your org. An artifact is immutable and pinned to an app config version. A reprovision a year later applies the exact code that install was created with. The install runner pulls the sandbox from the same registry it already pulls component artifacts from. Nothing in your customer's account reaches GitHub or holds a Git credential. With the provider mirror on, providers ship inside the artifact, so a provision doesn't depend on `registry.terraform.io` being reachable. A bad sandbox fails at build time, in your branch run, instead of part-way through a customer's provision. ## Turn it on Sandbox builds are behind the `sandbox-oci-artifacts` org feature. Ask us to switch it on — there is nothing to install and no change to your `sandbox.toml`. If your app uses [app branches](/concepts/app-branches), the next push builds it automatically. Otherwise hit **Build sandbox** on the app's **Sandbox** tab. Any sandbox run for an install on that app config version now uses the artifact. The run's logs say which source it used. There is no configuration for any of this. Nothing in your app config changes, and nothing below is something you have to set up. ## How the source is resolved Every sandbox run — provision, reprovision, deprovision — resolves where to get the code from before it runs anything: ```mermaid theme={null} flowchart TD run["sandbox run starts"] --> lookup{"active sandbox build
for this install's
app config version?"} lookup -->|yes| oci["pull and unpack
the OCI artifact"] lookup -->|no| git["clone Git
at the configured ref"] oci --> apply["terraform / pulumi apply"] git --> apply ``` **The lookup is per install, not per app.** It keys off the app config version *that install is pinned to*, so two installs on different versions of your app provision from two different sandbox artifacts — each one the sandbox that shipped with its version. **A missing artifact is never a failure.** No build for that version, or an artifact that can't be resolved, falls back to cloning Git exactly as before. That's what makes turning the flag on safe: installs that predate your first sandbox build keep working untouched, and adoption happens install-by-install as each one moves onto a version that has a build behind it. This applies to Terraform and Pulumi sandboxes alike — the runner unpacks the artifact and works in that directory instead of a clone, and everything downstream is unchanged. ## Where builds come from A sandbox build is a record tied to one app config version and one sandbox config, executed by a [build runner](/architecture/platform#build-runner) in the Nuon control plane. It moves through `queued` → `planning` → `building` → `active`, or `error`, and streams logs the whole way. The artifact is pushed to your app's OCI repository — the same one your component builds are pushed to — tagged with the build's ID. ### On every app branch run If your app uses [app branches](/concepts/app-branches), the `building components and sandbox` step builds the sandbox alongside your components. It appears as its own row in that step, next to each component, and links through to its logs. ```mermaid theme={null} flowchart LR push["git push"] --> config["fetch app config"] config --> build["building components and sandbox"] build --> comps["component artifacts"] build --> sbx["sandbox artifact"] comps --> plan["plan install group"] sbx --> plan ``` With the flag off, that step still runs — it just builds components only. **Which ref gets built:** if your sandbox lives in the same repo as the app config being built, the build is pinned to that run's commit SHA, so the sandbox and the components come from one commit. If the sandbox lives elsewhere, it uses the ref from your sandbox config. ### On demand The app's **Sandbox** tab lists every build with its status, and **Build sandbox** runs one against your current config. Each build links to a detail page with its timeline and logs. ```bash theme={null} curl -X POST "https://api.nuon.co/v1/apps/$APP_ID/sandbox/builds" \ -H "Authorization: Bearer $NUON_API_TOKEN" \ -H "X-Nuon-Org-ID: $NUON_ORG_ID" ``` Useful for validating a sandbox change before you push it, and for apps that don't use app branches. Building doesn't require the feature flag — the tab and the endpoint work either way. What the flag controls is whether installs **use** the result. Turning it on for an org that has been building all along changes nothing until the next sandbox run. ## Vendoring Terraform providers Sandbox builds are what make provider vendoring possible for a sandbox at all — there's no build step to vendor into otherwise. With the `terraform-provider-mirror` feature also enabled, the build runs `terraform providers mirror` and ships the resulting filesystem mirror inside the artifact. The install runner finds it on unpack and initializes against it, so provisioning needs no Terraform Registry access. Pin `terraform_version` in your `sandbox.toml` so the build mirrors providers for the version that will actually run: ```toml sandbox.toml theme={null} terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-sandbox" branch = "main" ``` Together, these two features take a provision from "needs GitHub *and* `registry.terraform.io`" to "pulls one more artifact from the registry it already pulls component artifacts from" — a much smaller ask of a customer with a restricted egress policy. ## Troubleshooting There's no `active` build for the app config version *that install* is pinned to. An install on an older version has no artifact behind it until it moves to a version that does — check the app's **Sandbox** tab for which versions have builds, and the sandbox run's logs for which source it chose. Three reasons it's skipped: the flag is off, the app has no sandbox config, or the run had no config changes and wasn't forced — a run with nothing to build doesn't build anything, sandbox included. Re-trigger the run with force, or build once from the **Sandbox** tab. Open it from the **Sandbox** tab or from the branch run's build step and read its logs. These are the same failures you'd otherwise hit mid-provision — an unresolvable module, a provider constraint that can't be satisfied, invalid Terraform — which is the point of catching them here. Vendoring is a second feature. Ask us to enable `terraform-provider-mirror` as well, and set `terraform_version` in your `sandbox.toml` so the mirror matches the version that runs. ## See also * [Sandboxes](/concepts/sandboxes) — what a sandbox is and what it provisions * [Configuring Sandboxes](/guides/configuring-sandboxes) — the `sandbox.toml` reference * [App Branches](/concepts/app-branches) — the workflow that builds sandboxes on every push * [App and Install Life Cycle](/guides/app-install-life-cycle) — how builds and deploys fit together # Nuon Self-Hosted Source: https://docs.nuon.co/guides/self-hosted Run Nuon entirely within your own infrastructure, with no path for Nuon to reach in Nuon Self-Hosted is a single-tenant Nuon control plane running in your own AWS, Azure, or Google Cloud account, with no path for Nuon to reach in. It is the most disconnected deployment option: you install it, operate it, and upgrade it. This is the same software as [Nuon BYOC](/guides/byoc), but without the remote operator. There is no Nuon Runner in your account polling Nuon Cloud for jobs, no managed upgrades, and no remote troubleshooting. In exchange, the installation has no mechanism for Nuon to access, which is useful for compliance, sovereignty, or air-gap requirements where any remote-managed control plane (even a single-tenant one) is ruled out. Nuon Self-Hosted is open source. You can deploy and operate it yourself at no cost. A paid license is required only if you need Nuon support. [Contact sales](https://nuon.co/contact-sales) to learn more. ## Architecture Your control plane runs in your cloud account with no connection to Nuon. Customer Runners poll your control plane for jobs and pull artifacts from your container registry, the same flow as every other deployment model. ```mermaid theme={null} flowchart TD subgraph YourCloud["Your Cloud Account: AWS · Azure · GCP"] subgraph CP["Control Plane: EKS · AKS · GKE"] DASH["dashboard-ui"] CTL["ctl-api"] BR["Build Runner"] end subgraph Deps["Dependencies"] TMP["Temporal"] CH["ClickHouse"] DB[("Postgres")] REG[("Container Registry")] BLOB[("Blob Storage")] end end subgraph EndCustomers["Customer Clouds: AWS · Azure · GCP"] R["Nuon Runner"] I["Installs"] end DASH -->|"API calls"| CTL CTL --> TMP CTL --> CH CTL --> DB CTL --> BLOB CTL -->|"trigger builds"| BR BR -->|"push OCI artifacts"| REG R -->|"polls for jobs"| CTL R -->|"pull artifacts"| REG R -->|"deploys"| I ``` ## Operational Responsibility Self-hosting means you own the full lifecycle of the Nuon control plane: * **Upgrades**: you apply new Nuon releases. There is no Nuon Runner in your account to do this remotely. * **Backup and restore**: your Postgres, blob storage, and container registry are yours to back up and recover. * **Monitoring and alerting**: wire your own observability into the control plane services. * **Troubleshooting**: Nuon engineers cannot remotely inspect logs, run diagnostics, or apply hotfixes. Support is limited to guidance you can act on yourself. ## Supported Platforms * AWS: Nuon deploys on EKS with RDS, ECR, Route 53, ACM, and Secrets Manager. * Azure: AKS with Azure SQL, ACR, Blob Storage, and Key Vault. * GCP: GKE with Cloud SQL, GAR, GCS, and Secret Manager. # Nuon Self-Hosted Dependencies Source: https://docs.nuon.co/guides/self-hosted/dependencies Dependencies required to self-host Nuon ## Dependency Overview The `ctl-api` and `dashboard-ui` services depend on a handful of managed resources you'll provision in your cloud account before deploying Nuon. ### Temporal Nuon uses Temporal to orchestrate long-running workflows: component builds, install provisioning, and deployment actions. The `ctl-api` connects to Temporal to schedule and drive these workflows. This guide assumes you have an existing temporal deployment already. We suggest using the official [temporal helm chart](https://github.com/temporalio/helm-charts/). ### Clickhouse Clickhouse is the log and event store behind the Nuon dashboard: workflow logs, deployment history, and OTel traces stream into it from `ctl-api` and are served back to the UI for inspection. This guide is not concerned with configuring and operating clickhouse itself. We use the [`Altinity/clickhouse-operator`](https://github.com/Altinity/clickhouse-operator/) to deploy a 3-node CH Keeper cluster and a 2 node ClickHouse Cluster. The `ctl-api` expects a cluster since we leverage several features such as `ReplicatedMergeTree` tables. ### Postgres Two Postgres databases are required: one for `ctl-api`'s application state (orgs, apps, installs, builds) and one backing Temporal's persistence layer. **AWS:** Our [Nuon BYOC terraform module](https://github.com/nuonco/byoc/tree/main/byoc-nuon/src/components/rds_cluster_nuon) is a useful reference. **GCP:** Our [Nuon BYOC GCP terraform module](https://github.com/nuonco/byoc/tree/main/byoc-nuon-gcp/src/components) provides references for both `cloudsql_nuon` and `cloudsql_temporal`. ### Container Registry `ctl-api` builds customer components into OCI artifacts and pushes them to a container registry in the management account, where runners later pull them during install deployments. **AWS:** ECR is used for container image storage. **GCP:** Artifact Registry is used for container image storage. ### IAM & Permissions **AWS:** The `ctl-api` deployment relies on an IAM role (attached to the service account) with specific permissions which we enumerate here. The [Nuon BYOC terraform module](https://github.com/nuonco/byoc/tree/main/byoc-nuon/src/components/management) is a useful reference. #### ECR A management IAM role is used by `ctl-api` to push to ECR and assume cross-account roles into customer AWS accounts, while service-account roles (IRSA) grant the `ctl-api` and `dashboard-ui` pods scoped access to AWS APIs. #### RDS The `ctl-api` uses RDS IAM auth to connect to the db, as opposed to a `DATABASE_URI` with credentials baked in. #### S3 The blob storage requires the `ctl-api` to be able to read and write from an S3 bucket. **GCP:** The `ctl-api` and `dashboard-ui` deployments rely on GCP service accounts bound to Kubernetes service accounts via Workload Identity. The [Nuon BYOC GCP management component](https://github.com/nuonco/byoc/tree/main/byoc-nuon-gcp/src/components/management) is a useful reference for the required service accounts and IAM bindings. #### Artifact Registry A management service account is used by `ctl-api` to push to GAR. It is granted `roles/artifactregistry.admin` and bound to the `ctl-api/ctl-api` Kubernetes service account via Workload Identity. #### Cloud SQL The `ctl-api` connects to Cloud SQL using IAM database authentication via Workload Identity, as opposed to a `DATABASE_URI` with credentials baked in. #### Cloud Storage Blob storage requires the `ctl-api` to be able to read and write from a GCS bucket. ## Deploying Nuon We maintain helm charts for Nuon at [`nuonco/charts`](https://github.com/nuonco/charts). These can be deployed with helm into an existing Kubernetes cluster. **AWS:** Our helm configs make heavy use of annotations for nodepool selection because we use Karpenter. We recommend deploying to an EKS cluster with Karpenter installed or EKS Auto Mode. **GCP:** Deploy to an existing GKE cluster. Please refer to the charts to deploy the following two services: * [`ctl-api`](https://github.com/nuonco/charts/tree/main/charts/ctl-api) * [`dashboard-ui`](https://github.com/nuonco/charts/tree/main/charts/dashboard-ui) Note: `dashboard-ui` depends on `ctl-api`. # Slack Source: https://docs.nuon.co/guides/slack Install the Nuon Slack app, link it to your org, and subscribe channels to deploy, action, and approval events. The Nuon Slack app posts deploy, action, workflow, and approval events into Slack channels you choose, and lets users approve workflow steps without leaving Slack. The Slack integration is gated by an org feature flag. If you don't see **Slack** in the dashboard sidebar, ask Nuon support to enable the `slack` feature flag for your org. ## Overview There are three pieces of state involved: | Concept | What it is | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | **Slack installation** | The OAuth installation of the Nuon app in a Slack workspace. One per workspace. | | **Org link** | Connects a Nuon org to a Slack workspace. One Nuon org can be linked to multiple workspaces; one workspace can host multiple orgs. | | **Channel subscription** | A per-channel rule that decides *which* events from a linked org are posted into *that* channel. Backed by `match` + `interests`. | ## Installing the Slack app 1. In the dashboard sidebar, click **Slack**. 2. Click **Install in Slack**. You'll be redirected to Slack's OAuth consent screen. 3. Approve the requested scopes. Slack redirects back to Nuon and creates the workspace installation. 4. The workspace now shows up under **Slack → Linked workspaces** for your org. Repeat step 1–2 from another org to link the same workspace to multiple orgs. ## Subscribing a channel Subscriptions are created from inside Slack via the slash command: ``` /nuon subscribe ``` This opens a modal where you choose: * **Org** — which linked Nuon org to subscribe to. * **Match** — *which entities* in the org events must come from. * **Interests** — *which events* you care about. Re-running `/nuon subscribe` in a channel that already has a subscription opens the modal pre-filled with the current settings — you can use it to edit. ### Slash commands | Command | What it does | | ------------------- | ---------------------------------------------------------------------------------------------- | | `/nuon subscribe` | Open the subscribe dialog for the current channel. | | `/nuon unsubscribe` | Open the unsubscribe dialog for the current channel. | | `/nuon status` | Show the workspace installation, linked orgs, and the current channel's subscription (if any). | | `/nuon help` | Show the help message. | All responses are ephemeral — only the user who ran the command sees them. ## Match — routing predicate `match` decides which entities a subscription cares about. Three modes: * **Org-wide** — every entity in the linked org. * **Specific IDs** — an explicit list of install / app / component IDs. * **Label selector** — entities whose labels match a selector (e.g. `env=prod,tier=critical`). The same `match` contract is shared with [webhooks](/guides/webhooks), so the same mental model applies in both places. ## Interests — event filter `interests` decides which kinds of events are delivered. It's a structured filter across: * **Resource kinds** — `installs`, `components`, `actions`, `workflows`. * **Operations** — `deploy`, `destroy`, `plan`, etc. * **Outcomes** — `completion`, `failures`. * **Approvals** — approval requests and approval responses. For example, a "production deploy failures" subscription matches `installs` with the `env=prod` label, and interests of `deploy` + `failures`. ## Role change notifications When a customer enables or disables an [operation role](/concepts/operation-roles) in their install stack, Nuon can post a notification to subscribed Slack channels. Role change events appear as standalone messages (not threaded under a workflow) showing the role name, type, and install context. To receive role change notifications, include **installs** in your subscription's interests. Role enable/disable events are delivered alongside other install lifecycle events. ## Approvals from Slack When a workflow step requires approval and a channel is subscribed to approval events, Nuon posts the approval request into that channel with **Approve** / **Reject** buttons. Clicking either button records the response against the approver's Nuon account (resolved from their Slack identity), so the audit trail matches a dashboard approval. ## Unsubscribing From inside the channel, run: ``` /nuon unsubscribe ``` You can also remove a subscription from **Slack → Subscriptions** in the dashboard. ## Removing the workspace installation To fully remove the Nuon app from a workspace, an org admin can delete the **Slack installation** from **Slack → Linked workspaces** in the dashboard. This deletes the OAuth token, all org links for the workspace, and all channel subscriptions tied to those links. ## Troubleshooting * **`/nuon subscribe` says "this workspace isn't linked to any Nuon org yet"** — Open the dashboard and complete the OAuth install for the org you want to subscribe to. * **No events arriving in a subscribed channel** — Run `/nuon status` in the channel to confirm the subscription is recorded, then verify the source entity's labels actually match the `match` selector. * **Buttons in approval messages do nothing** — The Slack app's *Interactivity Request URL* must be reachable from Slack. If you're running locally, your Slack app config must point at a public tunnel (e.g. Tailscale Funnel or ngrok) in front of the Nuon Slack listener. # Team Management Source: https://docs.nuon.co/guides/team-management Add team members to your Nuon Org You can add users to your Nuon Org using our CLI. ## Invite a user to your Org You can invite a user by email to your Nuon Org using our cli: ```sh theme={null} nuon orgs invite --email=user@yourdomain.com --role=org_read_only ``` Pass `--role` to set what the new member can do. See [Access control](/concepts/access-control) for the available roles and what each grants; run `nuon roles list` to see the current set. If you omit `--role`, the member is invited as an admin. The invited user will get a welcome email, and when they sign into the dashboard next will automatically be added to your Org. ## Change a member's role To change the role of an existing member, use their user ID. This requires org admin access. ```sh theme={null} nuon orgs update-user-role --user-id= --role=org_read_only ``` A member holds one role at a time, so this replaces their current role. ## View invites You can view invites for your Org using our cli: ```sh theme={null} nuon orgs list-invites ``` ## Delete an invite / remove users If you need to delete an invite, or remove a user from your Org, [please contact us](https://nuon.co/contact-us). # Using Terraform CLI Source: https://docs.nuon.co/guides/terraform-cli Run Terraform commands locally against your Nuon-managed sandbox and component workspaces. The Nuon dashboard provides a **Use Terraform CLI** option on both the Sandbox and Terraform Component detail pages. This lets you run Terraform commands locally against the remote state managed by Nuon. It's useful for debugging, inspecting resources, importing state, or running targeted plans. ## Prerequisites Install the Nuon CLI if you haven't already: ```bash theme={null} brew install nuonco/tap/nuon ``` Authenticate with your account: ```bash theme={null} nuon auth login ``` You must use the **same Terraform version** configured for the sandbox or component. The version is displayed on the configuration card in the dashboard. For sandboxes, this is the `terraform_version` in your `sandbox.toml`. For components, it's the `terraform_version` in the component's `.toml` config. Terraform is deprecated on `brew`. Consider using Terraform version manager `tfenv` to install and use multiple Terraform versions locally. You can install it with: ```bash theme={null} brew update brew install tfenv ``` List remote versions, install specific versions, list versions, and globally select a version ```bash theme={null} tfenv list-remote tfenv install 1.13.5 tfenv install 1.11.3 tfenv list tfenv use 1.13.5 ``` Verify your local version matches: ```bash theme={null} terraform version ``` Running a different Terraform version can corrupt state or produce unexpected plan diffs. Use a version manager like [tfenv](https://github.com/tfutils/tfenv) or [asdf](https://github.com/asdf-vm/asdf) to switch versions easily. You must run Terraform commands from within the **same Terraform module directory** that the sandbox or component is configured to use. This is the directory containing your `.tf` files. For example, if your sandbox is configured with: ```toml sandbox.toml theme={null} [public_repo] repo = "nuonco/aws-eks-sandbox" directory = "." branch = "main" ``` Then clone and enter that repo: ```bash theme={null} git clone https://github.com/nuonco/aws-eks-sandbox.git cd aws-eks-sandbox ``` For a component configured with a subdirectory: ```toml components/database.toml theme={null} [public_repo] repo = "your-org/your-infra" directory = "modules/database" branch = "main" ``` Navigate to that subdirectory: ```bash theme={null} cd modules/database ``` ## Setting Up the Backend Click the **\{} Use Terraform CLI** button on the lower right part of Sandbox or Component detail page in the dashboard. This opens a modal, and click the Download button to retrieve a `nuon_backend.tf` file containing the HTTP backend configuration for that workspace. The generated file looks like this: ```hcl nuon_backend.tf theme={null} terraform { backend "http" { lock_method = "POST" unlock_method = "POST" address = "https://api.nuon.co/v1/terraform-backend?workspace_id=&org_id=&token=undefined" lock_address = "https://api.nuon.co/v1/terraform-workspaces//lock?org_id=&token=undefined" unlock_address = "https://api.nuon.co/v1/terraform-workspaces//unlock?org_id=&token=undefined" } } ``` Download this file and place it in your Terraform module directory alongside the other `.tf` files. ## Set your Nuon API token Create a Nuon API token for your current org ```bash theme={null} nuon orgs api-tokens create --name terraform-cli --output json | jq -r .api_token ``` Replace the three token `undefined` blocks in `nuon_backend.tf` with the token value. The token is only shown once, so copy it before continuing. ## Initializing Terraform Initialize the backend: ```bash theme={null} terraform init -reconfigure ``` The `-reconfigure` flag is required to switch Terraform's backend to the Nuon HTTP backend. After initialization, your local Terraform is connected to the remote workspace state. `nuon orgs api-tokens create` generates a durable API token (valid one year by default) scoped to your currently selected org. Make sure you've selected the correct org with `nuon orgs select` before running this command. The token authenticates all Terraform backend requests (state, lock, and unlock). Revoke it any time with `nuon orgs api-tokens delete --id `. ## Running Commands Once initialized, you can run standard Terraform commands against the workspace: ```bash theme={null} # View current state terraform state list # Inspect a specific resource terraform state show # Preview changes terraform plan # Apply changes terraform apply # Import an existing resource into state terraform import ``` Running `terraform apply` locally will modify the live install infrastructure. Use caution and prefer `terraform plan` for inspection. ## Unlocking State If a Terraform operation is interrupted (e.g., a network failure during apply), the workspace state may remain locked. You can unlock it from the dashboard using the **Unlock Terraform state** button on the Sandbox or Component detail page. Only force unlock a workspace if you are certain no other operation is actively running. Force unlocking a workspace in use by a running job may cause state corruption. ## Viewing State in the Dashboard The dashboard displays the current Terraform state for both sandboxes and components, organized into two tabs: * **Outputs** - all Terraform outputs and their current values * **Resources** - all managed resources with their type, provider, mode, and attribute values This state view updates after each successful sandbox run or component deploy. # Terraform Components Source: https://docs.nuon.co/guides/terraform-components Terraform components allow you to provision infrastructure in your customer's cloud account to power your install. Terraform components allow you to deploy Terraform from a public or connected repo, to provision infrastructure resources in customer installs. ## Configuring a Terraform component To configure a Terraform component, specify a repo, the required configuration values, and the version of Terraform to run it with. ```toml components/.toml theme={null} # terraform name = "certificate_wildcard_public" type = "terraform_module" terraform_version = "1.11.3" [public_repo] repo = "nuonco/byoc" directory = "byoc-nuon/src/components/certificate" branch = "main" [vars] install_id = "{{ .nuon.install.id }}" region = "{{ .nuon.install_stack.outputs.region }}" zone_id = "{{ .nuon.sandbox.outputs.nuon_dns.public_domain.zone_id }}" domain_name = "*.{{ .nuon.sandbox.outputs.nuon_dns.public_domain.name }}" ``` You can configure Terraform components to use either a public repo (using a `public_repo` block) or a private GitHub repo (using a `connected_repo` block). Read more about VCS configuration [here](/guides/vcs). You can also put the Terraform source inside of the same repo as the app, and point to it. If you include the Terraform source in the app repo, you must push the entire repo and do a manual build of the component in the dashboard or CLI. The CLI's `nuon apps sync` will not recognize that the component has changed so will not do the build. ## Configuring Terraform Vars You can configure terraform variables for a component using [component variables](/guides/using-variables). These variables can access inputs, other Components, the sandbox, and details on the install itself using templating. ```toml components/.toml theme={null} # terraform name = "dynamodb_table" type = "terraform_module" terraform_version = "1.11.4" [public_repo] repo = "nuonco/example-app-configs" directory = "aws-lambda/src/components/dynamodb-table" branch = "main" [vars] name = "widgets-{{.nuon.install.id}}" hash_key = "ID" install_id = "{{.nuon.install.id}}" region = "{{.nuon.install_stack.outputs.region}}" ``` ## State Management All Terraform state is managed by the Nuon data plane through the Nuon http-based API. Each execution of a Terraform component in an install is visible in the plan and apply steps in the dashboard. The Enterprise edition has two additional features including requiring approvals of the plan and apply steps and showing diffs from previous runs. ## Execution and Permissions The [runner](/concepts/runners) is responsible for executing Terraform, and is provisioned as part of the initial Install Stack. e.g., in AWS, the CloudFormation stack creates a VPC, networking, ASG and an EC2 instance to start the runner. The runner is separate from the Install's infrastructure e.g., Kubernetes cluster. To learn more about the runner, read the [Nuon Runner Architecture](https://nuon.co/blog/the-nuon-runner-architecture/#nuons-runner-model) blog post. ## Per-install variable overrides To override a Terraform module's variables for a single install without changing your app config, use [component overrides](/guides/component-overrides). # Toggleable Components Source: https://docs.nuon.co/guides/toggleable-components Turn components on and off per install, with the install config as the source of truth. Some components are optional: a customer on one tier gets observability, another doesn't; one install needs a certificate, another brings its own. Toggleable components let each install decide which components are enabled, without forking your app config. ## Marking a component toggleable Set `toggleable = true` on a component in your app config, with an optional `default_enabled` for its starting state: ```toml components/observability.toml theme={null} name = "observability" type = "helm_chart" toggleable = true default_enabled = false ``` If you omit `default_enabled`, it defaults to `false` — the component starts disabled until an install enables it. A non-toggleable component is always enabled and cannot be turned off. Only toggleable components can be enabled or disabled per install. ## Toggling per install A toggleable component's enabled state is stored as an [install input](/guides/configuring-inputs-and-secrets). That input is the source of truth, and there are several ways to set it: the install config, the dashboard, and the CLI all write the same underlying input. If you manage the install with an install config, set the state per component in a `[component_toggles]` section, keyed by component name: ```toml install.toml theme={null} name = "customer-acme" [component_toggles] observability = true certificate = false ``` `true` enables the component on this install; `false` disables it. A component you don't list falls back to its `default_enabled` value from the app config. When you run `nuon installs sync`, Nuon reconciles every toggle in one pass: newly enabled components are deployed and newly disabled components are torn down, all within a single workflow. Enabling or disabling a component also runs its `pre`/`post` enable and disable [lifecycle actions](/guides/actions) when defined. ## Dependencies Toggle state has to stay consistent with the [component dependency graph](/guides/component-dependencies). Nuon enforces two rules whenever you sync: * An enabled component cannot depend on a disabled one. * A component cannot be disabled while a component that depends on it is still enabled. These run before any change is applied, so an inconsistent config is rejected up front rather than producing a broken install. Both declared `dependencies` and implicit output references (`{{.nuon.components..outputs...}}`) count as edges in the graph. For example, given `api_gateway` depends on `certificate`, syncing a config that enables the gateway while the certificate is disabled fails with: ``` component "api_gateway" is enabled but its dependency "certificate" is disabled Enable "certificate", or disable "api_gateway", so the install reaches a consistent state. ``` Fix the config — enable the dependency or disable the dependent — and sync again. ## Dashboard and CLI Each toggleable component's enabled state is exposed through the API, the dashboard, and the CLI: ```bash theme={null} nuon installs components list --install-id ``` You can also toggle a component imperatively from the dashboard or CLI, which sets the same enabled input directly: ```bash theme={null} nuon installs components toggle --install-id --component-id --enable nuon installs components toggle --install-id --component-id --disable ``` `--component-id` accepts a component ID or name. If you omit both `--enable` and `--disable`, the CLI prompts you to choose. For installs managed by an install config, the dashboard toggles are disabled, since the config file is the place these inputs are managed and a later `nuon installs sync` would overwrite an out-of-band change. Config sync reconciles all toggles in a single workflow. An imperative per-component toggle runs a dedicated enable or disable workflow for that one component. # Configure Triggers Source: https://docs.nuon.co/guides/triggers Create a trigger, route events with rules, and inspect events and dispatches. [Triggers](/concepts/triggers) turn external events into Nuon actions. This guide walks through creating a trigger, exposing its ingress URL, routing events with rules in your app config, and inspecting the events and dispatches that result. ## 1. Create a trigger Create a trigger with the CLI. Use a `--preset` to configure authentication and payload parsing for a known provider: ```sh theme={null} nuon triggers create my-trigger --preset github ``` Without a preset, set the authentication type explicitly. For secret-based types like `hmac`, Nuon generates a signing secret for the trigger — reveal and rotate it with the secret commands below. ```sh theme={null} nuon triggers create my-trigger --auth-type hmac ``` Use `--auth-config` to supply advanced settings as a JSON object — for example the header to read the signature from, the signature algorithm, or the expected JWT audience. Common flags: | Flag | Description | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `--description` | A human-readable description. | | `--preset` | Provider preset (`github`, `slack-events`, `datadog`, `aws-eventbridge`, `aws-sns`, `google-pubsub`, `azure-event-grid`). | | `--auth-type` | `none`, `hmac`, `api_key`, `basic`, `bearer_jwt`, or `sns_signature`. | | `--auth-config` | JSON configuration for the chosen auth type. | | `--envelope` | Transport envelope to unwrap: `none`, `pubsub_push`, `cloudevents`, or `sns`. | | `--type-header` / `--type-payload` | Where to read the event type from — a header name or a payload JSONPath. | | `--id-header` / `--id-payload` | Where to read the event ID (used for deduplication) from. | ## 2. Get the ingress URL The ingress URL is the secret endpoint external systems POST events to. Reveal it, then configure it in the sending system: ```sh theme={null} nuon triggers reveal-ingress-url ``` If a URL is ever leaked, rotate it: ```sh theme={null} nuon triggers replace-ingress-url ``` ## 3. Route events with rules Rules decide which events start which targets. Author them in your app config's `triggers.toml`, where each `[[rules]]` entry references a trigger by name, matches events, and declares a target. ```toml triggers.toml theme={null} [[rules]] name = "runbook-on-main-push" trigger = "my-trigger" event_types = ["push"] [[rules.filters]] from = "payload" op = "eq" path = "$.ref" value = "refs/heads/main" [rules.target] type = "runbook" runbook = "redeploy" install = "acme-prod" ``` This example targets a **runbook**, which runs on an existing install and requires no app branch. To run an app branch instead, use the `app_branch_run` target — see [Targets](#targets) below. ### Rule fields | Field | Required | Description | | ------------- | -------- | --------------------------------------------------------- | | `name` | yes | Unique name for the rule. | | `trigger` | yes | The name of the trigger this rule listens to. | | `event_types` | no | List of exact event type strings to match. | | `filters` | no | Payload or header predicates, ANDed together (max 20). | | `match_all` | no | Match every event. Cannot be combined with `event_types`. | | `target` | yes | The app branch run or runbook to start. | A rule must declare at least one of: `event_types`, a positive filter, or `match_all = true`. ### Filters Each filter compares a value from the payload or headers. ```toml triggers.toml theme={null} [[rules.filters]] from = "payload" # "payload" (default) or "headers" op = "eq" path = "$.action" # JSONPath for payload filters, or a header name for header filters value = "opened" ``` Supported `op` values: `eq`, `neq`, `in`, `prefix`, `suffix`, `contains`, `gt`, `gte`, `lt`, `lte`, `regex`, `exists`, `not_exists`. Omit `value` for `exists` and `not_exists`. Each filter's value must encode to at most 4096 bytes. To discover which payload paths and headers are available to filter on, use `nuon triggers events paths` against a received event — see [Test and inspect events](#4-test-and-inspect-events). ### Targets A target is either an **install runbook** or an **app branch run**. Run a runbook on a specific install, mapping payload values into runbook inputs. This target requires no app branch: ```toml triggers.toml theme={null} [rules.target] type = "runbook" runbook = "rotate-credentials" install = "acme-prod" [rules.target.inputs] requested_by = "$.sender.login" reason = "$.action" ``` Each entry under `inputs` maps a runbook input name to a singular payload JSONPath. The `runbook` must be declared in your app config, and `install` must name an existing install. Alternatively, run an app branch: ```toml triggers.toml theme={null} [rules.target] type = "app_branch_run" app_branch = "main" ``` The named `app_branch` must already be declared in your app config — the rule fails to sync if it doesn't exist. ## 4. Test and inspect events List received events for a trigger: ```sh theme={null} nuon triggers events list --trigger ``` Filter by type or outcome: ```sh theme={null} nuon triggers events list --trigger --event-type push --outcome failed ``` Inspect a single event, then replay it to re-run its routing: ```sh theme={null} nuon triggers events get nuon triggers events replay ``` Tail events live as they arrive: ```sh theme={null} nuon triggers events tail --trigger ``` Dry-run a local app config against a received event to see what would match, without dispatching. Pass the path to your app TOML with `--app-config`, and select the event with either `--last` (most recent event for a trigger) or `--event `: ```sh theme={null} nuon triggers events test --last --trigger --app-config ./app.toml nuon triggers events test --event --app-config ./app.toml ``` To discover which fields you can filter on, list the filterable payload paths and request headers for an event: ```sh theme={null} nuon triggers events paths --last --trigger nuon triggers events paths ``` ## 5. Inspect dispatches A dispatch is one target execution produced by a matching rule. List, inspect, and retry them: ```sh theme={null} nuon triggers dispatches list nuon triggers dispatches get nuon triggers dispatches retry ``` ## Rotating secrets and suspending Rotate a trigger's signing secret, or revoke a specific secret, without downtime: ```sh theme={null} nuon triggers rotate-secret nuon triggers revoke-secret ``` Suspend a trigger to stop processing events without deleting it, then re-enable it later: ```sh theme={null} nuon triggers disable nuon triggers enable ``` # Using READMEs Source: https://docs.nuon.co/guides/using-readmes Use READMEs to support operating Installs. Define a README on your app; Nuon renders it on each install's **Overview**. This guide covers authoring and formatting. For per-install variables and live data components, see [Programmable READMEs](/guides/programmable-readmes). ## Adding a README to an app Set the `readme` field on your app: ```toml metadata.toml theme={null} # metadata name = "My App" readme = "This is the README for My App." ``` Sync, and it renders on each install's Overview. ## Formatting The `readme` is a string, rendered as Markdown in the dashboard. Use TOML multi-line strings for readability: ```toml metadata.toml theme={null} # metadata name = "My App" readme = """ # My App This is the README for My App. ## Sandbox This app uses the nuonco/aws-eks-sandbox. ## Components This app consists of the following components. | Name | Type | |------|------| | api | container_image | | deployment | helm_chart | """ ``` Markdown accepts HTML, so HTML/CSS/JS works too, but not all of it renders reliably in the dashboard. Proceed with caution. ## Reference a README.md file Reference a `README.md` file instead of inlining. It's easier to manage for longer documents: ```toml metadata.toml theme={null} # metadata version = "v2" description = "Grafana App Config" display_name = "Grafana App Config" readme = "./README.md" ``` ## Tables Markdown tables render as interactive data tables with styled headers, click-to-sort columns, and horizontal auto-scroll when the content is wider than the page. ```markdown README.md theme={null} | Component | Type | Port | Health check | |-----------|------|------|--------------| | api-server | container_image | 8080 | /healthz | | worker | container_image | — | — | | ingress-nginx | helm_chart | 443 | /ready | | networking | terraform_module | — | — | ``` Cell content is full inline markdown, so links, **bold**, and `inline code` render inside cells. Column alignment from the delimiter row (`:---`, `:---:`, `---:`) is preserved. ### Searchable tables Add a `` tag directly before a table to render a search box that filters the rows client-side as the viewer types. ```markdown README.md theme={null} | Name | Status | Region | |------|--------|--------| | prod-us-east | Active | us-east-1 | | prod-eu-west | Active | eu-west-1 | | staging-us-west | Updating | us-west-2 | | dev-central | Inactive | us-central1 | ``` The tag must be the line immediately before the table (blank lines between are fine). Matching is case-insensitive substring, and searches the value of each cell — not its rendered markdown. Attributes: * `column` — which column(s) to search, by header name. Comma-separate for multiple (e.g. `column="name,region"`). Omit to search every column. * `placeholder` — custom placeholder text for the search box (defaults to "Search table…"). The same marker works on raw HTML `` markup. HTML tables render through the same common table component as markdown tables (sortable headers, shared styling), and a `` marker before one adds the search box: ```markdown README.md theme={null}
MonitorStatus
api-healthcheckfinished
db-healthcheckerror
``` Tables that use `colspan`/`rowspan` or multiple header rows can't be represented this way, so they render as authored (with any custom markup preserved). ## Collapsible sections Use HTML `
` and `` tags to create expandable sections. These render with styled expand/collapse behavior, including a rotate animation on the chevron icon. ````markdown README.md theme={null}
Troubleshooting: pod stuck in CrashLoopBackOff 1. Check the pod logs for the failing container: ```bash kubectl logs -n {{.nuon.install.name}} deploy/api-server --previous ``` 2. Verify the config map has the correct values: ```bash kubectl get configmap app-config -n {{.nuon.install.name}} -o yaml ``` 3. If the issue persists, re-run the **deploy components** action.
```` You can include any Markdown inside a collapsible section: lists, code blocks, tables, and even nested `
`. ## Code blocks Fenced code blocks with a language identifier get syntax highlighting. ````markdown README.md theme={null} ```bash nuon installs deploy --install-id {{.nuon.install.id}} --component api-server ``` ```go func healthCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } ``` ```` Supported languages include `bash`, `go`, `typescript`, `python`, `hcl`, `yaml`, `sql`, and many more. JSON code blocks render as an interactive tree viewer that you can expand and collapse: ````markdown README.md theme={null} ```json { "cluster": "eks-production", "region": "us-west-2", "node_pools": [ { "name": "default", "instance_type": "m5.xlarge", "min": 2, "max": 10 } ] } ``` ```` ## Mermaid diagrams Code blocks with the `mermaid` language render as diagrams. **Flowcharts** (`graph TD`, `flowchart LR`, etc.) render as interactive diagrams with pan, zoom, and drag via ReactFlow. Supported directions: `TD`, `TB`, `LR`, `RL`, `BT`. ````markdown README.md theme={null} ```mermaid graph TD A[networking] --> B[ingress-nginx] A --> C[api-server] B --> C C --> D[worker] ``` ```` Flowcharts support subgraphs, edge labels, node shapes, and custom styling via `style` directives. **All other diagram types** (sequence, class, state, etc.) render as static SVG: ````markdown README.md theme={null} ```mermaid sequenceDiagram participant Client participant API as api-server participant Worker Client->>API: POST /jobs API->>Worker: enqueue(job) Worker-->>API: job.complete API-->>Client: 200 OK ``` ```` ## Callouts READMEs support GitHub-style callout blockquotes. These render with a colored left border, icon, and label. ```markdown README.md theme={null} > [!NOTE] > Highlights information that users should take into account, even when skimming. > [!TIP] > Optional information to help a user be more successful. > [!IMPORTANT] > Crucial information necessary for users to succeed. > [!WARNING] > Critical content demanding immediate user attention due to potential risks. > [!CAUTION] > Negative potential consequences of an action. ``` Five types are supported: `NOTE` (blue), `TIP` (green), `IMPORTANT` (purple), `WARNING` (orange), and `CAUTION` (red). Regular blockquotes without a type prefix render normally. ## Local time Use the `` tag to render timestamps in the viewer's local timezone. ```markdown README.md theme={null} Last deployed Maintenance window: ``` Attributes: * `time` — ISO 8601 timestamp string * `seconds` — Unix timestamp (alternative to `time`) * `format` — `relative` (e.g. "2 hours ago"), `short-datetime`, `long-datetime`, `time-only` (defaults to `short-datetime`) The `relative` format auto-updates and shows a tooltip with the full date on hover. ## Display components READMEs support custom `` HTML tags that render as dashboard UI components. These are purely presentational and work in both app-level and install-level views. ### Badge Renders an inline badge. ```markdown README.md theme={null} healthy degraded ``` Attributes: * `theme` — `brand`, `default`, `neutral`, `success`, `warn`, `error`, `info` * `size` — `sm`, `md`, `lg` * `variant` — `default`, `code` ### Label badge Renders a key/value label badge — useful for tagging installs with metadata like environment, region, or version. ```markdown README.md theme={null} ``` You can pass the label as a single colon-separated `label` attribute, or as separate `key` and `value` attributes. Attributes: * `label` — colon-separated key:value string (e.g. `env:production`) * `key` — label key (alternative to `label`) * `value` — label value (alternative to `label`) * `theme` — `brand`, `default`, `neutral`, `success`, `warn`, `error`, `info` * `key-theme` — override the theme for just the key portion * `size` — `sm`, `md`, `lg` * `variant` — `default`, `code` The `theme` here only controls how a badge renders inside a README. To set the color a label key uses across the dashboard (on apps, installs, and components), see [label colors](/guides/managing-apps#label-colors). ### Banner Renders a callout banner for important notices. ```markdown README.md theme={null} This app requires a NAT gateway in the target VPC. Ensure one exists before creating an install. ``` Attributes: * `theme` — `brand`, `default`, `neutral`, `success`, `warn`, `error`, `info` ### Status Renders a status indicator dot with label. ```markdown README.md theme={null} ``` Attributes: * `status` — any string (e.g. `active`, `provisioning`, `error`) * `variant` — `default`, `badge`, `timeline` ### Group A flexbox layout container for arranging other elements. ```markdown README.md theme={null} v2.4.1 production us-west-2 ``` Attributes: `gap` (number), `align`, `justify`, `wrap` (`"true"` or `"false"`, defaults to true). ### Card Wraps content in a styled card container with border, padding, and shadow. ```markdown README.md theme={null} ## Quick reference | Variable | Default | Description | |----------|---------|-------------| | `REPLICAS` | `2` | Number of API server replicas | | `LOG_LEVEL` | `info` | Application log level | ``` Attributes: * `class` — optional CSS class name for custom styling ### Tabs Renders tabbed content sections. Wrap `` elements inside a `` block: ```markdown README.md theme={null} 1. Create an install from the dashboard 2. Run the **deploy components** action 3. Verify the health check endpoint returns 200 | Variable | Default | Description | |----------|---------|-------------| | `REPLICAS` | `2` | Number of API server replicas | | `LOG_LEVEL` | `info` | Application log level | ``` Each `` requires a `name` attribute. The content inside each tab is full markdown. ### Modal Renders a button that opens a modal dialog containing markdown content. ```markdown README.md theme={null} ## System architecture The application consists of three layers: 1. **Ingress** — NGINX handles TLS termination and routing 2. **API** — Go service processing requests 3. **Workers** — Async job processing via Temporal ``` Attributes: `heading`, `trigger` (button label, defaults to "View"), `size`. ### Panel Same as modal, but slides in from the side of the screen. ```markdown README.md theme={null} 1. Generate new credentials in the target cloud account 2. Update the install config with the new values 3. Re-run **deploy components** 4. Verify connectivity with the health check ``` Attributes: `heading`, `trigger` (button label, defaults to "View"), `size`. ## Render per-install values Everything above is static authoring. To render each install's live values and embed real-time components (status cards, the config graph, runnable runbooks), see [Programmable READMEs](/guides/programmable-readmes). # Using Variables Source: https://docs.nuon.co/guides/using-variables Using variables with your Nuon configurations. The Components of your App can get custom configuration settings applied by using variables. Each deployed Component instance can make use of a set of name/value variables to handle configuration settings that need to be set differently on each customer's instance. Nuon brings in a tree of information from the sources detailed below, which can then be accessed by the Component using a templating syntax inspired by Go templates. This variable replacement templating syntax is used by several other infrastructure management tools. You can set these variables as vars in your Terraform configuration. For example, if you wanted to set values in a Helm chart: ```toml components/helm_chart.toml theme={null} # helm name = "helm_chart" type = "helm_chart" chart_name = "" [connected_repo] directory = "components/helm-chart" repo = "/" branch = "main" [values] # manually set a variable "some-var-name" = "some-var-value" # reference another component "reference-to-other-component" = "{{.nuon.components.some_other_component.outputs.s3_bucket_name}}" ``` Example directly-set value would be | Name | Value | | ------ | ------ | | `port` | `3000` | Example dynamic variable reference would be | Name | Value | | ------------- | -------------------------------------------- | | `bucket_name` | `{{ .nuon.components.storage.bucket_name }}` | **Note** After changing variables, you must trigger a new build and release of that component before your changes will take affect. ## Same Variable Value on Every Install Settings common to all your installs can be set directly on the component and it will be the same across all installs. Example: `cache_size` with value `4096`. ## Unique Variable Value For Each Install For example, the install id can be passed as a variable to the Sandbox for an EKS cluster name. ```toml sandbox.toml theme={null} [vars] cluster_name = "n-{{.nuon.install.id}}" ``` ## Labels Templating also works in [install labels](/guides/install-configs#dynamic-labels) and app-level [default labels](/guides/managing-apps#default-labels). Label values re-render automatically as the install state changes. ## Variable Data Sources ### Nuon Information * **Nuon Organization ID** `{{ .nuon.org.id }}` * Unique identifier for the vendor organization * **Nuon Application ID** `{{ .nuon.app.id }}` * Unique identifier for the vendor application * **Nuon Install ID** `{{ .nuon.install.id }}` * **Nuon Install Sandbox Type** `{{ .nuon.install.sandbox.type }}` * Example: `aws-eks` * **Nuon Install Sandbox Version** `{{ .nuon.install.sandbox.version }}` * Example: `0.11.1` ### Nuon Install Sandbox Outputs See the [aws-eks-sandbox](https://github.com/nuonco/aws-eks-sandbox/blob/main/outputs.tf) repository for the most up-to-date list of outputs available from the Sandbox. ### Install Inputs Any app input can be accessed using variables. For instance, given an app input, `foo`, it can be accessed using: * `{{ .nuon.install.inputs.foo }}` ### Output from Other Components in an Application Some component types including terraform components will provide output values that will be required as input variables to other components of the application. These outputs are available grouped under the slug version of the component name. * Template Syntax: `{{ .nuon.components..outputs. }}` * Example: `{{ .nuon.components.rds_db.outputs.db_url }}` ### Component Image Data from Other Components in an Application * `{{ .nuon.components..image.tag }}` * Docker/OCI Image tag used for the component deployment * `{{ .nuon.components..image.repository }}` * ECR Repository containing this component's images * `{{ .nuon.components..image.registry }}` * ECR Registry containing this component's images # Connect GitHub Source: https://docs.nuon.co/guides/vcs Connect your GitHub account to use both public and private repositories in your Nuon apps. Connecting Nuon with your Version Control System (VCS) will give Nuon the ability to use your code when configuring your application. To use public repositories, you do not have to connect Nuon with your VCS. In this guide we'll use the [Nuon CLI](/cli) to connect your Nuon Org to a GitHub account or repository and verify the connection is working by listing the connected repositories. You may want to update an existing GitHub connection (e.g. add a new repository to the connection). To do this follow the steps below, make the updates with in the GitHub connection flow and your existing connection should be updated. ## Select an Org Because a VCS connection is scoped to your Nuon Org you'll need to login using the CLI and select the Org you want to use. ```sh theme={null} nuon auth login ``` ```sh theme={null} nuon orgs select ``` Alternatively if you already have an existing CLI session you can just verify the Org is correct before you start the connection flow. ```sh theme={null} nuon orgs current ``` ## Start the connection flow Now that you're logged in with a valid Org selected you'll want to kick off the GitHub connection flow with the CLI. ```sh theme={null} nuon orgs connect-github ``` This command will open a tab in your browser asking you to install [Nuon Connect](https://github.com/apps/nuon-connect) app into your GitHub account. ## Select GitHub access At this point you'll follow the GitHub connection flow, selecting an account to install Nuon Connect and selecting which repositories Nuon can have access to. Select GitHub account Select which GitHub account to want to install Nuon Connect Select repos Select which repos Nuon Connect can have access to After you've selected which repositories Nuon Connect can access you'll be redirected to the [Nuon web app](https://app.nuon.co). If the connection was a success you'll see either a success message or you'll be redirected back to the [Nuon web app](https://app.nuon.co) if you have an active web session. ## Verify connection works At this point you can close the browser tab and verify everything is correct in the terminal via the CLI. ```sh theme={null} nuon orgs list-vcs-connections ``` If the connection was successful you should see a list on connected GitHub accounts in Nuon. ## Use Connected Repositories in Nuon In a Component configuration file, you must use the `[connected_repo]` table syntax to reference a connected repository. For example: ```toml components/my-component.toml theme={null} # helm name = "my-private-component" type = "helm_chart" chart_name = "my-private-chart" namespace = "my-namespace" storage_driver = "configmap" [connected_repo] repo = "acmeco/my-app" directory = "chart" branch = "main" ``` If the repository is public, you must use the `[public_repo]` table syntax instead. If you use `[public_repo]` and the repository is private, Nuon will not be able to access it and will throw an error when you try to deploy the App. # Positioning Safety of BYOC Source: https://docs.nuon.co/guides/vendor-customers Learn how to position Nuon's security and user experience to customers. Bring Your Own Cloud (BYOC), or allowing vendors to install and manage their software in their customers' cloud accounts, introduces scrutiny and questions from those customers. This is reasonable and to be expected since BYOC is a newer software deployment paradigm and allowing an external party to do anything in a customer cloud should be managed carefully. For a technical overview of all security properties, see the [Security](/security) page. ### Secrets and Sensitive Values In Nuon, secrets and sensitive values are entered by the customer, not the vendor, when they deploy the [Stack](/concepts/stacks). They are then stored in AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager and are retrieved programmatically by the Nuon [Runner](/concepts/runners) in the customer cloud account to insert into [components](/concepts/components) as the vendor's software is provisioned or updated. Nuon can automatically sync secrets to a Kubernetes cluster deployed by the Runner, which can then be referenced in components like Helm charts, Kubernetes manifests, and Terraform modules. The vendor by default cannot access the secrets unless the customer happened to approve IAM roles with elevated privileges to run action scripts to view the secrets. ### Log Viewing and Exfiltration Prevention By default, Nuon only stores logs of the infrastructure changing in the customer account, e.g., creating a Kubernetes cluster, deploying a Helm chart, running Terraform. Application logs are not accessible or sent to the Nuon control plane. Customers can independently [export runner audit logs](/guides/export-runner-audit-logs) from AWS, Azure, or GCP to an OTLP-compatible backend they control. The destination and credentials remain in the customer's cloud secret manager; they are not sent to the Nuon control plane. One of Nuon's concepts is an [action](/concepts/actions), which are vendor-defined scripts to deploy infrastructure but are also useful in troubleshooting and debugging. Outputs of actions are stored in the Nuon control plane. Actions are defined by IAM roles and permissions that vendor's customers would approve beforehand. If those roles were elevated enough to execute commands that can access sensitive data like a database table with `psql` or view a Kubernetes secret with `kubectl`, the outputs would be stored in the Nuon control plane. ### Customer Inputs [Inputs](/concepts/app-inputs) are dynamic values specific to an install that are entered and then referenced into the install provision workflow. With Nuon, inputs can be configured to be vendor-entered or customer-entered. Vendor inputs may be the Kubernetes and vendor app release versions or the compute node size. Customer inputs could be an AI or vendor license key. Customers enter inputs in the customer dashboard and can be updated over time. Vendors conversely enter their inputs either in the vendor dashboard or with the Nuon CLI. ### Customer Cloud Permissions When customers deploy the vendor's application through Nuon, they run a [Stack](/concepts/stacks) in their own cloud account that creates the IAM roles and policies defining what Nuon's Runner can do. The Runner is a lightweight agent deployed entirely within their cloud account that connects outbound-only to Nuon's control plane. No VPN tunnels, no cross-account access, and no inbound connections required. The vendor's app config defines three scoped IAM roles with different permission levels: provision (for initial setup), deprovision (for removal, automatically disabled after use), and maintenance (for ongoing updates and monitoring). Customers control these permissions directly through the Stack and can toggle them on or off at any time. For emergency access, break-glass permissions work the same way. Customers grant or revoke elevated access by updating parameters in their Stack, giving them complete control over when emergency permissions are enabled. All Runner actions are logged for complete audit visibility. This architecture ensures the vendor's software can be deployed and managed in the customers' environment without ever exposing their cloud credentials to external parties. ### Nuon Deployment Options Nuon Cloud is our SaaS offering primarily targeted for free trials and smaller customers. Vendors' apps, installs, and logs are stored in an encrypted PostgreSQL database and securely accessed by the vendors and customers using TLS for encryption of data in transit and Auth0 for authentication. For vendors and their customers concerned about data residency and multiple SaaS 3rd parties in the loop, Nuon supports self-hosting Nuon's control plane in the vendor's AWS cloud. Nuon uses its BYOC product to provision and update Nuon. In this deployment scenario, the metadata and logs about customer installs are stored encrypted in PostgreSQL RDS in the vendor's AWS cloud. The vendor can cut off access of the Nuon Runner to the Nuon control plane except as required during maintenance windows to upgrade the Nuon control plane. This offering is called Nuon BYOC. Nuon also supports fully self-hosted deployments where the vendor can deploy Nuon using Helm charts into their own cloud account. For a technical overview of all these properties, see the [Security](/security) page. # Webhooks Source: https://docs.nuon.co/guides/webhooks Receive workflow lifecycle events from your Nuon Org as CloudEvents over HTTP. Org-scoped webhooks let you subscribe to **workflow** and **workflow step** lifecycle events as well as operational notifications for your Nuon [Org](/concepts/glossary). Whenever a subscribed event occurs, Nuon `POST`s a [CloudEvents v1.0](https://cloudevents.io) envelope to every matching webhook URL registered on the Org. Webhooks are scoped to the current Org. Manage them with the [Nuon CLI](/cli) or the dashboard. ## Manage webhooks ```sh theme={null} # List webhooks for the current Org nuon orgs webhooks list # Create a webhook (URL is required, secret is optional) nuon orgs webhooks create --url https://example.com/webhooks/workflow/lifecycle --secret # Update a webhook's subscription (interests + match) and/or rotate its signing secret nuon orgs webhooks update --webhook-id --subscription-file ./subscription.json nuon orgs webhooks update --webhook-id --secret # Delete a webhook nuon orgs webhooks delete --webhook-id ``` A few constraints to be aware of: * The `--url` must be an absolute `http` or `https` URL with a host. * Webhook URLs are unique per Org **per scope**. The uniqueness key is `(org_id, webhook_url, match)` — the same URL can be registered multiple times in one Org as long as each registration uses a different [match predicate](#scoping-deliveries-with-match) (for example, once for "all installs" and again for "components in env=prod"). Registering the exact same `(url, match)` twice returns a conflict. * The `--secret` is write-only. The API never returns it; responses include `has_secret: true|false` so you can tell whether one is configured. * The webhook URL is part of the `(org_id, webhook_url, match)` unique index and cannot be changed in place. To rename it, delete the webhook and create a new one. Use the dashboard's edit action (or `PATCH /v1/orgs/current/webhooks/{webhook_id}`) to rotate the signing secret or replace the [interests filter](#filtering-events-with-interests) / [match predicate](#scoping-deliveries-with-match) without recreating the webhook. ## Filtering events with interests By default a new webhook receives **every** supported workflow, deploy, sandbox, runner, and approval event for the Org. To narrow that down, attach an `interests` filter on create or update. The filter is a structured object stored on each webhook and persisted as JSONB on the webhook row. If you omit `interests` entirely (and on legacy webhooks created before this filter shipped), Nuon treats the configuration as `{"all_events": true}` so deliveries don't silently stop. The list endpoint surfaces this same effective shape so the CLI/dashboard/SDK stay in sync with what's actually delivered. ### Top-level shape A configuration is one of two shapes: ```json theme={null} { "all_events": true } ``` ```json theme={null} { "resources": { "installs": { "outcome": "completion", "approval_requests": true, "approval_responses": true }, "components": { "ops": ["deploy"], "outcome": "all", "drift_detected": true }, "sandboxes": { "outcome": "failures", "drift_detected": true }, "install_configurations": {}, "runners": { "ops": ["provision"] } } } ``` `all_events: true` is the new-subscription default and short-circuits everything else. To opt into a per-resource configuration, omit `all_events` (or set it to `false`) and populate `resources`. A resource missing from `resources` is **off** — no events of that kind are delivered. ### Resource vocabulary Each resource accepts a list of sub-ops drawn from a fixed vocabulary: | Resource | Supported `ops` | | ------------------------ | --------------------------------------------------- | | `installs` | `provision`, `deprovision`, `reprovision` | | `stacks` | `version_active` | | `components` | `deploy`, `teardown` | | `sandboxes` | `provision`, `reprovision`, `deprovision` | | `install_configurations` | `inputs`, `secrets` | | `runners` | `provision`, `reprovision`, `inactive`, `unhealthy` | | `actions` | `run` | Drift workflow lifecycle events (`drift_run`, `drift_run_reprovision_sandbox`) are intentionally **not** part of the `ops` vocabulary — every cron tick fires a started/completed pair, which is pure noise on a clean scan. To subscribe to drift, set `drift_detected: true` on `components` and/or `sandboxes` (see below). ### Per-resource fields | Field | Type | Default | Description | | -------------------- | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ops` | `[]string` | `[]` | Sub-ops to subscribe to. An empty (or omitted) list means **every sub-op** for the resource. | | `outcome` | `string` | `"all"` | One of `all`, `completion`, `failures`, or `none`. Filters lifecycle events on terminal status; `none` mutes lifecycle for this resource entirely. | | `approval_requests` | `bool` | `false` | Deliver workflow-step approval **requests** for this resource. | | `approval_responses` | `bool` | `false` | Deliver workflow-step approval **responses** (approved / rejected) for this resource. | | `drift_detected` | `bool` | `false` | Deliver a notification **only when drift is actually detected** during a drift scan. Only meaningful for `components` and `sandboxes`. | | `component_health` | `bool` | `false` | Deliver a notification when a component's live health crosses into degraded or unhealthy, **and when it recovers**. Only meaningful for `components`. | | `install_degraded` | `bool` | `false` | Deliver a notification when the install's composite health crosses into degraded or unhealthy, **and when it returns to healthy**. Only meaningful for `installs`. | `outcome` semantics: * `all` — every `started` + terminal event for the matched ops. * `completion` — terminal events only (`succeeded`, `failed`, `cancelled`); suppress `started`. * `failures` — only `failed` and `cancelled` terminal events. * `none` — mutes lifecycle events entirely for this resource. Drift and approval events are gated independently and remain functional, so `none` is the right choice when you want a drift-only or approvals-only subscription for this resource. Approval events do not have a started/succeeded/failed lifecycle, only a requested / approved / rejected handshake. They are gated **independently of `outcome`** by the `approval_requests` and `approval_responses` booleans. #### Drift detection To receive drift notifications, set `drift_detected: true` on `components` and/or `sandboxes`. The notification fires once per component (inside `drift_run`) or once per sandbox (inside `drift_run_reprovision_sandbox`) whenever the plan-only check finds non-no-op changes. It works for both manually triggered and scheduled drift scans, and is gated **independently of `outcome`** — the same way `approval_requests` / `approval_responses` are. #### Component health To receive component health notifications, set `component_health: true` on `components`. One flag covers both directions: the notification fires when a component's live health crosses into `degraded` or `unhealthy`, and again when it recovers, so you always get the resolution to a failure you were told about. A verdict of `unknown` — the runner is offline or its observations are stale — never notifies; unhealthy runners have their own event, below. A worsening inside the bad band (`degraded` to `unhealthy`) does not re-notify. Like `drift_detected`, the flag is gated **independently of `outcome`**. Set `install_degraded: true` on `installs` for the same signal one level up: it fires when the install's composite health crosses into degraded or unhealthy, and when it returns to healthy. Subscribing to both levels means a single-component outage produces two messages. #### Unhealthy runners Runners emit `runners.unhealthy` when the 15-minute runner health check first transitions a runner from `active` to `offline` because its primary process is missing. Subsequent checks do not emit another notification while the runner remains offline. If the runner recovers and later transitions from `active` to `offline` again, a new notification is emitted. This operation is separate from process-level `runners.inactive` events, which can occur during routine process shutdown and rotation. Subscribe by including `unhealthy` in the `runners` `ops` list (or by leaving `ops` empty to receive every runner sub-op). ### Examples Subscribe to every supported event (this is the default if `interests` is omitted): ```json theme={null} { "all_events": true } ``` Per-resource opt-in matching the dashboard's "opted-out-of-AllEvents" baseline — terminal events plus approval notifications for the four most common resources: ```json theme={null} { "resources": { "installs": { "outcome": "completion", "approval_requests": true, "approval_responses": true }, "stacks": { "outcome": "completion" }, "components": { "outcome": "completion", "approval_requests": true, "approval_responses": true, "drift_detected": true }, "sandboxes": { "outcome": "completion", "approval_requests": true, "approval_responses": true, "drift_detected": true }, "install_configurations": { "outcome": "completion", "approval_requests": true, "approval_responses": true } } } ``` Narrowly scoped — only component deploy failures: ```json theme={null} { "resources": { "components": { "ops": ["deploy"], "outcome": "failures" } } } ``` Drift only — components, no lifecycle, no approvals: ```json theme={null} { "resources": { "components": { "outcome": "none", "drift_detected": true } } } ``` ### Setting interests via the CLI `nuon orgs webhooks create` and `nuon orgs webhooks update` take the full subscription — both the [interests filter](#filtering-events-with-interests) and the optional [match predicate](#scoping-deliveries-with-match) — as a single JSON document via `--subscription-json` (inline) or `--subscription-file` (path on disk). The two flags are mutually exclusive. Omit both to fall back to the default: every supported event in the Org, no scope. ```sh theme={null} # Default — subscribe to every supported event in the Org nuon orgs webhooks create --url https://example.com/hook # Inline JSON (interests only) nuon orgs webhooks create --url https://example.com/hook \ --subscription-json '{"interests":{"resources":{"components":{"ops":["deploy"],"outcome":"failures"}}}}' # Inline JSON (interests + match — scope to two specific installs) nuon orgs webhooks create --url https://example.com/hook \ --subscription-json '{"interests":{"all_events":true},"match":{"installs":{"ids":["ins_abc","ins_def"]}}}' # From a file (recommended for non-trivial configs) nuon orgs webhooks create --url https://example.com/hook --subscription-file ./subscription.json # Replace the subscription on an existing webhook nuon orgs webhooks update --webhook-id --subscription-file ./subscription.json ``` The wire shape is `{"interests": {...}, "match": {...}}` — both keys are optional. A missing `interests` defaults to `{"all_events": true}` so you can scope a webhook with `--subscription-json '{"match":{...}}'` without restating the events filter. A missing or `null` `match` is the org-wide ("every entity") subscription. `update` replaces both `interests` and `match` wholesale — it is not a deep merge. Pass `--secret ` to rotate the signing secret at the same time; omit `--secret` to leave the existing secret unchanged. The webhook URL plus its `match` predicate together form the `(org_id, webhook_url, match)` unique index — delete and recreate to rename, or register a sibling row with a different `match` to deliver the same URL with a different scope. ### Setting interests via the dashboard The dashboard's webhook create + edit forms expose the [interests filter](#filtering-events-with-interests) as an inline picker. Toggling **Send all events** off materializes the per-resource baseline shown above so you land on a sensible starting point instead of an empty config that silently drops every event. Saving the form persists the same JSON shape documented here. > **Match in the dashboard.** The dashboard's webhook forms do not yet expose the > [match predicate](#scoping-deliveries-with-match), and saving from the dashboard resets the webhook to org-wide. > To create or preserve a scoped webhook, manage it from the [CLI](#setting-interests-via-the-cli). ## Scoping deliveries with match By default a webhook is **org-wide** — it fires for every event the [interests filter](#filtering-events-with-interests) opts into, regardless of which install, component, or action triggered it. Attach a `match` predicate to scope deliveries to specific entities or to entities carrying specific labels. `match` is an object with up to three optional kinds — `installs`, `components`, `actions`. For each kind, list the `ids` you care about and/or a label `selector`: ```json theme={null} { "match": { "installs": { "ids": ["ins_abc", "ins_def"] }, "components": { "selector": { "match_labels": { "env": "prod" } } } } } ``` How matching works: * An event is delivered if **any** populated kind matches. * Within a kind, the entity matches if its ID is in `ids` **or** its labels satisfy `selector`. * A `selector` requires every `match_labels` entry to match (AND); use `"*"` as the value to require only that the key is present. * An empty filter (`{}`) for a kind means "any entity of this kind". * Omit `match` entirely (or send `null`) for the org-wide default. ### Examples Every event, but only for two specific installs: ```json theme={null} { "interests": { "all_events": true }, "match": { "installs": { "ids": ["ins_abc123", "ins_def456"] } } } ``` Drift-only on components labelled `env=prod`: ```json theme={null} { "interests": { "resources": { "components": { "outcome": "none", "drift_detected": true } } }, "match": { "components": { "selector": { "match_labels": { "env": "prod" } } } } } ``` The same URL can be registered multiple times in one Org with different `match` predicates — for example, a noisy "all events" subscription to a staging endpoint plus a focused "drift only on env=prod components" subscription to a paging endpoint. ## Event types The webhook surface exposes two workflow lifecycle primitives plus dedicated notification event types: 1. **Workflow lifecycle** (`com.nuon.workflow.lifecycle.v1`) — fires for the workflow as a whole. Carries the workflow's `type` (e.g. `provision`, `reprovision`, `manual_deploy`, `action_workflow_run`) and its owner (`installs`, `apps`, `app_branches`). 2. **Workflow step lifecycle** (`com.nuon.workflow_step.lifecycle.v1`) — fires for each step within a workflow. Carries the step's `name`, `idx`, and `target_type` (e.g. `install_deploys`, `install_sandbox_runs`, `install_action_workflow_runs`) along with denormalized `component_id` / `sandbox_id` when applicable. 3. **Runner unhealthy** (`com.nuon.runner.unhealthy.v1`) — fires once when the runner health check transitions a runner from `active` to `offline`. Carries `kind: runner_unhealthy`, `transition: unhealthy`, runner and runner-group metadata, the reason, and install context when the runner belongs to an install. You do not need to memorize an operation taxonomy. To know "did the sandbox finish provisioning?", look at workflow events with `data.workflow.type == "provision"` and a step whose `target_type == "install_sandbox_runs"` succeeded. To know "did this component deploy?", look at a step whose `target_type == "install_deploys"` and inspect `data.step.component_id`. An `owner_type` of `app_branches` means the workflow is an [app branch](/concepts/app-branches) run: a rollout across a fleet of installs rather than work on a single install. Its step names include `plan install group: ` and `deploy install group: `, one pair per deployment group, so you can watch a staged rollout progress and drive the next approval through the API. See [configuring app branches](/guides/app-branches). ## Transitions Both primitives use the same transition vocabulary: | Transition | When it fires | | ----------- | -------------------------------------------------- | | `started` | The workflow / step begins execution | | `succeeded` | Execution completed successfully | | `failed` | Execution errored or validation rejected the input | | `cancelled` | Execution was cancelled | | `unhealthy` | A runner transitioned from `active` to `offline` | `data.transition` is the transition name. `data.outcome.status` mirrors it on `*.succeeded`, `*.failed`, and `*.cancelled` events. Validation failures of the workflow / step wrappers surface as a `failed` outcome on the following execute event — there is no separate validate event. A typical Install provision produces, in order: 1. `workflow.lifecycle started` for the provision workflow. 2. For each step in the workflow: `workflow_step.lifecycle started` → `workflow_step.lifecycle succeeded` (or `failed` / `cancelled`). 3. `workflow.lifecycle succeeded` for the provision workflow once every step terminates. ## Delivery guarantees and failures Nuon webhooks are best-effort notifications, not a durable event stream. * A final `2xx` response is required for a delivery to succeed. * Nuon waits up to 5 seconds for a response. Endpoints should acknowledge promptly and perform long-running processing asynchronously. * Network errors, timeouts, and non-`2xx` responses are treated as delivery failures. Nuon does not retry these failures or provide delivery history or replay. * A failed webhook does not interrupt the workflow that produced the event or prevent delivery attempts to other webhook subscriptions. * Rare infrastructure interruptions can result in duplicate deliveries when Nuon cannot determine whether an earlier request completed. A duplicate logical event may have a different CloudEvents `id`. Make webhook handlers idempotent. Persist or enqueue the event before returning a `2xx` response, and derive an idempotency key from the relevant workflow, step, resource, and transition fields rather than relying only on the CloudEvents `id`. ## Payload format The body is a CloudEvents v1.0 JSON envelope sent with `Content-Type: application/cloudevents+json; charset=utf-8`. Nuon-specific extension attributes (`nuonorgid`, `nuonkind`, `nuontransition`) are mirrored on the envelope for routing. ### Workflow lifecycle event ```json theme={null} { "specversion": "1.0", "id": "9f6c5b4e-…", "type": "com.nuon.workflow.lifecycle.v1", "source": "//nuon.co/ctl-api", "time": "2026-04-28T12:34:56Z", "subject": "org_…/workflow/inwYY…/started", "datacontenttype": "application/json", "nuonorgid": "org_…", "nuonkind": "workflow", "nuontransition": "started", "interests": [ "resource:installs", "op:installs.provision", "event:lifecycle.started" ], "data": { "kind": "workflow", "transition": "started", "org_id": "org_…", "workflow": { "id": "inwYY…", "type": "provision", "owner_id": "ins_…", "owner_type": "installs" }, "links": { "org": "https://app.nuon.co/org_…", "install": "https://app.nuon.co/org_…/installs/ins_…", "workflow": "https://app.nuon.co/org_…/installs/ins_…/workflows/inwYY…" } } } ``` ### Workflow step lifecycle event ```json theme={null} { "specversion": "1.0", "id": "3487f2f5-…", "type": "com.nuon.workflow_step.lifecycle.v1", "source": "//nuon.co/ctl-api", "time": "2026-04-28T12:35:42Z", "subject": "org_…/workflow_step/inwYY…/iws_…/succeeded", "datacontenttype": "application/json", "nuonorgid": "org_…", "nuonkind": "workflow_step", "nuontransition": "succeeded", "interests": [ "resource:components", "op:components.deploy", "event:lifecycle.succeeded", "outcome:completion" ], "data": { "kind": "workflow_step", "transition": "succeeded", "org_id": "org_…", "workflow": { "id": "inwYY…", "type": "provision", "owner_id": "ins_…", "owner_type": "installs" }, "step": { "id": "iws_…", "name": "deploy api (apply)", "idx": 7, "target_type": "install_deploys", "target_id": "ind_…", "component_id": "cmp_…", "execution_type": "system" }, "outcome": { "status": "succeeded", "duration_ms": 12345 }, "links": { "org": "https://app.nuon.co/org_…", "install": "https://app.nuon.co/org_…/installs/ins_…", "workflow": "https://app.nuon.co/org_…/installs/ins_…/workflows/inwYY…", "component": "https://app.nuon.co/org_…/installs/ins_…/components/cmp_…" } } } ``` ### Nested workflows: `parent` When a workflow is launched from another workflow's step (for example, an action workflow run launched from a deploy step), the child workflow's events include a `data.parent` block: ```json theme={null} "parent": { "workflow_id": "inwParent…", "step_id": "iwsParentStep…", "kind": "workflow_step" } ``` `parent` is omitted for top-level workflows. ## Field reference | Field | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `nuonorgid` | Org id this event belongs to. Nuon CloudEvents extension; mirrors `data.org_id`. | | `nuonkind` | `workflow` or `workflow_step`. Mirrors `data.kind`. | | `nuontransition` | `started`, `succeeded`, `failed`, or `cancelled`. Mirrors `data.transition`. | | `interests` | Slug list produced by Nuon's classifier for this event. Always contains a `resource:` and `op:.` slug, plus an `event:<…>` slug identifying the transition (e.g. `event:lifecycle.succeeded`, `event:approval.request`, `event:drift.detected`, `event:component.unhealthy`, `event:component.recovered`, `event:install.degraded`) and zero or more `outcome:<…>` slugs (`outcome:completion` on terminal events, plus `outcome:failures` on failed or cancelled transitions). Consumers can route by slug prefix without re-implementing the classifier. | | `data.kind` | `workflow` or `workflow_step`. | | `data.transition` | `started`, `succeeded`, `failed`, or `cancelled`. | | `data.org_id` | Org id. | | `data.workflow.id` | Workflow id. Stable across all events for the workflow. | | `data.workflow.type` | Workflow kind (`provision`, `reprovision`, `manual_deploy`, `action_workflow_run`, etc.). | | `data.workflow.owner_id` | Id of the entity that owns the workflow (an install, app, or app branch). | | `data.workflow.owner_type` | One of `installs`, `apps`, `app_branches`. | | `data.step.id` | Workflow step id (workflow\_step events only). | | `data.step.name` | Human-readable step name (for example, `"deploy api (apply)"`). | | `data.step.idx` | Step index within the workflow. | | `data.step.target_type` | The kind of resource the step manipulates: `install_deploys`, `install_sandbox_runs`, etc. | | `data.step.target_id` | Id of the manipulated resource. | | `data.step.component_id` | Component id when `target_type == "install_deploys"`. | | `data.step.sandbox_id` | Sandbox id when `target_type == "install_sandbox_runs"`. | | `data.step.execution_type` | `system`, `user`, `approval`, `skipped`, or `hidden`. | | `data.parent` | Present when this workflow was launched from another workflow's step. See above. | | `data.outcome.status` | Mirrors `data.transition` on terminal events. Omitted on `started`. | | `data.outcome.error` | Human-readable error message. Set only on `failed` events. | | `data.outcome.duration_ms` | How long the workflow / step took to run, in milliseconds. | | `data.links` | Dashboard URLs for the org, install, workflow, sandbox, and component (when applicable). | ## Verifying signatures When a webhook is created with `--secret`, Nuon signs the raw request body with HMAC-SHA256 using your secret and sends the lowercase hex digest in the `X-Nuon-Signature` header. Reject any request whose signature does not match. When no secret is configured, no signature header is sent. ## End-to-end example 1. Start your receiver locally and expose it (for example with `ngrok http 8080`). 2. Register the webhook with your Org: ```sh theme={null} nuon orgs webhooks create \ --url https:///webhooks/workflow/lifecycle \ --secret $NUON_WEBHOOK_SECRET ``` 3. Trigger a workflow. The smallest reliable trigger is reprovisioning a Sandbox: ```sh theme={null} nuon installs reprovision-sandbox --skip-components ``` 4. Your receiver should record one `workflow.lifecycle.started`, a sequence of `workflow_step.lifecycle.*` deliveries (one started + one terminal per step), and finally a `workflow.lifecycle.succeeded` (or `failed` / `cancelled`). A `401` response from your receiver, such as when the secrets do not match, is a delivery failure. See [Delivery guarantees and failures](#delivery-guarantees-and-failures). 5. When you are done, remove the webhook: ```sh theme={null} nuon orgs webhooks list nuon orgs webhooks delete --webhook-id ``` # API Source: https://docs.nuon.co/nuon-api Get started using the Nuon API endpoints. You'll need an access token to use the Nuon API. Create an [API token](/concepts/api-tokens) from the Nuon [Dashboard][1] or the Nuon CLI. Tokens are shown only once, so store them somewhere safe. ```sh theme={null} nuon login nuon orgs api-tokens create --name my-token ``` For automation and CI/CD, we recommend using [OIDC Federation](/concepts/oidc-federation) to avoid storing long-lived tokens. ## Using the Swagger API Reference You can call the Nuon API endpoints from the API Reference by visiting the [Swagger API Reference](https://api.nuon.co/docs/index.html) ## Using the API You can call the Nuon API endpoints from the API Reference by adding `Bearer ` to the "Authorization" field in each endpoint card. Verify your API token with the [current-user](/api-ref/general/get-current-user) endpoint. [1]: https://app.nuon.co # AWS Source: https://docs.nuon.co/platform-support/aws Deploy and manage BYOC apps on AWS. ## Configuration To deploy an app to AWS, configure it to use an AWS sandbox and runner. We provide a minimal sandbox you can use to get started. You will need to set some vars to configure the sandbox. Create inputs for the public and private domains, since those will be different for each install. You will also need to set some IAM roles to grant the runner access to the EKS cluster. ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/aws-eks-karpenter-sandbox" branch = "main" [vars] cluster_version = "1.33" cluster_name = "n-{{.nuon.install.id}}" public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" [[var_file]] contents = "./sandbox.tfvars" ``` ```toml sandbox.tfvars theme={null} maintenance_role_eks_access_entry_policy_associations = { eks_admin = { policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy" access_scope = { type = "cluster" } } eks_view = { policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" access_scope = { type = "cluster" } } } ``` ```toml runner.toml theme={null} # runner runner_type = "aws" helm_driver = "configmap" ``` ## Installation When you create a new install, Nuon generates the install stack your customer uses to provision the runner and base infrastructure in their AWS account. Two stack formats are produced for every AWS install, and your customer can use whichever fits their tooling: * **CloudFormation** — a generated Cloudformation template, installable via a Quick-Create URL or the AWS CLI. * **Terraform** — generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files for the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) Terraform module, applied with the standard `terraform` CLI. Both formats describe the same set of resources (IAM operation roles, secrets, and trust to the Nuon control plane), so installs end up in the same state regardless of which one your customer runs. ### Create an Install Navigate to the Installs tab in the Nuon dashboard, click "Create Install", and select the app you want to install. This kicks off a provision workflow. Both the CloudFormation stack and the Terraform tfvars are templated with the install ID, region, and other settings. The provision workflow's "await install stack" step exposes both via tabs ("CloudFormation" and "Terraform"), which can be shared with your customer. ### Install the Stack Your customer must log into the AWS account they want to install your app in, and ensure they have the required permissions. #### Option 1: CloudFormation Either: * Open the **Quick-Create URL** in a browser and complete the web flow, or * Run the provided **AWS CLI** `create-stack` snippet ([install the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) first). #### Option 2: Terraform From the Terraform tab in the dashboard: 1. Download the generated `inputs.auto.tfvars` and `secrets.auto.tfvars` files. 2. Create a `backend.tf` to store Terraform state (an S3 snippet is provided in the dashboard). 3. Run `terraform init && terraform apply` against the [`install-stacks/aws`](https://github.com/nuonco/install-stacks) module. Both `.auto.tfvars` files are loaded automatically. Custom resources from CloudFormation nested stacks are not translated automatically. If you extend the install stack with custom Cloudformation resources, fork [`install-stacks`](https://github.com/nuonco/install-stacks) and make the equivalent Terraform changes there. ## Updating If you make changes to the install stack, the install must be reprovisioned. 1. From the install's Overview page, click "Reprovision install" in the "Manage" drop-down. This triggers a reprovision workflow. 2. A new CloudFormation template and Terraform tfvars are generated. 3. Send the updated Quick-Create URL / CLI command, or the new `inputs.auto.tfvars` and `secrets.auto.tfvars` files, to your customer. 4. Customers using Terraform re-run `terraform apply` against the same backend. # Azure Source: https://docs.nuon.co/platform-support/azure Deploy and manage BYOC apps on Microsoft Azure. ## Configuration To deploy an app to Azure, configure it to use an Azure sandbox and runner. We provide an AKS sandbox you can use to get started. You will need to set some vars to configure the sandbox. Create inputs for the public and private domains, since those will be different for each install. ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/azure-aks-sandbox" branch = "main" [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" location = "{{.nuon.cloud_account.azure.location}}" vnet_name = "{{.nuon.install_stack.outputs.network_name}}" resource_group_name = "{{.nuon.install_stack.outputs.resource_group_name}}" private_subnet_names = "{{.nuon.install_stack.outputs.private_subnet_names}}" public_subnet_names = "{{.nuon.install_stack.outputs.public_subnet_names}}" ``` ```toml runner.toml theme={null} # runner runner_type = "azure" helm_driver = "configmap" ``` ## Installation Azure does not support a GUI installation flow, so you must use the CLI installation flow. It consists of 3 steps: 1. You will create the install in the dashboard. 2. Your customer will provision the install stack, which includes the install runner, in their Azure account. 3. The install runner will provision your app. Let's walk through each step in detail. ### Create an Install Navigate to the Installs tab in the Nuon dashboard, and click on the "Create Install" button, and select the app you want to install. This will kick off a provision workflow. A new install stack template will be generated along with CLI commands to install it. These commands are templated with the install ID and other settings, and can be shared with your customer to run as-is. ### Install the Stack Your customer must [install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) and log in. ```sh theme={null} az login ``` They will be prompted to select a subscription. They should select the subscription they want to manage the install under. Now they can run the command to create the resource group that the install will be provisioned in. ```sh theme={null} az group create --name {{install_id}}-rg --location eastus ``` We limit the install runner's permissions to this resource group, with the exception of service provider registration. Providers must be registered at the subscription level before their services can be used. We grant the runner this lone permission at the subscription level to facilitate provisioning in a new subscription. Once the resource group has been created, they can provision the stack. ```sh theme={null} az stack group create --name {{install_id}}-stack --resource-group {{install_id}}-rg --template-uri https://nuon-install-templates-stage.s3.us-east-1.amazonaws.com/templates/inlgofml5mnk1e69dw9w1w0xzt/ist5hnwpr1q36mpegdj6o1terw.json --deny-settings-mode "denyDelete" --aou deleteAll ``` ### Customer Inputs Every app input declared with `source = "customer"` is a parameter on the install stack template, named `input` — `db_name` becomes `inputDbName`. The Deploy to Azure quick link renders one form field per input, and on the CLI they are `--parameters` arguments: ```sh theme={null} az stack group create ... --parameters inputDbName="orders" inputApiKey="" ``` The values the stack deploys with are reported back and become the install's inputs, so they show up on the install's Inputs page and are available to components as `{{ .nuon.inputs.inputs. }}`. Each parameter defaults to the value the install already has, falling back to the input's declared default. An input that has neither and is marked required has no default at all, so Azure refuses the deploy until a value is supplied rather than writing a blank one. Omitting a parameter keeps the install's current value. Customer inputs are ordinary template parameters, so their values are visible in the deployment history of the customer's own subscription. Anything that needs to stay secret belongs in the app's `secrets` config, which is written to Key Vault instead. ## Updating If you make changes to the install stack template, and need to update an install, your customer must re-provision the stack. 1. From the install's Overview page, click on "Reprovision install" in the "Manage" drop-down menu. This will trigger a reprovision workflow, and an updated CLI command will be provided. 2. Send the command to your customer for them to run. Note that the provided CLI command is the same `az stack group create` command you used to create the install. Even though the command is `create` it is effectively an upsert, and will update the stack if it already exists. # Google Cloud Source: https://docs.nuon.co/platform-support/gcp Deploy and manage BYOC apps on Google Cloud. ## Configuration To deploy an app to GCP, configure it to use a GCP sandbox and runner. We provide a [GKE sandbox](https://github.com/nuonco/gcp-gke-sandbox) you can use to get started, which provisions a GKE cluster and the IAM bindings the runner needs. Create inputs for the public and private domains, since those will be different for each install. ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.11.3" [public_repo] directory = "." repo = "nuonco/gcp-gke-sandbox" branch = "main" [vars] cluster_name = "n-{{.nuon.install.id}}" public_root_domain = "{{ .nuon.inputs.inputs.domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.domain }}" [[var_file]] contents = "./sandbox.tfvars" ``` ```toml sandbox.tfvars theme={null} additional_namespaces = ["whoami"] ``` ```toml runner.toml theme={null} # runner runner_type = "gcp" helm_driver = "configmap" ``` ## Installation When you create a new install, Nuon will generate a Terraform install stack your customer can use to install the runner and base infrastructure. The customer applies it locally with the Google Cloud SDK and Terraform. ### Create an Install Navigate to the Installs tab in the Nuon dashboard, and click on the "Create Install" button, and select the app you want to install. This will kick off a provision workflow. The stack will be generated and pre-configured `inputs.auto.tfvars` and `secrets.auto.tfvars` files will be provided for it. The tfvars files are templated with the install ID, runner ID, API token, and IAM permissions, and can be shared with your customer. ### Install the Stack Your customer must be authenticated to the GCP project they want to install your app in (typically via `gcloud auth application-default login`) and have permissions to create the IAM bindings, service accounts, and resources defined in the stack module. The dashboard provides four steps the customer can follow: 1. **Clone the install stack module** ```bash theme={null} git clone https://github.com/nuonco/install-stacks.git cd install-stacks/gcp ``` 2. **Configure remote state (recommended)** — create a `backend.tf` pointing at a GCS bucket so the install state is durable. ```hcl theme={null} terraform { backend "gcs" { bucket = "" prefix = "nuon/" } } ``` 3. **Save the install configuration** — copy or download the `inputs.auto.tfvars` and `secrets.auto.tfvars` content shown in the dashboard and save both files next to the module. 4. **Apply with Terraform** ```bash theme={null} terraform init && terraform apply ``` Both `.auto.tfvars` files are loaded automatically, so no `-var-file` flag is needed. The customer is prompted for `gcp_project_id` and `gcp_region` at apply time unless those are pre-populated in the install config (`gcp_account.project_id` / `gcp_account.region`), in which case they're injected into the tfvars and applied automatically. ## Try it with a sample app We maintain [`gke-simple`](https://github.com/nuonco/example-app-configs/tree/main/gke-simple), a sample app that uses the GKE sandbox to deploy a `whoami` workload with a certificate and load balancer. To try it: * Install the [CLI](../cli) * Clone the [example apps repo](https://github.com/nuonco/example-app-configs) * `cd` into the `gke-simple` directory * Run `nuon auth login` * Run `nuon apps create -n gke-simple` * Run `nuon apps sync` * Open the Nuon dashboard and create an install ## Updating If you make changes to the install stack template, the install must be reprovisioned to update the stack. 1. From the install's Overview page, click on "Reprovision install" in the "Manage" drop-down menu. This will trigger a reprovision workflow. 2. A new Terraform stack version and updated `inputs.auto.tfvars` and `secrets.auto.tfvars` files will be generated. 3. Send the updated files to your customer to re-apply with `terraform apply`. # Introduction Source: https://docs.nuon.co/platform-support/introduction Overview of supported cloud platforms — AWS, Azure, and Google Cloud — and what's available on each. The first and most obvious challenge of BYOC is supporting all the cloud platforms you want to deploy to. Leveraging our runner architecture, Nuon is able to provide first-class support for the major cloud platforms, while maintaining a constistent experience for day-2 operations. ## Stacks To install the Nuon runner into your customer's cloud account, Nuon generates a Stack template in both Terraform and the platform's native IaC language. Each version of the Stack will create the same resources, so you can pick whichever one suits you and your customers the best. The Stack is meant to be provisioned by your customer in their cloud account, phoning home to the Nuon control plane after it has provisioned. This means no cross-account access is required to provision it — your customer stays in full control of the runner identity and the resources it manages. Nuon will generate links and CLI commands you can share with your customer to install the Stack. You can also download the template if your customer would like to inspect it. If the platform offers a simple, one-click installation method, such as AWS Cloudformation Quickcreate, will support that. Google Cloud's IaC solution, Infrastructure Manager, uses Terraform natively, so we do not generate a separate template for Google installs. | | AWS | Azure | Google Cloud | | --------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------- | | **Terraform** | | | | | **Native IaC** | [Cloudformation](https://aws.amazon.com/cloudformation/) | [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/) | N/A | | **GUI Installation** | | | | | **CLI Installation** | | | | | **Download Template** | | | | You can find more information about Stacks on the [Stacks Concept page](/concepts/stacks). ## Access Control Each cloud platform has it's own conventions, best practices, and tools to manage access control. At the same time, BYOC requires an approach that can be consistently applied across all of them. How does Nuon balance these competing concerns? ### Nuon Runner Identity-Based Authentication The Nuon runner is designed to be platform-independent and stateless. When deployed, it is given an identity by the Stack, which is granted limited access to the cloud environment based on your application config. The runner does not store platform credentials. It will attempt to authenticate for each job it runs, using whatever identity has been assigned to it. Since your customer installs the Stack, they have full control over it, and can revoke the runner identity's access at any time using their cloud's native access control. The runner will immediately lose access to the cloud environment if they do this. ### Platform-Native Permissions The access the runner identity has is controlled by each platform's native access control features. See the page for each platform for implementation and configuration details. | | AWS | Azure | Google Cloud | | ------------------------------------------ | --------------------- | --------------------- | --------------------- | | **Custom Roles and Permissions** | | | | | **Kyverno Policies on Runner Jobs** | | | | | **Kyverno Policies in Kubernetes Cluster** | | | | # Go SDK Source: https://docs.nuon.co/sdks Use the Nuon Go SDK to integrate with organizations, apps, installs, components, workflows, and other Nuon resources. # Use the Nuon API from Go The Nuon Go SDK provides typed access to the Nuon control-plane API. Use it to work with organizations, apps, installs, components, workflows, action workflows, runbooks, and other Nuon resources from a Go program. > **Package to use:** `github.com/nuonco/nuon/sdks/nuon-go` > > The former standalone package at `github.com/nuonco/nuon-go` is deprecated. New integrations should use the SDK in the main Nuon repository. ## Requirements * Go 1.24.4 or later * A Nuon API token * The ID of the Nuon organization the request should access The examples use these environment variables: | Variable | Required | Description | | ----------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `NUON_API_TOKEN` | Yes | Bearer token used to authenticate API requests. | | `NUON_ORG_ID` | Yes for organization-scoped operations | Nuon organization ID sent with each request. | | `NUON_API_URL` | No for Nuon Cloud; **required in practice for BYOC** | API base URL. The examples default to `https://api.nuon.co`, but BYOC clients must target their deployed control plane. | | `NUON_INSTALL_ID` | Yes for the example lookup | ID of the install to retrieve. | The SDK does not read environment variables itself. Your program must read them and pass their values to the client constructor. ## Match the SDK to the deployed control plane Version selection is part of correctness for BYOC integrations. The SDK's methods, request types, response types, enum values, and validation are generated from a particular snapshot of the Nuon control-plane OpenAPI document. A BYOC control plane may run a different Nuon release from Nuon Cloud, especially during a rollout. Do **not** assume that the latest SDK is compatible with every deployed control plane. Nuon does not currently publish a broad SDK/API compatibility matrix, and the SDK does not perform a runtime compatibility handshake. Before selecting or upgrading the SDK: 1. Query the control plane the integration will actually call: ```sh theme={null} curl -fsS "${NUON_API_URL%/}/version" ``` A response includes the deployed `version`, source `git_ref`, and `recommended_cli_version`. For example, Nuon Cloud at `https://api.nuon.co` returned the following on August 10, 2026: ```json theme={null} { "git_ref": "0.19.1113", "recommended_cli_version": "0.19.1102", "version": "0.19.1113" } ``` This example is a point-in-time response, not a version recommendation. Query the target control plane before selecting or upgrading its SDK. 2. Pin the SDK version or source revision verified for that control-plane release. Do not infer compatibility from version-number similarity: control-plane and SDK module versions are separate release streams. 3. Test the exact SDK and control-plane pairing in a non-production environment. Exercise every endpoint and model shape the integration uses, including pagination and writes. 4. Record the verified pairing in the integration's release metadata or dependency-update notes. For example, pin a published SDK module release rather than asking Go for `latest`: ```sh theme={null} go get github.com/nuonco/nuon/sdks/nuon-go@ ``` If Nuon identifies a source revision rather than a published SDK module version, Go can pin the module from that revision and record a pseudo-version in `go.mod`: ```sh theme={null} go get github.com/nuonco/nuon/sdks/nuon-go@ ``` Commit both `go.mod` and `go.sum`. Avoid an unreviewed `go get ...@latest` in automated dependency updates. ### Plan control-plane and client upgrades together Treat a control-plane upgrade and an SDK upgrade as one compatibility change, even when they are deployed separately: 1. Inventory the SDK methods and generated model fields the integration uses. 2. Validate the existing client against the candidate control plane. 3. Validate the candidate SDK against the candidate control plane. 4. If the candidate control plane remains compatible with the existing client, upgrade the control plane first, then the client. This avoids deploying a client that calls endpoints an older control plane does not have. 5. If either side contains a breaking API change, coordinate the rollout and get an explicit supported upgrade path from Nuon rather than relying on deployment order alone. 6. Keep rollback artifacts for both the client and control plane until production verification is complete. `recommended_cli_version` applies to the Nuon CLI; it is useful release context but is **not** an SDK compatibility declaration. Likewise, API responses include `X-Nuon-API-Version`, but the Go SDK does not currently read or enforce that header. These values are useful for diagnostics and deployment checks, not proof of compatibility. ## Install the SDK ```sh theme={null} go get github.com/nuonco/nuon/sdks/nuon-go@ ``` Import the client and, when needed, its generated model types: ```go theme={null} import ( nuon "github.com/nuonco/nuon/sdks/nuon-go" "github.com/nuonco/nuon/sdks/nuon-go/models" ) ``` ## Create a client Create one client and reuse it. `WithURL` is required. In most integrations, validate the token and organization ID before constructing the client so configuration errors fail early. ```go theme={null} package main import ( "context" "fmt" "os" "strings" "time" nuon "github.com/nuonco/nuon/sdks/nuon-go" ) func main() { apiURL := strings.TrimSpace(os.Getenv("NUON_API_URL")) if apiURL == "" { apiURL = "https://api.nuon.co" } token := strings.TrimSpace(os.Getenv("NUON_API_TOKEN")) if token == "" { panic("NUON_API_TOKEN is required") } orgID := strings.TrimSpace(os.Getenv("NUON_ORG_ID")) if orgID == "" { panic("NUON_ORG_ID is required") } installID := strings.TrimSpace(os.Getenv("NUON_INSTALL_ID")) if installID == "" { panic("NUON_INSTALL_ID is required") } client, err := nuon.New( nuon.WithURL(apiURL), nuon.WithAuthToken(token), nuon.WithOrgID(orgID), ) if err != nil { panic(fmt.Errorf("create Nuon API client: %w", err)) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() install, err := client.GetInstall(ctx, installID) if err != nil { panic(fmt.Errorf("get install: %w", err)) } fmt.Printf("%s: %s\n", install.ID, install.Name) } ``` The Cloud default is an application choice in this example, not an SDK default. `nuon.New` requires `WithURL`; it does not automatically select Nuon Cloud or discover a BYOC control plane. For software that must never fall back from BYOC to Nuon Cloud, require `NUON_API_URL` instead of applying the default: ```go theme={null} apiURL := strings.TrimSpace(os.Getenv("NUON_API_URL")) if apiURL == "" { panic("NUON_API_URL is required") } ``` The client sends the following headers automatically: ```http theme={null} Authorization: Bearer X-Nuon-Org-ID: ``` Do not log tokens, include them in error messages, or commit them to source control. ### Switch organizations Organization-scoped methods use the client's current organization ID. If an application intentionally operates across organizations, update it before the next request: ```go theme={null} client.SetOrgID(orgID) ``` Avoid changing the organization concurrently on a client shared by multiple goroutines. Prefer one client per organization in concurrent multi-organization applications. ## Call the API The SDK exposes a `nuon.Client` interface with resource-oriented methods. Most methods accept a `context.Context`, identifiers, and an optional generated request or query model. ### Get one resource ```go theme={null} install, err := client.GetInstall(ctx, installID) if err != nil { return fmt.Errorf("get install %q: %w", installID, err) } ``` Other common lookups follow the same pattern: ```go theme={null} app, err := client.GetApp(ctx, appID) component, err := client.GetInstallComponent(ctx, installID, componentID) workflow, err := client.GetWorkflow(ctx, workflowID) stack, err := client.GetInstallStack(ctx, installID) ``` ### Create or update a resource Request and response types live in the `models` package: ```go theme={null} req := &models.ServiceUpdateInstallRequest{ Name: "production-us-east-1", } install, err := client.UpdateInstall(ctx, installID, req) if err != nil { return fmt.Errorf("update install %q: %w", installID, err) } ``` The model types are generated from Nuon's OpenAPI definition. Consult the [SDK models](https://github.com/nuonco/nuon/tree/main/sdks/nuon-go/models) for the fields accepted by a particular request. ### Work with nested data Some resources support recursive responses. For example, requesting an app configuration with `recurse` enabled includes its related configuration data: ```go theme={null} recurse := true config, err := client.GetAppConfig(ctx, appID, appConfigID, &recurse) if err != nil { return fmt.Errorf("get app config %q: %w", appConfigID, err) } ``` ## Paginate every list operation Paginated methods return three values: ```go theme={null} (items, hasMore, error) ``` Passing `nil` as the query uses the server default, which is currently 10 results. If the integration needs the complete collection, continue until `hasMore` is false. ```go theme={null} func allInstallComponents( ctx context.Context, client nuon.Client, installID string, ) ([]*models.AppInstallComponent, error) { const pageSize = 100 var all []*models.AppInstallComponent for offset := 0; ; offset += pageSize { page, hasMore, err := client.GetInstallComponents( ctx, installID, &models.GetPaginatedQuery{ Offset: offset, Limit: pageSize, }, ) if err != nil { return nil, fmt.Errorf( "list install components at offset %d: %w", offset, err, ) } all = append(all, page...) if !hasMore { return all, nil } } } ``` The same pattern applies to methods such as `GetApps`, `GetAllInstalls`, `GetAppInstalls`, `GetAppComponents`, `GetWorkflows`, and `GetInstallDeploys`. The maximum page size is 100. Do not increment the offset by the number of returned records; increment it by the requested page size, as shown above. ## Handle errors Always preserve the SDK error with `%w` when adding operation-specific context: ```go theme={null} install, err := client.GetInstall(ctx, installID) if err != nil { return fmt.Errorf("get install %q: %w", installID, err) } ``` The SDK includes helpers for common HTTP error classes: * `nuon.IsBadRequest(err)` — HTTP 400 * `nuon.IsUnauthorized(err)` — HTTP 401 * `nuon.IsForbidden(err)` — HTTP 403 * `nuon.IsNotFound(err)` — HTTP 404 * `nuon.IsServerError(err)` — HTTP 5xx * `nuon.ToAPIError(err)` — extracts a readable API error message * `nuon.ToUserError(err)` — extracts an API response explicitly marked as a user error Classify an error before wrapping it. The status helpers currently inspect the direct SDK error, while `ToAPIError` and `ToUserError` walk wrapped error chains. ```go theme={null} install, err := client.GetInstall(ctx, installID) if err != nil { switch { case nuon.IsUnauthorized(err): return fmt.Errorf("Nuon API token was rejected: %w", err) case nuon.IsForbidden(err): return fmt.Errorf("token cannot access organization %q: %w", orgID, err) case nuon.IsNotFound(err): return fmt.Errorf("install %q does not exist: %w", installID, err) case nuon.IsServerError(err): return fmt.Errorf("Nuon API server error: %w", err) default: return fmt.Errorf("get install %q: %w", installID, err) } } ``` Respect context cancellation and timeouts separately when callers need to distinguish them: ```go theme={null} if err != nil { if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("Nuon API request timed out: %w", err) } return fmt.Errorf("get install %q: %w", installID, err) } ``` ## Common API areas The public client covers these major resource groups: | Area | Representative methods | | -------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Authentication and organizations | `ValidateToken`, `GetAuthMe`, `GetOrg`, `GetOrgs`, `SetOrgID` | | Apps and app configuration | `GetApps`, `GetApp`, `CreateApp`, `GetAppConfig`, `GetAppLatestConfig`, `BuildAppConfig` | | Components and builds | `GetAppComponents`, `GetComponent`, `GetComponentBuilds`, `GetComponentLatestBuild` | | Installs | `GetAllInstalls`, `GetInstall`, `CreateInstall`, `UpdateInstall`, `ReprovisionInstall`, `DeprovisionInstall` | | Install components and deploys | `GetInstallComponents`, `GetInstallDeploys`, `DeployInstallComponents`, `GetInstallComponentOutputs` | | Inputs and secrets | `GetInstallInputs`, `UpdateInstallInputs`, `GetAppSecrets`, `CreateAppSecret` | | Workflows | `GetWorkflows`, `GetWorkflow`, `GetWorkflowSteps`, `CancelWorkflow`, `RetryWorkflowStep` | | Actions and runbooks | `GetActionWorkflows`, `CreateInstallActionWorkflowRun`, `GetInstallRunbooks`, `CreateInstallRunbookRun` | | Runners and logs | `GetInstallRunnerGroup`, `GetRunnerJobs`, `GetLogStream`, `LogStreamTailLogs` | | VCS and service accounts | `GetVCSConnections`, `CreateVCSConnection`, `ListServiceAccounts`, `CreateServiceAccountToken` | See the current [`Client` interface](https://github.com/nuonco/nuon/blob/main/sdks/nuon-go/client.go) for the complete method list and signatures. ## Structure integrations for testing `nuon.Client` intentionally exposes the whole SDK, which is often more than one package needs. Define a small local interface containing only the methods your code calls. Production code can receive the real SDK client, and tests can provide a focused fake. ```go theme={null} type installReader interface { GetInstall(context.Context, string) (*models.AppInstall, error) GetInstallComponents( context.Context, string, *models.GetPaginatedQuery, ...nuon.GetInstallComponentsOpts, ) ([]*models.AppInstallComponent, bool, error) } type Service struct { api installReader } func NewService(api installReader) *Service { return &Service{api: api} } ``` This keeps test doubles small and prevents application code from depending on generated transport internals. ## Recommended integration practices 1. Use the in-tree SDK path, not the deprecated standalone module. 2. Identify the deployed control-plane `version` and `git_ref`, then pin a verified SDK version for that environment. 3. Require the correct `NUON_API_URL` for BYOC integrations; do not silently send their traffic to Nuon Cloud. 4. Validate `NUON_API_TOKEN` and `NUON_ORG_ID` in your configuration layer. 5. Reuse a client rather than constructing one for every request. 6. Give network operations a caller-controlled context and timeout. 7. Paginate list calls explicitly when completeness matters. 8. Wrap errors with `%w`, but run the SDK's HTTP status helpers before wrapping. 9. Program against the handwritten `nuon.Client` facade, not generated `client/operations` transport types. 10. Treat generated `models` as versioned API contracts and test them when either the SDK or control plane changes. 11. Commit the pinned dependency in `go.mod` and `go.sum`; avoid unattended `@latest` upgrades. 12. Never expose API tokens in logs, command output, or error messages. ## Working examples Two Nuon extensions demonstrate current SDK usage: * [`nuon-ext-terraform`](https://github.com/nuonco/nuon-ext-terraform) shows client construction, install and app-config lookups, generated model inspection, and complete pagination. * [`nuon-ext-cf-stack`](https://github.com/nuonco/nuon-ext-cf-stack) shows client construction and retrieving an install and its stack before operating on AWS resources. ## Reference * [Nuon Go SDK source](https://github.com/nuonco/nuon/tree/main/sdks/nuon-go) * [Complete client interface](https://github.com/nuonco/nuon/blob/main/sdks/nuon-go/client.go) * [Generated models](https://github.com/nuonco/nuon/tree/main/sdks/nuon-go/models) * [SDK generation from the control-plane OpenAPI document](https://github.com/nuonco/nuon/blob/main/sdks/nuon-go/generate.sh) * [Control-plane version response](https://github.com/nuonco/nuon/blob/main/services/ctl-api/internal/health/version.go) * [Nuon API OpenAPI document](https://api.nuon.co/docs/doc.json) * [OpenAPI v2 specification](https://api.nuon.co/oapi/v2) * [OpenAPI v3 specification](https://api.nuon.co/oapi/v3) * [Nuon documentation](https://docs.nuon.co) # Security Source: https://docs.nuon.co/security How Nuon keeps customer cloud accounts secure throughout the BYOC deployment lifecycle. Nuon is designed so that vendors can deploy and manage software in customer cloud accounts without ever having direct access to those accounts. ## No Cross-Account Access The Nuon [Control Plane](/architecture/platform) never has credentials or network access to customer cloud accounts. [Runners](/concepts/runners) are deployed inside the customer's environment and communicate outbound only, polling the Control Plane for jobs. The Control Plane cannot push commands or open connections into customer accounts. ## Customer-Deployed Infrastructure All infrastructure in a customer's account is created by the customer themselves. When a vendor creates an [install](/concepts/installs), Nuon generates a [Stack](/concepts/stacks) template — both a Terraform version and the platform's native IaC version (CloudFormation on AWS, Azure Resource Manager (Bicep) on Azure; on GCP, only Terraform is generated since Infrastructure Manager is Terraform-native). The customer reviews and deploys whichever format fits their tooling, using their own credentials, retaining full ownership and visibility of every resource created. ## Least-Privilege IAM Roles Runners use separate IAM roles for different operations (provisioning, maintenance, deprovisioning), each scoped to the minimum permissions required. Components and actions define their own roles and policies, so the Runner never holds more access than a single job needs. See [Permissions](/config-ref/permissions) for configuration details. ## Control-Plane Access Control Access within your Nuon org is governed by roles. Every identity — a team member, service account, API token, or OIDC trust policy — is assigned a single role that determines what it can read and change, and roles are scoped to a single org. See [Access control](/concepts/access-control) for the full model. ## Customer Kill Switch The customer controls the Runner through the Stack. They can disable the Runner at any time to stop it from executing jobs, and re-enable it when ready. No action from the vendor or Nuon is required. ## Break Glass Access Vendors can define break glass roles for temporary elevated access during emergencies. These roles are granted by the customer through the Stack and can be revoked at any time. See [Break Glass](/config-ref/break-glass) for configuration details. ## Secrets Stay in the Customer's Cloud [Secrets](/concepts/app-secrets) are entered by the customer when deploying the Stack and stored in their cloud's secret manager: AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager. Neither Nuon nor the vendor ever sees secret values. ## Log Visibility Nuon only stores logs of infrastructure operations (e.g., creating a Kubernetes cluster, deploying a Helm chart, running Terraform). Application logs are never sent to the Control Plane. [Actions](/concepts/actions) can execute operational scripts, but the IAM roles they use are defined by the vendor and approved by the customer through the Stack. Customers running installs on AWS, Azure, or GCP can optionally [export detailed runner audit logs](/guides/export-runner-audit-logs) directly from their cloud account to an OTLP-compatible logging backend they control. Only Nuon audit events are forwarded. Application logs and general runner logs are excluded, and the destination and credentials remain in the customer's cloud secret manager. ## Build Isolation [Build Runners](/architecture/platform#build-runner) are always single-tenant. Each is deployed to its own host and does not share resources with other Runners. Build artifacts are stored as OCI images in the customer's cloud registry. ## Policies Vendors can enforce compliance and security standards across builds and deploys using [OPA](https://www.openpolicyagent.org/) and [Kyverno](https://kyverno.io/) policies. Policies can validate Terraform plans, Helm charts, Kubernetes manifests, container images, and sandbox infrastructure, blocking or warning on violations. See [Policies](/concepts/policies) for details. ## Deployment Options **Nuon Cloud** is the SaaS offering. App and install metadata is stored in an encrypted PostgreSQL database, accessed over TLS with Auth0 authentication. **Nuon BYOC** deploys the Nuon Control Plane into the vendor's own AWS account. Install metadata and logs stay in the vendor's cloud, and the vendor can restrict Runner access to the Control Plane outside of maintenance windows. For vendors with stricter requirements, Nuon also supports fully self-hosted deployments using Helm charts. # Support Source: https://docs.nuon.co/support/support Get help with the Nuon platform. The best way to get help with the Nuon platform is via our community Slack. ## Support Channels Nuon community Slack support. Submit a contact request via our web form. # TUI Source: https://docs.nuon.co/tui Run entire workflows and trigger install actions via our TUIs ## Workflow TUI Our workflow terminal user interface (TUI) lets users manage and follow the steps of an install in real time. Review plan diffs, approve steps, retry failed actions, and cancel workflows when needed. We designed this TUI to make working in our CLI faster and easier. Workflow TUI ## Quickstart Select your install ```bash theme={null} nuon installs select ``` Open the TUI ```bash theme={null} nuon installs workflows ``` ## Actions TUI The action terminal user interface (TUI) lets users trigger and view action runs in any app with actions configured. Actions TUI ## Quickstart Select your install ```bash theme={null} nuon installs select ``` Open the TUI ```bash theme={null} NUON_PREVIEW=true nuon installs actions ``` The action TUI is currently still experimental and in preview mode. # 001 - Install Workflow Updates Source: https://docs.nuon.co/updates/001-install-workflows-actions Updates to install workflows, bug fixes and actions improvements. *May 5th, 2025*
v0.19.539
This update introduces significant improvements to install workflows, including detailed step information, workflow cancellation, enhanced action outputs, and Terraform workspaces management.
## Workflows UI The UI view of each step now includes details on the step itself. For instance, you can see details when generating a step. Detailed view of a workflow step showing execution details and logs Workflows are now supported by actions and deploys ## Workflow Cancellation You can now cancel a workflow by clicking the "Cancel" button at the bottom of any running workflow. UI showing the cancel button at the bottom of a running workflow This cancellation will make a best effort to cancel the current running step, including cancelling any running jobs on the runner. ## Improvements to Action Outputs Previously, action outputs combined the outputs from all steps into a single dictionary. Now, you can access all outputs by step as well. Interface showing how action outputs are organized by step name Renaming an action step can potentially break references to actions. We plan to add an ID field for output references soon. ## Kube Config + Credentials We now automatically inject a valid kube config into the environment for each action. You can reference the `KUBECONFIG` env var to find a configuration file for the install sandbox the runner has access to. Currently, all actions use the maintenance role defined in your app. ## Terraform Workspaces All sandboxes and terraform components use the Nuon API as their state backend. You can now see resources that are managed via the UI: Terraform workspace UI showing managed resources for a component For manual state imports or bringing existing apps into Nuon, you can now generate a `backend` configuration for working with the state of an install sandbox or component directly. Button to generate a Terraform backend configuration file If you get a deploy error saying you have two backends configured, please remove your customer backend configuration as the runner environment will automatically configure it This is controlled with a feature flag and we will be working with users to enable this one-by-one. ## Bug Fixes and Internals * Fixed an issue where the runner could run out of space. * Fixed an issue where reprovisioning an install could fail if the stack was unable to be templated during initial setup. * Fixed a bug where all components would rebuild if the previous sync failed. ## Coming Soon We are working on approvals, a way to preview and accept or deny changes to an install before they are executed. # 002 - Install Inputs, dependencies, and more Source: https://docs.nuon.co/updates/002-inputs-dependencies Install Inputs, dependencies and more *May 6th, 2025*
v0.19.540
This update adds support for input types, internal inputs, auto-configured dependencies, and improvements to Terraform component workflows with KUBECONFIG integration and workspace management.
## Input Types You can now define an input type, which denotes which type of input is accepted. This is used to control rendering across different install inputs and will allow us to build more semantic meaning into inputs. You can define a `type` field on an input in your config. The following types are supported: * `string` * `bool` * `number` * `json` * `list` Input types are currently only settable as metadata, but will be rolled out to all different surfaces of install management soon. This change is backwards compatible and all current inputs will default to `string` as their `type`. ## Internal Inputs Internal inputs are inputs that are only settable via the Nuon dashboard. This allows you to define inputs that a customer cannot control directly or set internal values. You can use `internal: true` to mark an input as internal. This change is backwards compatible and all current inputs will default to `internal: false`. ## Dependencies Dependencies are now auto-configured based on the variables that you define. For example, the following config would mark `container_image` as a dependency. ```toml theme={null} [values] "image.repo" = "{{.nuon.components.container_image.outputs.repo_url}}" "image.tag" = "{{.nuon.components.container_image.outputs.tag}}" ``` If you would like to manually declare a dependency, you can define it in a component config using: ```toml theme={null} # define a dependency directly, if you do not use any outputs from it. dependencies = ["docker_build", "container_image"] ``` ## Terraform Component `KUBECONFIG` Each terraform component run will now have a `KUBECONFIG` env-var with the path to the kubeconfig for the run. You can use the `kubectl` provider without any additional configuration, as the file and env var are exposed using defaults: ```hcl theme={null} provider "kubectl" {} resource "kubectl_manifest" "example" { yaml_body = < To make working with terraform state easier, you can now download a nuon backend config file for any workspace with the push of a button. Export button for downloading Terraform backend configuration ## Bug Fixes and Improvements * Fixed an issue where only 64MB was reserved for the runner. * Input Group and Input ordering is now respected. * Fixed an internal issue where dependencies were not configured with an app-version, but only used the latest. * Missing or circular dependencies will now error when syncing a config. # 003 - Improvements to deletions, cancellations and more. Source: https://docs.nuon.co/updates/003-install-deletes-cancel-improvements Improvements to deletions, cancellations and more. *May 8th, 2025*
v0.19.542
This update focuses on improvements to install management, including component dependency ordering, UI enhancements, and comprehensive options for deprovisioning installs, components, sandboxes, and stacks.
## Component Dependencies Sub-Ordering When multiple components have either the same dependencies or no dependencies, they will automatically be ordered by component type. They will use this ordering: 1. `container_image` 2. `docker_build` 3. `terraform_module` 4. `helm_deploy` UI showing component ordering based on type and dependencies If two components have the same dependencies *and* the same type, they will be ordered by name. For instance, in the following deploy, only the helm deploy has any defined dependencies. ## Auto Hide Nav The side-nav bar will now automatically hide when not being used. Collapsible side navigation bar ## Restart Runner While you should not need this for normal usage, you can now restart your runner from the runners page. Button to restart a runner from the runners page ## OCI Artifact Information Whenever a component is deployed into an install, an OCI artifact is synced into the install first. You can now see any container image, helm chart artifact or terraform artifact that was synced during a deploy on the image tab. UI showing OCI artifacts synced during component deployment ## Deprovision an Install Deleting an install requires tearing down its deployed components, destroying the sandbox, and destroying the CloudFormation Stack via AWS. You can now delete an install from the install overview page in the UI: Deprovision Install button on the install overview page If you have components that are not successfully deploying and you want to attempt to tear down everything in the install, you can use the `continue` option: Continue option when deprovisioning with failing components ## Forgot an Install Once you have deprovisioned an install, you must forget it to remove it from Nuon's system. You can do this from the install overview page: Forget Install button to remove a deprovisioned install ## Additional Delete Options In the happy path, an install should be able to be deleted with just a single run of `Deprovision Install`. However, when things go wrong or you are iterating, you can delete individual parts of an install directly. ### Delete an Install Component Sometimes an install will fail to destroy because a single component has leaked state. You can manually tear down a component on the install component page. Teardown Component button on the install component page If you have a failed install deprovision due to a component, you can directly tear down the component and then re-run Deprovision Install to try again ### Teardown Install Components You can delete all install components using the Teardown All Components option from the components overview. Teardown All Components option on the components overview page ### Deprovision a Sandbox In rare cases, it can be helpful to forgo tearing down install components and deprovision only the sandbox. You can deprovision a sandbox directly from the sandbox tab on an install. Deprovision Sandbox button on the sandbox tab It is recommended to try the `Deprovision Install` button first ### Deprovision Install Stack The install stack can only be deleted from an install AWS account directly. If you click the deprovision stack button from the install overview, you can find directions to deprovision it. Deprovision Stack dialog with AWS CloudFormation instructions # 004 - Secrets Syncing Source: https://docs.nuon.co/updates/004-secrets Automatically sync customer secrets into Kubernetes *May 10th, 2025*
v0.19.544
## Customer Secrets Customer input secrets can be defined in your `secrets.toml` config. This allows you to define secrets that need to be added by users when provisioning an install via the CloudFormation stack. You can define a secret with the following config: ```toml theme={null} [[secret]] name = "license_key_{{.nuon.id}}" display_name = "license key for {{.nuon.id}}" description = "License key" ``` Templating can be used on any field, allowing you to dynamically namespace secrets and other things. Since secrets are synced *before* components are `required` secrets will cause the CloudFormation Stack to fail, if not added. ## Auto Generated Secrets You can automatically generate a secret value by adding the `auto_generate: true` field. For instance, to create a unique value that is configured in your cluster: ```toml theme={null} [[secret]] name = "db_password_{{.nuon.id}}" display_name = "Auto generated database password" description = "Auto generated database password" auto_generate = true ``` ## Automatic Syncing You can automatically sync secrets into your sandbox kubernetes cluster by adding the `kubernetes_sync: true` value to your config. ```toml theme={null} [[secret]] name = "license_key_{{.nuon.id}}" display_name = "License Key" description = "License Key" kubernetes_sync = true kubernetes_secret_namespace = "{{.nuon.id}}" kubernetes_secret_name = "database-password" ``` Syncing can be disabled via your config, and requires that correct permissions are enabled for the Provision role in your `permissions.toml`. ## Outputs You can use the outputs of a synced secret using the `{{.nuon.secrets}}` template. The values If you *do not* see your secrets in `{{.nuon.secrets}}`, it means you do not have syncing on. You can reference the source ARN directly using `{{.nuon.install_stack.outputs}}`. If you are having trouble working with your state, we recommend adding it directly to your readme using the following block. ```toml theme={null}
Full Nuon State
  {{ toPrettyJson .nuon }}
  
```
Secret values are not available in your state, and can only be read from your application by using the source secret (AWS Secrets Manager) or the Kubernetes secret directly. ## Life Cycle Currently secrets are synced on `Provision`, `Reprovision` and `ReprovisionSandbox` workflows. We currently do not automatically sync secrets. We are considering this for a future release ## Fixes * Fixed an issue where terraform state was attempted to be displayed on all component types. * Fixed a bug with dependency ordering. * Fixed a bug in helm value templating. * Fixed a bug where syncing an app with no installer, components or actions could fail. * Fixed an issue where secrets would not sync when the kubernetes name was not valid. # 005 - nuon dev and improvements Source: https://docs.nuon.co/updates/005-developer-experience `nuon dev` allows you to iterate on configs with a single command, view install state, view an app dependency graph and more. *May 16th, 2025*
v0.19.550
## `nuon dev` CLI Command This feature is currently in alpha. Please reach out to use if you are interested in using it. We have added a new CLI command, `nuon dev`, that you can use to create a new app version and deploy it to a dev install. ```bash theme={null} ~nuon (main)$ nuon dev ~/nuon/byoc/byoc-nuon -y Checking that you are ready to create a new app version... INFO checking git branch... SUCCESS you are on branch ja/005-changlog-updates INFO verifying app exists... SUCCESS app ID is appt2zli0c6rx6utsd922i9kbl INFO parsing app config... SUCCESS parsed app config successfully INFO validating config... SUCCESS app config is valid INFO checking component branches... SUCCESS component branches are ok INFO checking that local changes have been pushed... SUCCESS all required changes have been pushed INFO syncing config to api... ``` To use it: 1. Open a nuon app directory. 2. Create a dev branch. 3. Run `nuon installs select` to select the install you want to develop with. 4. Make some changes and push them to your remote. 5. Run `nuon dev` and follow the prompts. A new app version will be created and deployed to your dev install. This new version will **only** be deployed to your dev install. Other installs will not be affected. Additionally, you can pass the flag `--yes` to auto-approve all prompts. Make sure you have the correct branch and install set up before you do this. If you update only the source code of a component, and not it's config, a new build will not be created. You must create a build for that component yourself. This shortcoming will be resolved in a future update of this feature. Using the existing `nuon apps sync` command will update *all* installs to the config being synced, where as `nuon dev` will only update the *current* install. ## View Component Dependency Graph You can now view a graph of component dependencies for an app. On the "Components" tab of any app, click the "View dependency graph" button. View Dependency Graph 1 ## View Install State You can now view the complete state of an install in JSON format. On any install, click the "View state" item in the "Manage" drop-down menu. View Install State One View Install State Two ## Improved Step Viewer We have made improvements to the step viewer that make it easier to work with and debug steps. Improved Step Viewer ## Improved Components Page Performance The install components page has been overhauled to load faster, and support rendering more components. Improved Components Page Performance ## Cancel Workflows From History Page You can now cancel workflows from the history page. Cancel Workflows From History Page ## Fixes * Fixed a bug where cancelling a reprovision would leave a runner unable to recover. * Fixed a bug where inputs did not properly use their defaults. * Fixed a bug where manual env-var overrides on an action run were not respected. * Fixed a bug where `current-inputs` would show all historical inputs. * Fixed a bug where you could not update an input with an install name (needed an install id), via the CLI. * Fixed a bug where cancelling future workflows did not give the expected behavior. * Fixed a bug where cancelling an in-progress workflow left a deploy, sandbox run or other in an in-progress status. # 006 - Secrets improvements, workflow retries and bug fixes Source: https://docs.nuon.co/updates/006-secrets-workflows Secrets improvements, workflow retries and bug fixes. *May 19th, 2025*
v0.19.558
## Improved Secrets The secrets have had their naming convention updated to better support multiple deployments into the same account and faciliate access to these names which are guaranteed to be unique. The new naming convention is: ```txt theme={null} {{.nuon.install.id}}/{{.secret.name}} ``` Additionally, the secret ARN is now included in the `secrets` objects in `{{.nuon.secrets}}`. ## Handle Expired Stacks CloudFormation stacks have an expiration of 24 hours when setting up a customer install. If a stack is not run in 24 hours, it will show a better error message. ## Retry Failed Workflow You can now retry a workflow from where it failed by clicking the "Retry Workflow From This Step". This will create a new workflow that starts at the step that was failed. Retry workflow This is just the first pass at allowing more interaction when a workflow fails. We plan to integrate failures into approvals, next. ## View Deploy Plan You can now view a deploy plan, from the UI: Deploy plan view This is useful for debugging the rendered variables that went into a deploy, or other run. ## Nuon Helm driver You can now store helm release information in Nuon API. You can customise it by editing the `helm_driver` value in `runner.toml` file of the app. ```toml theme={null} runner_type = "aws" helm_driver = "nuon" ``` ## Helm Driver Options * `secret`- Store helm release information in kubernetes secrets. This is the default driver. * `configmap`- Store helm release information in kubernetes configmaps. * `nuon`- Store helm release information in Nuon API. This feature is currently in alpha and experimental. ## Internal Migrations We have been rolling out the following internal, platform improvements: ### Nested Event Loops Each Nuon app and install is backed by a long lived Temporal workflow called [an eventloop](). Event loops are used for orchestration, control-flow and limit concurrency. Previously, each install had a single event loop meaning only a single deploy or job could be executed at once. We have updated the install to have an event loop for each component, action, and the sandbox. This is a future migration that will allow us to build parallel deploys based on the component graph, and more. ### Component Builds As part of now building Nuon BYOC, we have deprecated an internal service, and changed the way we build components. ## Fixes * Deprovisioning an install now works when outputs are missing. Previously, a failed deprovision could leave an install in an undeprovisionable state. * Deprovisioning an install uses the previous active deploy config. * Fixed a bug where some outputs were not showing in the state. * Fixed a bug where the permissions boundary was not set properly on the stack. * Fixed a bug where a required secret was not required in the CloudFormation stack config. # 007 - Secret formats, bug fixes Source: https://docs.nuon.co/updates/007-secret-formats Secrets formats, bug fixes *May 24th, 2025*
v0.19.573
## Secret Formats Some complex secrets have formatting that can cause problems when being added to a CloudFormation stack. To enforce that a secret is base64 encoded, you can add the `format: base64` field to a secret. The following secret will be expected as a base64 encoded secret when inserted into the stack. ```toml theme={null} [[secret]] name = "github_app_key" display_name = "GitHub App Key" description = "Github App Key for Nuon BYOC Install {{.nuon.id}}" required = true kubernetes_sync = true kubernetes_secret_namespace = "ctl-api" kubernetes_secret_name = "github-app-key" format = "base64" ``` The runner sync job will automatically decode the value based on the format when syncing into the Kubernetes secret. # 008 - Stack Tab, VCS Import, and Component Build Updates Source: https://docs.nuon.co/updates/008-vcs-stacks-fixes New stack management features, VCS connection importing, and improved component build system *May 27th, 2025*
v0.19.576
## Install Stacks You can now view and interact with versions of an install's stack. ## Import a VCS Connection You can now import a VCS connection into your org. This is useful for sharing the same Github installation of between different Nuon orgs. ## Refactored component build checksum We have updated the logic for determining when component builds are triggered. The checksum calculation now relies on the contents of the component configuration file rather than the previous method. Please note that this change is not backwards compatible and will require a rebuild of all components during your first file sync following this update. ## Bug Fixes * Fixed an issue where cancelling a workflow could leave a component undeployable. * Fixed an issue where deprovisioning an install failed to find the latest deploy. * Fixed an issue where a failed provision on the first run would prevent future stack runs from getting to a healthy runner. # 009 - Action Triggers, improved configs, default secrets Source: https://docs.nuon.co/updates/009-action-triggers-secrets-configs Action triggers, improved configs, default secrets. *June 5th, 2025*
v0.19.590
## Action Triggers Action triggers are now available for all workflows. You can now use the following set of triggers: * `pre-provision` * `post-provision` * `pre-reprovision` * `post-reprovision` * `pre-deprovision` * `post-deprovision` * `pre-deploy-all-components` * `post-deploy-all-components` * `pre-teardown-all-components` * `post-teardown-all-components` * `pre-deprovision-sandbox` * `post-deprovision-sandbox` * `pre-reprovision-sandbox` * `post-reprovision-sandbox` * `pre-update-inputs` * `post-update-inputs` * `pre-secrets-sync` * `post-secrets-sync` Each workflow trigger is called at the beginning or end of the workflow. In some cases, such as `pre-provision` or `pre-reprovision` that include a stack-run, the trigger will be called right after the runner is healthy. The following triggers require a `component_name` field to be set, as they are tied to a specific component: * `pre-deploy-component` * `post-deploy-component` * `post-teardown-component` * `pre-teardown-component` `pre-component-deploy` and `post-component-deploy` have been renamed to `pre-deploy-component` and `post-deploy-component` for consistency with other triggers. `pre-sandbox-run` and `post-sandbox-run` have been deprecated, in favor of `pre|post-reprovision`, `pre|post-provision`, and `pre|post-deprovision` ## Inputs, Permissions and Secrets Dirs You can now structure configs using directories. The following fields support loading their config from either a sub-directory, or a single file: * `secrets` - supports either a `secrets.toml` or a `secrets/` directory * `inputs` - supports either an `inputs.toml` or an `inputs/` and `input_groups` directory. * `permissions` - support either a `permissions.toml` or a `permissions/` directory. The configuration format is slightly different for files that are added to a directory: ### Input No longer requires the `[[inputs]]` syntax when added into `./inputs/input-name.toml`. ```toml theme={null} name = "auth_audience" description = "Auth0 Audience" default = "api.{{.nuon.install.id}}.nuon.run" display_name = "Auth0 Audience" group = "auth" ``` ### Input Group No longer requires the `[[groups]]` syntax when added into `./input_groups/group-name.toml`. ```toml theme={null} name = "auth" description = "Auth0" display_name = "Authentication Configuration." ``` ### Secret No longer requires the `[[groups]]` syntax when added into `./input_groups/group-name.toml`. ```toml theme={null} name = "clickhouse_cluster_pw" display_name = "Clickhouse Cluster Password" description = "Password for the Clickhouse Cluster for the Nuon CTL API User." auto_generate = true kubernetes_sync = true kubernetes_secret_namespace = "clickhouse" kubernetes_secret_name = "clickhouse-cluster-pw" ``` ### Permissions No longer requires the `[provision_role]`, `[deprovision_role]`, or `[maintenance_role]` syntax, but now requires a `type`. ```toml theme={null} type = "deprovision" name = "{{.nuon.install.id}}-deprovision" description = "deprovision" display_name = "deprovision" permissions_boundary = "./deprovision_boundary.json" [[policies]] managed_policy_name = "AdministratorAccess" ``` ## Improved Schema Each configuration file now supports a JSON schema that can be loaded via our api. You can now add `#:schema https://api.nuon.co/v1/general/config-schema?type=input` at the beginning of any Nuon config file for editor supported TOML LSP integrations. The following `types` are valid: * `runner` - for use in `runner.toml` * `sandbox` - for use in `sandbox.toml` * `helm` - for use in any helm component toml config in `components/`. * `docker-build` - for use in any docker-build component toml config in `components/`. * `container-image` - for use in any container-image component toml config in `components/`. * `terraform` - for use in any terraform component toml config in `components/`. * `permissions` - for use in any permissions config in `permissions/`. * `secret` - for use in any permissions config in `secrets/`. * `input` - for use in any input config in `input/`. * `input-group` - for use in any input config in `input_groups/`. * `metadata` - for use in `metadata.toml`. * `action` - for use in action defined in `actions/`. * `stack` - for use in `stack.toml`. * `installer` - for use in `installer.toml`. * `full` - only useful for validating a full, parsed Nuon config. ## Component and Action Groups You can now group components and actions using subdirectories. For now, the change is purely for configuration management, but will soon be added into the ui and other places. ## Default Secrets You can now provide a default for secrets that get added in a cloudformation stack. ## Fixes * Made org creation email configurable for BYOC installs of Nuon. * Fixed a bug where content in the `permissions` config would not look in the `./permissions` subdirectory. # 010 - Approvals Source: https://docs.nuon.co/updates/010-approvals Workflows now support approvals, so you can verify the helm / terraform plan. *June 16th, 2025*
v0.19.582
## Approvals You can now see a Helm or Terraform plan when updating an install. Each workflow will automatically prompt you to approve the plan, and only proceed upon approval. Approve Sandbox ## Auto Approve Install You can auto approve all changes on an install. Auto Approve Install ## Auto Approve Workflow Sometimes, you just want to approve the first step in a workflow. You can approve all following steps on a workflow, which means each step will automatically proceed. Approve Workflow ## Re Plan When an approval has been waiting for an extended period of time, you can re-run the plan phase by pressing the **Retry Plan** button. Retry Plan Retry Plan Two This will re-run the plan step, and prompt the approval before continuing. ## Helm component config You can now define the `TakeOwnership` flag in the helm component config. ```toml theme={null} name = "helm_chart_two" type = "helm_chart" chart_name = "acme-corporation" take_ownership = true ``` We'll be exposing more helm install flags in the future releases. ## Bug Fixes and other * `nuon apps sync-dir` has been deprecated in favor of `nuon apps sync`. * Fixed an issue related to authentication for BYOC installs of Nuon. * Fixed a bug where actions could not be deleted. * Fixed a bug where some cron actions did not run. * Fixed a bug where an empty permissions boundary errored out. # 011 - Approval Improvements Source: https://docs.nuon.co/updates/011-approval-updates Inline workflow retries, auto-approve all steps, UI renames, and team member removal. *June 27th, 2025*
v0.19.595
## Inline Workflow Retries Failures can now be retried inline, and a new step will be generated. Inline workflow retries interface ## Auto Approve Workflow You can now approve all steps in a workflow. Auto Approve Workflow interface ## Renamed History to Workflows In preparation of the new Audit Log tool, we are renaming history to workflows. Inline workflow retries interface ## Remove Team Member You can now remove team members from your org. Update ## Improved Approval Diffs The diffs displayed for Helm and Terraform approval steps have been improved to preview more of the underlying changes. Improved Approval Diffs interface ## Approvals on Deploy and Sandbox Run Pages We now keep approvals for all deploys and sandbox runs on the detail pages. Approvals pinned interface ## Fixes * Default secrets are now respected. Adding a `default` field on a secret will be respected. * Fixed an issue where `connected_github` repos for sandboxes were not respected. * Selective component build on install input update. * Improved ctl-api healthchecks. * Inputs reference parsing. * Fixed a bug where authentication cookie could become too large and show a 431 response. * Fixed a bug where components that were not deployed, would still be torndown during deprovision. # 012 - Approval Diffs Source: https://docs.nuon.co/updates/012-approval-diffs-plan-only Helm teardown diffs, plan-only workflow mode, manual secret sync, and CLI pagination flags. *July 15th, 2025*
v0.19.601
## Improved Helm Resource Diff Improved the diff for helm teardowns. Improved Helm Resource Diff ## Plan Only Mode You can now run most workflows in plan only mode. This means that changes will only be planned, and execution skipped. This is useful for checking for drift, for instance. Plan Only Mode ## Sync Secrets You can sync secrets manually, using the UI. ## Nuon CLI list command pagination The CLI now accepts limit & offset options for listing out items from commands. `nuon components list --offset=15 --limit=5` ## Improved naming for manual Action Workflow runs Workflow runs for manually triggered Actions now show the Action name in the Workflows page. Improved Workflow history for Action runs ## Bug Fixes * Fixed an issue where the Terraform state for a component would show an old state version. * Fixed an issue where deprovisioning was not properly tearing down from a plan. * Fixed an issue where some workflows would not get executed, due to an internal error. * Fixed an issue with empty workflow on input update. # 013 - Kubernetes Manifest Component Source: https://docs.nuon.co/updates/013-kubernetes-manifest-component New Kubernetes Manifest component type for deploying raw Kubernetes resources into customer cloud accounts. *August 1st, 2025*
v0.19.611
## New component type: Kubernetes Manifests Adds support for kubernetes manifests component type. Kubernetes Manifest Component Support Kubernetes Manifest Approvals and Diffs ## Support Helm Resource Diffs Previously, we did not support showing Helm resource-level diffs. Helm Changes Overview ## Support for Large Terraform Diffs Some Terraform diffs, such as nested kubernetes manifests could not render correctly in the UI. Improved Support for Larger Plans ## Bug Fixes * Fixed issue supporting large plans. * Fixed performance issues in application. * Fixed issue where you could not deprovision an application. * Added a new internal Temporal codec server for better performance. * Fixed an issue creating new org runners. * Fixed an issue where LSP server did not work correctly. * Fixed a bug where skipping some steps could render a workflow to make no progress. * Fixed a bug where apps would not delete with the CLI. Note: Components must still be deleted first. * Fixed a bug where the App's Components tab would error after nuon apps create --no-template and before nuon apps sync. # 014 - Install Config Files Source: https://docs.nuon.co/updates/014-install-config-files Manage Installs via Source Control *August 7th, 2025*
v0.19.614
## Install Config Files You can now manage installs via source control for apps managed by `nuon apps sync`. Create one config file per-install in a folder named `installs/` under your app's config. Now syncing the app will also create/update the corresponding installs. For more details take a look at [Install Config file reference](/configuration-files#install-config). ```toml theme={null} name = "config_demo" approval_option = "approve-all" [aws_account] region = "us-east-1" [inputs] sub_domain = "whoami" test = "required-value" testdefault = "default-value" ``` ## Generate an Install Config File To make it even easier to manage existing installs using CI, we have added a new command `nuon installs generate-config` to export an existing install to stdout. Create a TOML config file for the install in the `installs` folder of your app's config and paste the stout. If required, make updates e.g., you app's release number, and `nuon apps sync` to update the install. In the dashboard, see the newly-created workflow to track the install's upgrade process. ```bash theme={null} cd mkdir -p installs touch installs/.toml nuon installs generate-config -i | tee installs/.toml ``` Generate Install Config ## Workflow Step Groups Workflow steps are now grouped, and some retries will retry an entire group when required. For instance, when retrying a Terraform apply step, the plan can not be reused, so a new group is created and the plan is retried (and re-approved) first. Workflow Step Groups ## Runner Instance Lifetime > **Deprecated**: MaxInstanceLifetime is no longer set on the ASG. Instance > refresh is now handled by a backend cron that gracefully shuts down the > runner before powering off the VM. As part of the install stack, we now set the MaxInstanceLifetime for Runner instances to 7 days by default and recreate it via the autoscaling group. This serves as a safety net in case the runner instance becomes unresponsive for some reason due to accumlated state, such as disk space. ## Bug Fixes * Fixes an issue where you could not cancel an install reprovision. * Fixes an issue where running a `plan-only` action did not update it's status correctly. * Fixes an issue where an install showed a "deprovisioned" status on first setup. * Fixed memory leak that would cause page crashes during workflows. * Removed extra fields on the log read endpoints. # 015 - Azure Support Source: https://docs.nuon.co/updates/015-azure-support Deploy and manage installs on Azure *August 8th, 2025*
v0.19.615
## Azure Support You can now configure, deploy, and manage apps on Microsoft's Azure cloud platform. Nuon had Azure support before we made the switch to the independent runner architecture. When we made that change, we decided to focus on one platform first, AWS, in order to ensure the best possible experience. We always intended to re-introduce Azure support once the new architecture had matured, and we felt that time had arrived. We have taken great care to ensure the Nuon experience is as consistent as possible across both AWS and Azure. Aside from a few small digressions, the experience of configuring, deploying, and maintaining an Azure app should be familiar and require little to no ramp-up time. ### Configuring an Azure App To get started, configure an app using an Azure sandbox and runner. Here's an example using our off-the-shelf Azure AKS sandbox. ```toml sandbox.toml theme={null} # sandbox terraform_version = "1.12.2" [public_repo] directory = "." repo = "nuonco/terraform-azure-aks-sandbox" branch = "main" [vars] public_root_domain = "{{ .nuon.inputs.inputs.root_domain }}" internal_root_domain = "internal.{{ .nuon.inputs.inputs.root_domain }}" location = "{{.nuon.cloud_account.azure.location}}" vnet_name = "{{.nuon.install_stack.outputs.network_name}}" resource_group_name = "{{.nuon.install_stack.outputs.resource_group_name}}" private_subnet_names = "{{.nuon.install_stack.outputs.private_subnet_names}}" public_subnet_names = "{{.nuon.install_stack.outputs.public_subnet_names}}" ``` ```toml runner.toml theme={null} # runner runner_type = "azure" helm_driver = "configmap" ``` You can then add components to the app as you normally would. All component types are supported. Keep in mind that Terraform components will need to create Azure resources using the [azurerm provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest). Many Helm charts may also rely on platform-specific operators. If you want to re-use components across platforms, you must avoid platform-specific dependencies. ### Installing an App on Azure When you create an Azure install, you will be presented with an install creation modal, just as you would for AWS, except with an Azure location selector instead of an AWS region selector. azure_install_modal The provision workflow will create an Azure Resource Manager template, and provide azure CLI commands your customer can use to create a resource group and install the Nuon runner in it. azure_awaiting_stack ### Maintaining an Azure Install Once the install has been provisioned, the experience is practically identical for both cloud platforms. You will have access to the same info and controls for the runner, the sandbox, components, and actions. Currently, the runner does not have the Azure CLI installed. This will be added in a future update, so it can be used for actions on Azure like the AWS CLI can be used on AWS. For more details on Azure, and how it compares to AWS, and our general approach to supporting cloud platforms, see our [Platform Support docs](/platform-support/introduction) # 016 - Enhanced Install Config Files and Improved Plans Source: https://docs.nuon.co/updates/016-enhanced-install-config-files-and-improved-plans Directory and file-level install config file syncing. More clear Helm diffs. Improved performance with optimized state management. *August 19th, 2025*
v0.19.619
## New Installs Sync CLI Command Syncing installs is now independent from app syncing. The new "installs sync" command can be used with a single [Install config file](/configuration-files#install-config) or with a directory containing multiple install config files. ```bash theme={null} nuon installs sync -a -d ``` Installs Sync CLI Command ## Forget Install CLI Command The "forget install" command is now available in our CLI client, bringing feature parity with the dashboard. This gives developers more flexibility in managing their installs directly from the command line. ```bash theme={null} nuon installs forget -i inlcz0349isibg7pyw1f5p8zf5 --confirm ``` ## Improved Plans Added inline YAML diff support to Helm plan UI so Helm plan changes now display with inline YAML diffs for better visibility. improved_diff ## State Management Improvements We've refactored our install state generation and fetching to deliver better performance across the platform. This optimization reduces loading times for Dashboard views that rely on state data and improves memory efficiency in our temporal background processes. ## Bug Fixes * Fixed CLI nuon installs deploy-components command. * Fixed missing stack outputs from state. * Fixed teardown of Docker build images not working. * Upgraded AWS EKS sandbox AWS provider version. * Improved error display for "build is not deployable". # 017 - Noop Approvals, Install Config Improvements and more Source: https://docs.nuon.co/updates/017-noop-approvals-stability-updates Approvals with no changes will automatically be approved, and more stability fixes. August 28th, 2025
v0.19.621
## Resource Drift for Terraform Component Plans We now show resource drift for Terraform Component Plans. Resource Drift ## Install Config Updates For installs managed by a config file, a confirmation in the UI will be shown before making any input changes. Prompt The UI now shows whether an install is managed by a config file or not. UI Label ## Noop Approvals Approvals for deploys with no changes will automatically be approved. Auto Approvals To prevent inadvertently mutating infrastructure due to drift, when noop changes are auto-approved the apply step is skipped. ## Org Search Box For users with many orgs, you can now search for them in the UI: Search ## Stability Improvements We have been rolling out stability improvements for workers that are running in BYOC installs, including: * Workers use less memory. * States will automatically be marked as stale when generating. * Improvements to worker caching. ## Bug Fixes * Removed deprecated sandboxes from [nuonco GitHub org](https://github.com/nuonco). * Improved documentation for [Kubernetes Manifests](https://docs.nuon.co/guides/kubernetes-manifest-components). * Fixed a bug where some orgs would not properly provision due to a timeout. * Fixed an issue where some workflows could fail and not show any user feedback. * Fix for wrong account id set for the manual deploys. * Fix for workflow panics on noop approvals. * Increase CPU allocation for cloud and BYOC workers. * Fixed an issue where some workflow steps required additional retries. # 018 - Management Mode Source: https://docs.nuon.co/updates/018-management-mode Runner Management Mode, input updates, and App Variables for storing values across installs. *Sep 8, 2025*
v0.19.630
## Management Mode Management mode is a standalone runner process that runs on the install runner VM and is responsible for monitoring and managing the health of the VM as well as the runner process itself. Read more about how to enable this for your installs here: [Runner Management Mode](/guides/runner-management-mode). ## Stack Config Breaking Change We fixed a bug where the `runner_nested_template_url` is now recognized, where as before it was ignored. This requires a config update for any apps that currently define a `runner_nested_template_url` in their `stack.toml`, as older versions of the nested template could be invalid or not work. We recommend using the latest nested template url: ```toml theme={null} runner_nested_template_url = "https://nuon-artifacts.s3.us-west-2.amazonaws.com/aws-cloudformation-templates/v0.1.6/runner/asg/stack.yaml" ``` ## Sensitive Inputs and Install Config Files When you generate a config, inputs will now be grouped together, reflecting the input groups you have defined in the app. Sensitive inputs will be excluded, to avoid saving them in plain text in version control. ```toml theme={null} name = "my_install" [aws_account] region = "us-east-1" [[inputs]] use_insecure_cookies = "false" [[inputs]] bool_type = "true" json_type = "{\"key\": \"value\"}" number_type = "10" ``` When syncing install config files, sensitive inputs will be ignored. They can still be managed manually via the dashboard. ## App Variables App Variables is a feature that allows you to define values you can use across all installs of an app. The values are not managed in the app itself, and therefore do not need to be stored in version control, and are not displayed to your customer during the install process. You can create app variables via the CLI. ```sh theme={null} nuon apps variables create --name=my_var --value="some value" ``` To use an app variable, reference it in your app with a template expression. ```toml actions/demo_action.toml theme={null} # action name = "simple_demonstration" timeout = "1m" [[triggers]] type = "manual" [[steps]] name = "show the value of my_var" inline_contents = """ #!/usr/bin/env sh echo $MY_VAR """ [steps.env_vars] MY_VAR="{{.nuon.app.variables.my_var}}" ``` Learn more about App Variables here: [Concepts - App Variables](/concepts/app-variables). ## Orderable Action Triggers It is often useful to be able to define an explicit order for actions. We are introducing a new optional field ,`index`, on action triggers that allows you to define an order for actions with the same trigger. For example, you can define a trigger like this in one action: ```toml theme={null} # post_cert_deploy_action_1.toml [[triggers]] type = "post-component-deploy" component_name = "certificate" index = 1 ``` and in another action: ```toml theme={null} # post_cert_deploy_action_2.toml [[triggers]] type = "post-component-deploy" component_name = "certificate" index = 2 ``` This will result in the actions running in the order defined by `index`. ## Bug Fixes * Nested Runner ASG Cloudformation Stack is now respected and used during install stack re/provisions. * Fixed a bug causing workflow cancellation to intermittently fail. * Fixed a bug where large plans could cause failures when being checked for noop plans. # 019 - Self-Service Cloud Registration Source: https://docs.nuon.co/updates/019-self-service Anyone can now sign up for Nuon Cloud *Sep 30, 2025*
v0.19.649
## Self-Service Cloud Registration Anyone can now sign up for Nuon Cloud, without needing an invite. To get started, go to [https://app.nuon.co](https://app.nuon.co) and click "Sign Up". Sign Up After you sign in, a trial org will be created for you, and you will be sent to our onboarding flow. You can then select one of our example apps to get started with. Select an App Once you have an app, you can create an install. Select an App At any point during this process, you can click the "Skip" button to exit the onboarding flow and do your own thing. ## CLI UX Improvements We have landed a workflow TUI in the `nuon` cli for viewing and approving workflows. Workflow TUI This feature requires the `NUON_PREVIEW` to be set to `true` in the environment. You can try it like this: ```bash theme={null} nuon installs workflows-get -w inwxxxxxxxxxxxxxxxxxxxxxxx ``` This is part of a concerted effort to provide rich functionality directly in the TUI. ## Improved Helm Drift Detection When you run a plan-only deploy, or are doing a noop deploy with no expected changes to a Helm chart, we will now detect any resource changes. Previously, the diff functionality only included changes to the chart values or rendered yaml, and did not account for drift in the cluster. ## Bug Fixes and Improvements * Fixed a bug where uninstalling a Helm chart could fail because the diff could not be loaded. * We have rolled out changes to our networking stack in the dashboard to decrease network usage, prevent unexpected errors (or hnadle them more gracefully) and also make the application feel more performant. * We fixed a bug where some endpoints in the UI and some plans would not use the current install's app-config, leading to situations where components could not be deleted, or would be incorrectly rendered on the graph. * Fixed a bug causing new actions to now work for a few seconds after syncing an app config. * Fixed a bug where some workflows could fail if there were too many steps, too large of plans or were not approved in time. * Fixed a bug where the install component summary endpoint did not always succeed. # 020 - Break glass actions Source: https://docs.nuon.co/updates/020-break-glass-actions Define actions that customers can enable to break glass. *Oct 7, 2025* Define actions that can use break glass roles for executing privileged operations.
v0.19.662
## Actions with break glass permissions Actions can now utilize break glass roles and policies that have been enabled via an install CloudFormation stack. This allows for elevated permissions during critical operations while maintaining security boundaries. ### Break Glass Role Configuration Break glass roles are enabled in CloudFormation and can be referenced in your action configurations. These roles provide temporary elevated access for emergency situations or critical deployments. Sample break glass configuration: ```toml break_glass.toml theme={null} [[role]] name = "bucket-operations-break-glass" description = "Grants access to the install bucket" display_name = "Bucket operations Break Glass" permissions_boundary = "" [[role.policies]] name = "bucket operations" contents = """ { "Version": "2012-10-17", "Statement": [ { "Sid": "SpecificS3BucketAccess", "Effect": "Allow", "Action": [ "s3:CreateBucket", "s3:DeleteBucket", "s3:ListBucket", "s3:GetBucketLocation", "s3:GetBucketVersioning" ], "Resource": "arn:aws:s3:::{{.nuon.install.id}}-*" }, { "Sid": "ListAllBuckets", "Effect": "Allow", "Action": [ "s3:ListAllMyBuckets" ], "Resource": "*" } ] } """ ``` To configure an action with break glass permissions, specify the break glass role name in your action definition: ```toml action.toml theme={null} name = "simple_demonstration" timeout = "1m" break_glass_role="bucket-operations-break-glass" [[triggers]] type = "manual" [[steps]] name = "manage install-specific bucket" inline_contents = """ #!/usr/bin/env sh set -x BUCKET_NAME="{{.nuon.install.id}}-demo-bucket-$(openssl rand -hex 4)" aws s3 mb s3://$BUCKET_NAME --region us-east-1 aws s3 ls | grep {{.nuon.install.id}} aws s3api delete-bucket --bucket $BUCKET_NAME --region us-east-1 """ ``` ### CloudFormation Stack Integration The break glass role and associated policies are created through a CloudFormation stack that you deploy to your AWS account. This stack creates: * A break glass IAM role with elevated permissions * Policies that define the scope of emergency access * Trust relationships that allow Nuon runners to assume the role Once enabled, the roles can be referenced in your actions, providing secure access to elevated permissions when needed. ### Security Considerations Break glass roles enable a customer to temporarily grant elevated permissions and should be used with caution. They are primarily designed for mitigating customer issues, running migrations and other opt-in operations that require a customer to grant temporary elevated permissions. ## Nuon Workflows TUI You can now run entire workflows via our experimental TUI. To interact with workflows, you can use the following command: ```bash theme={null} NUON_PREVIEW=true nuon installs workflows ``` You will then see all recent workflows, and can select a workflow to enter the TUI view: Nuon Workflows TUI ## Bug Fixes and Improvements * Fixed an issue where some Nuon Workflows would not generate if too many actions or components were defined. * Fixed an issue where some Nuon Workflows could fail silently. * Reduced logging volume by not logging requests unless debug flags are enabled. * Reduced metrics volume by pruning some redundant metrics. # 021 - Drift Detection for Components and Sandboxes Source: https://docs.nuon.co/updates/021-drift-detection Detect infrastructure drift automatically on your installations *Oct 13, 2025*
v0.19.662
## Drift Detection Drift detection allows you to automatically identify discrepancies between your defined infrastructure and what's actually running. You can now configure periodic drift detection checks for components and sandboxes through a simple cron schedule. ## Features ### Drift Detection for Sandboxes You can now define a cron schedule in your `sandbox.toml` that periodically checks for drift by performing a "terraform plan": ```toml theme={null} terraform_version = "1.11.3" drift_schedule = "0 * * * *" # Runs every hour at minute 0 ``` ### Drift Detection for Components Similarly, drift detection can be configured at the component level for Terraform, Helm, and Kubernetes Manifest components. Example for a Helm component: ```toml theme={null} name = "helm_chart" type = "helm_chart" chart_name = "nuon" namespace = "{{.nuon.install.id}}-test" storage_driver = "configmap" drift_schedule = "0 * * * *" # Runs every hour at minute 0 ``` > **Note:** Changes to drift\_schedule require rebuilding the sandbox or component to take effect. ### Drift Visibility The install components page now clearly highlights components that have drifted from their defined state and provides direct links to the relevant workflows to review the differences. Dashboard to view drifted component Install workflows page also marks the workflows that are drift scans. Install workflows flagging drift scans ## Limitations **Current Workflow Constraint:** At present, only one workflow can run per install at a time. This means: * If a drift detection workflow is running when you attempt to manually deploy a component or reprovision a sandbox, your workflow will be queued until the drift workflow completes. * We're actively working to remove this limitation in an upcoming release. ### Deny plans and continue workflow We can now deny plan and continue with the workflow with rest of the component. Deny button comes with additional option to deny and skip steps. Denied and continued plan ### Actions TUI Actions and Action Runs for installs can now be viewed via the cli. ```bash theme={null} NUON_PREVIEW=true nuon installs actions ``` Actions TUI ### Nuon Runner Cloudformation Stack The stack has been updated to remove a policy in order to reduce permissions available to the Runner ASG instances. It is recommended to update the stack to version `v0.1.8`. ## Bug Fixes and Improvements * Improvements to generate steps to improve resilience in Temporal. * Fix to disable server side apply on helm client for components. * Fix where some Nuon plans can not be generated if outputs are not ready yet. * Fix where some app configs from older cli versions (pre 0.19.620) would not correctly build. * Fix where some build runners could run out of memory and not recover. # 022 - Nuon CLI Github Action Source: https://docs.nuon.co/updates/022-nuon-cli-github-actions GitHub Action for setting up the Nuon CLI in CI/CD workflows *Oct 17, 2025*
v0.19.673
## Nuon CLI GitHub Action A GitHub Action is now available for setting up the Nuon CLI in your CI/CD workflows. Here's an example of how the action can be used: ```yaml theme={null} - name: Run Nuon command uses: nuonco/actions-nuon@v1 with: org_id: ${{ secrets.NUON_ORG_ID }} api_token: ${{ secrets.NUON_API_TOKEN }} command: 'orgs current' ``` Take a look at [nuonco/actions-nuon GitHub repo](https://github.com/nuonco/actions-nuon) for documentation. ## Improvements * Onboarding flow can now be re-opened from the sidebar. ## Bug Fixes * Fixed various issues with Terraform backend download. * Fixed bug causing user to get stuck in onboarding if they close the view before creating an org. * Fixed bug causing sandboxes to not reprovision after input update. * Fixed bug causing helm diffs to not render changes. * Assorted small UI and API fixes. # 023 - Drift Scanning Improvements Source: https://docs.nuon.co/updates/023-drift-workflow-improvements Enhanced drift detection capabilities. *Oct 27, 2025*
v0.19.678
## Drift Scanning Enhancements We've expanded our drift detection capabilities with new scanning features. **Component Drift Scanning**: You can now trigger drift scans directly from individual component pages to detect configuration drift at the component level. Component Drift Scanning **Sandbox Drift Scanning**: Added drift scan functionality for sandbox environments, allowing you to identify discrepancies between expected and actual sandbox states. **Break Glass Action Info**: Added comprehensive break glass information to action pages, giving you better visibility into emergency access procedures and their usage. ## Bug Fixes and Improvements * **Log Streams**: Fixed issue resulting in log streams not being closed properly. * **Component Configuration**: Resolved issues with TOML parsing and component capability handling. * **Action Validation**: Resolved action run validation errors that were preventing successful action execution. * **Monitoring**: Added frontend endpoint timing metrics for better observability. # 024 - Improvements to Install Inputs Source: https://docs.nuon.co/updates/024-install-inputs-ui-redesign Install inputs via CloudFormation stacks, and user-configurable inputs. *Nov 5, 2025*
v0.19.688
## Install Inputs Install input parameters can now be integrated into your CloudFormation stack, allowing users to add them when installing your application. You can now also distinguish between vendor-provided inputs and user-configurable inputs, with vendor as the default source type. ## Sign-Up Updates Support users are now added to trial orgs, to provide support for new users. ## Bug Fixes * Fixed issue with steps ordering during teardown processes. * Removed empty input groups from CloudFormation templates. * Improved resilience to state issues caused by cookies. * Fixed rendering of break glass roles on install actions page. * Install will be put into "mangaed-by-config" mode, even if there are no changes to sync. * Install configs will generate even if inputs are empty. # 025 - Temporal Workflow Stability Improvements Source: https://docs.nuon.co/updates/025-temporal-stability Fix issues with component workflows retrying. *Nov 10, 2025*
v0.19.690
## Temporal Workflow Stability Improvements Fixed an issue where temporal workflows were being continuously retried when an install was created and not deployed. This was resulting in extra memory pressure on the temporal workers and postgres instance. ## Bug Fixes * Fixed a null pointer issue with missing input values. * Fixed validation responses now returning 400 level responses instead of 500s. # 026 - Helm Repository Support Source: https://docs.nuon.co/updates/026-helm-repository-support Deploy charts directly from public Helm repositories. *Nov 12, 2025*
v0.19.691
## Helm Repository Support Helm Components now support deploying charts directly from public Helm repositories via the `helm_repo` block. This allows you to use any chart available on public repositories like the Prometheus Community charts without needing to maintain your own Git repository. To use a public Helm chart, specify the repository URL and chart name: ```toml theme={null} [helm_repo] repo_url = "https://prometheus-community.github.io/helm-charts" chart = "kube-prometheus-stack" ``` This is equivalent to the Helm CLI workflow: ```bash theme={null} helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install my-release prometheus-community/kube-prometheus-stack ``` You can discover public Helm charts on [Artifact Hub](https://artifacthub.io/), which indexes thousands of open-source Helm charts. See the [Helm Chart Components](/guides/helm-chart-components#using-public-helm-repositories) guide for more details. # 027 - Install config toggle Source: https://docs.nuon.co/updates/027-install-config-toggle Clearer install config mode switching. *Nov 20, 2025*
v0.19.707
## Install Config file improvements ### Toggle Install Config file syncing Users can now enable or disable syncing an Install from a config file, from dashboard as well as CLI. **Dashboard:** You can find the toggle to enable/disable config file syncing in the management menu of an Install. New install config toggle button
New install config toggle button **CLI** The CLI features a new command to toggle config file syncing for an Install. Run `nuon installs toggle-sync` to toggle the config file syncing, or use `--enable` or `--disable` to explicitly set the syncing status. New CLI command installs toggle-sync > **Note:** Please upgrade the CLI to `0.19.708` to avoid seeing the error `unable to sync installer - &{ false} (*models.StderrErrResponse) is not supported by the TextConsumer` when running `nuon apps sync`. ### Required Inputs Install Config files now need to have the required inputs defined explicitly. Now it is not possible to set their value from UI, or utilising the default value implicitly. ### UI Refresh We've been rolling out our new design system to users, bringing a more modern and polished look to the dashboard. This update includes refreshed UI components, improved visual hierarchy, and a cleaner overall aesthetic for developer workflows. If you would like to use the updated UI, reach out to our team so we can enable it for your organization. We would love to hear your feedback! Stratus screenshot ### Mermaid Diagrams in READMEs READMEs now support [Mermaid](https://mermaid.js.org/) diagram rendering. Mermaid diagram screenshot ## Bug Fixes * UI: Fixed missing badge for Install managed by config file in the header. * CLI: Fix empty diff when syncing a new install using a config file. * CLI: Syncing an Install without having Install Config syncing enabled now results in an error. These only produced a warning before, going unnoticed in CI workflows. * UI: Fixed missing API URL in Terraform backend config download. * Azure: Fixed phone home script in Azure install stack. * CLI: Fixed new input values not syncing from install config files * Azure: Correctly load and display azure stack outputs # 028 - Nuon is now Open Source Source: https://docs.nuon.co/updates/028-open-source Nuon's core platform is now open source, plus new features and improvements. *Dec 24, 2025*
v1.0.0rc
## Nuon is now Open Source 🎉 We're excited to announce that **Nuon is now open source**! The core platform powering BYOC deployments is now available on GitHub at [github.com/nuonco/nuon](https://github.com/nuonco/nuon). This release marks a major milestone for Nuon, enabling the community to contribute, inspect, and build upon the platform. ## Nuon Labs We've launched [Nuon Labs](https://labs.nuon.co/) — a place where we'll announce new and upcoming features, share experimental ideas, and give you early access to what we're building next. Check it out to stay ahead of the curve! ## New Features ### Organization Dashboard A new organization dashboard provides visibility into your org's stats and usage at a glance. Organization Dashboard ### Real-time Streaming Logs Logs throughout the dashboard now stream in real-time using Server-Sent Events (SSE), giving you instant visibility into workflow execution without manual refreshing. ### CLI `--no-input` Flag The CLI install script now supports a `--no-input` flag for fully non-interactive installations, making it easier to integrate into CI/CD pipelines and automation scripts. ```bash theme={null} curl -sSL https://install.nuon.co | bash -s -- --no-input ``` ### VS Code Extension & Language Server Nuon now includes a Language Server Protocol (LSP) implementation with a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Nuon.nuon-lsp), providing autocomplete, validation, and inline documentation for Nuon configuration files. ### Templatable Stacks Stacks now support templating, allowing you to parameterize and reuse stack configurations across different environments and installs. ## Bug Fixes * UI: Fixed invalid state issue during multi-tab login * UI: Login page button now uses proper `` tag for accessibility * API: Fixed missing `org_id` bugs * CLI: Fixed `nuon delete` command * API: Fixed post-install validation response code * API: Fixed error handling when getting workflow logger # 029 - BYO-VPC, Auth & UX Improvements Source: https://docs.nuon.co/updates/029-auth-inputs-better-nested-templates BYO-VPC support, Nuon Auth service, Runner Auth, and numerous UX improvements *Feb 3, 2026*
v0.19.770
## Better Support for Custom Nested VPC Templates We've improved handling of nested VPC stack parameters to better support custom templates for `byo-vpc` (bring your own VPC) and `byo-eks` (bring your own EKS Cluster) deployments. Parameters from nested VPC stack templates are now dynamically loaded and "hoisted" into the parent stack in the "VPC Configuration" parameter group. ### Stack Config Breaking Change Breaking Change Apps using the nuon `vpc/eks/default` nested VPC stack should upgrade to `v0.1.12`. This change is a noop on CloudFormation but addresses an issue with earlier versions that affected strict YAML parsers. ## Nuon Auth Service Nuon Auth BYOC installs now have the option of using the new Nuon Auth service which adds support for `google` and generic `oidc` providers. Users can bring their own IdP and Nuon BYOC no longer has a dependency on Auth0. Key features: * Suport for multiple IdPs * Device code login flow for CLI authentication * Configurable allowed domains ## Runner IAM Authentication Runners can now authenticate using an AWS IAM-based scheme with pre-signed requests. This improves security, simplifies install provisioning, and reduces friction during runner token refresh. ## Onboarding UX Improvements Moved new user onboarding to it's own page, to make entering and leaving the flow more intuitive. ## Install Form Improvements Install form values are now saved as drafts preventing the need to re-input a large set of inputs if the install modal is closed. ## Customer and Vendor Inputs App authors can now define inputs as customer or vendor inputs. This is used to control which inputs a customer has control over in the upcoming customer dashboard. ## Forget Install Component New feature to "forget" an install component, removing it from an app. Intended to be used for components that have already been torn down. ## Drift Detection UI Updated drift detection elements for better visibility into infrastructure state changes. ## Dashboard UX Improvements * New sign up page design * Stratus design system updates across app inputs, team page, install forms * Improved VCS connection experience * Better action run outputs display * SSE log connection improvements * Auto-approved status display for workflow steps * Improved async boundary patterns for better loading states ## Kustomize Support Kubernetes manifest components now support Kustomize for more flexible manifest management. ## Bug Fixes * Fixed empty string handling in edit inputs form * Fixed platform passing on create install form * Fixed handling of Temporal workflow not found during cancel * Fixed short circuit for action crons if install not provisioned * Fixed app config sync not migrating existing install inputs * Fixed VCS validation handling * Fixed multi-tab login invalid state issue # 030 - Policies Source: https://docs.nuon.co/updates/030-policies Enforce compliance, security, and operational standards across component deploys, sandbox runs, and external images. *Feb 19, 2026* ## Policies Nuon now supports **Policies** for component deployments, sandbox runs, and external container images. Nuon policies now extends beyond the existing in-cluster Kyverno policies to give you control over your entire deployment pipeline. ### What's New * **Deploy-time policies**: Validate Terraform plans, Helm charts, and Kubernetes manifests before changes are applied. Enables blocking of deployments with `deny` rules and logging warnings with `warn` rules * **Sandbox policies**: Enables enforcing standards on sandbox infrastructure provisioning * **External image policies**: Validate container image metadata during builds to check for signed images, SBOMs, and attestations and provenance before images are deployed into an install * **Policy reporting**: View and filter all policy reports across installs by status, type, for compliance auditing and governance reporting ### Learn More * [Policies Concepts](/concepts/policies): Understand how policies work, supported types, and evaluation phases * [Configuring Policies Guide](/guides/configuring-policies): Step-by-step walkthrough for adding policies to your app * [Example Policies Repository](https://github.com/nuonco/policies): Ready-to-use policy examples for Terraform and Kubernetes ## Custom Nested CloudFormation Stacks The install stack, `stack.toml`, now supports custom nested CloudFormation templates. This enables the provisioning of additional AWS or Kubernetes resources before the sandbox or any components simplifying the permissions story and enabling two new deploy methods: `byo-vpc` and `byo-eks`. Custom stacks are configured via `[[custom_nested_stacks]]` blocks in `stack.toml`. These are ideal for use cases like creating Kubernetes namespaces, EKS access entries, dedicated subnets, or transit VPCs. See the [Custom Nested Stacks guide](/guides/custom-nested-stacks) for full details. ## Introducing Ad-hoc Actions Run one-off bash scripts or commands on installs for debugging and maintenance without creating permanent action config, with full UI support including a code editor component. ## Nuon LSP Improvements ### Added * Workspace-wide (multi-file) diagnostics. * Rich hover documentation with signatures, required/deprecated badges, constraints, and examples. ## Dashboard UX Improvements * Policy reports UI with filtering and dedicated policy detail pages. * Auto-approve option on the install creation. * Improved install stack page with stack version details and config visibility * Redirect to workflow page after manual action runs with improved log filtering * Stratus design system updates for component config, install tables, and workflow filters * Break glass role display fix for unprovisioned installs. ## Bug Fixes * Fixed Kubernetes manifest plan rendering * Fixed manual action now display the workflow name instead of a generic message * Fixed handling of null props in action run outputs rendering * Made `CreateApprovalResponse` idempotent to prevent duplicate approval errors on workflow retries * Bumped default Helm timeout to 30 minutes to avoid bad state on initial deploy failures * Fixed CloudFormation stack to omit secrets section when none are configured * Fixed empty allowed domains env var handling in auth service # 031 - Operation Roles Source: https://docs.nuon.co/updates/031-operation-roles Assign different roles to different operations across sandbox, components, and actions for least-privilege access control. *Feb 26, 2026* ## Operation Roles Nuon now supports **operation roles** — a flexible system for assigning distinct roles to individual operations across your app's sandbox, components, and actions. Instead of relying on a single maintenance role for everything, you can grant exactly the permissions each operation needs and nothing more. ### What's New * **Entity-level roles**: Assign specific roles inline on `sandbox.toml`, component configs, and action configs. Entity roles take precedence over app-wide matrix rules * **App-wide matrix rules** (`operation_roles.toml`): Define a central lookup table that maps entity principals and operations to role names, with support for wildcards (`nuon::component:*`, `nuon::action:*`) * **Runtime role override**: Pass `--role ` via the CLI or use the role selector in the dashboard to override the role for a single run without changing any config * **Break-glass role for actions**: The existing `break_glass_role` field on actions is now part of the formal precedence chain, sitting above entity roles for emergency elevated access * **Role name templating**: All role name fields support Go template syntax (e.g., `{{.nuon.install.id}}-deploy-role`) for per-install role isolation ### Role Selection Precedence When the runner selects a role for an operation it uses the first match from: 1. Runtime override (CLI `--role` flag or dashboard selection) 2. Break-glass role (actions only) 3. Entity role (inline `operation_roles` block or `role` field) 4. Matrix rule (`operation_roles.toml`) 5. Default role (`permissions.toml`) ### Valid Operations by Entity | Entity | Operations | | --------- | ----------------------------------------- | | Sandbox | `provision`, `reprovision`, `deprovision` | | Component | `deploy`, `teardown` | | Action | `trigger` | ### Learn More * [Operation Roles Concepts](/concepts/operation-roles): Understand the mental model, precedence chain, and role name resolution * [Operation Roles Guide](/guides/operation-roles): Step-by-step configuration for entity roles, matrix rules, CLI overrides, and templating # 032 - Nuon TUIs and Extensions, Dashboard UX, and Role Management Updates Source: https://docs.nuon.co/updates/032-extensions-and-roles We're shipping CLI workflow TUIs, CLI Extensions to GA, dashboard-ui shipped major workflow and onboarding improvements, and ctl-api expanded operation role support. *Mar 25, 2026*
Since v0.19.821
## Workflow TUI The install workflows TUI is now generally available in the `nuon` CLI. The workflow TUI provides a single place to follow workflow progress in real time, inspect step details, and take action all from the terminal. workflow-tui Run `nuon installs workflows` to open a guided, interactive view of your install workflows. From there, you can review plan diffs, approve steps, retry failed actions, and cancel workflows when needed. The experience is designed to make day-to-day operations faster and easier for teams that work directly in CLI-first environments. ## Introducing CLI Extensions We are introducing a way to extend the functionality of the Nuon CLI via custom extensions. Extensions are generally available and work as first-class `nuon` commands. This enables teams to build tailored workflows directly into the CLI usage. We've authored a few extensions which are available now. For example, with `nuon api`, you can browse and call Nuon's public API from the command line using a spec-driven client that supports interactive discovery and script-friendly output. Run `nuon extensions` to explore extensions. ``` nuon extensions browse NAME VERSION INSTALLED REPO DESCRIPTION ────────────────────────────────────────────────────────────────────────────────────────────────────── api v0.19.821 * nuonco/nuon-ext-api Nuon Extension: API Client render v0.1.5 * nuonco/nuon-ext-render Nuon Extension: Utility to render app c… ``` Try out the `api` extension. ``` nuon extensions install nuonco/nuon-ext-api nuon api --help ``` ### Learn More * [Nuon CLI Extensions](/guides/cli-extensions) ## Dashboard UI Changes and Improvements The Nuon dashboard has focused on two themes: moving key product surfaces onto the SPA/BFF architecture and making install operations more observable and easier to act on. ### Platform and Navigation * Migrated major product surfaces to the SPA pattern, including onboarding, install components, admin panel, audit logs, and install config download flows. * Improved navigation behavior with empty-route redirects, safer login redirects, and org-level dashboard redirects when enabled. * Simplified and modernized dashboard presentation with better page titling, icon usage, scroll ergonomics, and version rendering consistency.