Make behavior-based overrides
If configuration overrides aren’t enough — for example, you need to skip a step, add new logic, or replace how something works — then it’s time to use a behavior override.
Behavior overrides let you change how a scope runs by customizing its workflows. You can:
- Skip or replace workflow steps
- Add new scripts or logic
- Inject behavior based on conditions (like platform type or scope kind)
This is especially useful when you're building advanced scopes (like background workers or scheduled tasks) and need full control over runtime behavior.
How it works
Each scope defines its lifecycle as a set of ordered YAML steps (e.g., create
, initial
, delete
). These
workflows live in the base scope repo.
You can override those steps by placing your own workflow files in the same path inside your override repo. When the agent detects these files, it uses them instead of the default ones.
You can:
action: skip
– ignore specific stepsaction: replace
– fully replace a step with a custom script- Insert your own named steps using
type: script
,type: command
, etc.
Note: See the lifecycle execution flow for scope actions for more details.
Repo structure
Place workflow overrides under scope/workflows/
or deployment/workflows/
depending on the lifecycle action you're
customizing.
your-override-repo/
├── scope/ # Scope lifecycle actions
│ └── my_domain_generator
│ └── workflows/
│ ├── create.yaml
│ ├── update.yaml
│ └── ..
├── deployment/
│ └── build_tolerations
│ └── workflows/
│ ├── initial.yaml
│ ├── blue_green.yaml
├── values.yaml # Your configuration overrides
└── ..
After changing your override repo, you must restart the agent so it loads the updated logic.
Example 1: Skip DNS configuration
By default, the create
lifecycle step includes logic to generate a DNS record. If you're building something that
doesn’t need networking — like a background worker — you can skip this step.
Create a create.yaml
in your overrides repo:
your-override-repo/
├── scope/ # Scope lifecycle actions
│ └── workflows/
│ ├── create.yaml
And add a step like this:
steps:
- name: networking
action: skip
When this override is active, the networking
step won't run during scope creation.
Example 2: Add tolerations for arm64 deployments
Say you want your application to run only on arm64 nodes. Kubernetes uses tolerations for this. You can inject them dynamically using a script override.
What you’ll do:
- Create a script that checks the asset's platform (e.g.
arm_64
) - Add tolerations to the deployment context if needed
- Insert that script into the deployment workflows (
initial.yaml
,blue_green.yaml
)
File structure
These are the files you'll need to create throughout the guide.
your-override-repo/
├── deployment/
│ └── build_tolerations # your shell script
│ └── workflows/
│ ├── initial.yaml
│ ├── blue_green.yaml
└── ..
-
In your
build_tolerations
script:#!/bin/bash
# Find the platform of the asset being deployed
ASSET_PLATFORM=$(echo "$CONTEXT" | jq -r .asset.platform)
MODIFIERS=$(echo "$CONTEXT" | jq .k8s_modifiers)
# ARM64 assets require a toleration to target the correct Kubernetes node
if [[ "$ASSET_PLATFORM" == "arm_64" ]]; then
TOLERATIONS='[{"key": "architecture", "operator": "Equal", "value": "arm64", "effect": "NoSchedule"}]'
echo "The asset is built for arm, adding tolerations to the deployment"
echo "$TOLERATIONS" | jq .
# Add the toleration to the modifiers and
# store it in the context so it is included in the deployment manifest
MODIFIERS=$(echo "$MODIFIERS" | jq --argjson tolerations "$TOLERATIONS" '
if .deployment then
.deployment.tolerations = (.deployment.tolerations // []) + $tolerations
else
.deployment = {"tolerations": $tolerations}
end
')
CONTEXT=$(echo "$CONTEXT" | jq --argjson modifiers "$MODIFIERS" '.k8s_modifiers = $modifiers')
fiThis script modifies the deployment context only if the target architecture is arm64.
-
Then, add the step in
initial.yaml
:steps:
- name: build_tolerations
type: script
file: "$OVERRIDES_PATH/deployment/build_tolerations"
after: build context -
Repeat the same step in
blue_green.yaml
if you're using both strategies.
$OVERRIDES_PATH
points to the root of your override repo inside the agent. It’s set in your notification channel
configuration as the –override-path
.
Example path: /root/.np/your-org/your-repo
In both workflows, we've added a build_tolerations
step that runs your custom script right after the build context
step, allowing the scope to inject tolerations dynamically during deployment.
Example 3: Generate your own domain
Scopes automatically generate a domain name during the create step. If you want to control the format of that domain, you can override just the step that generates it.
File structure
your-override-repo/
├── scope/ # Scope lifecycle actions
│ └── my_domain_generator
│ └── workflows/
│ ├── create.yaml
└── ..
Here’s how to do it:
Add the replace step in create.yaml
.
steps:
- name: networking
steps:
- name: generate domain
action: replace
type: script
file: "$OVERRIDES_PATH/scope/my_domain_generator"
This tells the agent to replace the generate
domain step inside the networking subworkflow with a step that runs the
my_domain_generator
script.
Here’s a basic example of what the my_domain_generator
script might look like:
SCOPE_SLUG=$(echo $CONTEXT | jq .scope.slug -r)
SCOPE_DOMAIN="$SCOPE_SLUG.acme_corp.com"
# Notify nullplatform the new domain_name
np scope patch --id "$SCOPE_ID" --body "{\"domain\":\"$SCOPE_DOMAIN\"}"
# Add the scope domain to the current and export as a global environment variable
CONTEXT=$(echo "$CONTEXT" | jq \
--arg scope_domain "$SCOPE_DOMAIN" \
'.scope.domain = $scope_domain')
export SCOPE_DOMAIN
Now your scope uses a custom domain like my-scope.acme_corp.com
instead of the default.
What’s next: Combine both override types
Now that you’ve seen both configuration and behavior overrides, you can combine them to build advanced, production-ready scopes.
- See the full example: Scheduled task scope