Infrastructure as Code /
Write a template
Templates are written in JSON, and consist of objects that define:
- the template structure
- Northflank resources to create or update
- conditions to check
- actions to perform
Templates can be used to create and manage projects, project resources, and team integrations.
When you create a template you can either edit the template as JSON, or use the visual editor to drag and drop nodes. Learn more about creating templates with the Northflank template editor and viewing specifications for your existing projects and resources.
This guide will first introduce best practices for writing templates. It will then run through the structure of a template and how to use node references and arguments, which are important to creating programmatic templates that are easy to modify and reuse. The different types of nodes and their properties will then be detailed, before providing some examples of templates.
Properties not mentioned in the application documentation may only be applicable to the Northflank API, and should not be included in templates managed using the Northflank application or a Git repository.
Template structure
Your template must include information about the template itself, such as apiVersion
and name
(and optionally a description
).
The spec
object contains the body of the template, that is the workflows, conditions, resources, and actions that will be executed during a template run.
If you create or edit a template via the Northflank application you can configure all the template options on the settings page. See the API page for create template for the full specification.
The top level of a template looks like this:
{
"apiVersion": "v1.2",
"name": "template-name",
"description": "Your template description",
"arguments": {},
"argumentOverrides": {},
"options": {},
"spec": {
...
}
}
If you are writing a template as a JSON file the required fields, listed in the attributes below, must be included. You can also see the examples further down on this page of complete templates.
- {object}
apiVersion
string requiredThe version of the Northflank API to run the template against.one ofv1.2name
string requiredName of the template.description
stringDescription of the template.arguments
{object}A set of arguments that can be referenced in a template using '${args.argumentName}'.argumentOverrides
{object}Argument overrides stored outside of the template. If GitOps is enabled, these will not be saved in version control.options
{object}Additional options for the template creation.autorun
booleanIf true, the template will run automatically whenever it is updated.concurrencyPolicy
stringDefines the concurrency behaviour of the template with respect to parallel runs.one offorbid, allow, queuespec
{object} requiredContains nodes representing actions to be performed as part of the template.
Execution order
Nodes in sequential workflows will be executed one after the other whereas nodes in parallel workflows will be executed at the same time. Sequential workflows can be nested inside parallel workflows, the nodes in the sequential workflows will be executed in order, but each sequential workflow will run concurrently.
Release flow and preview environment templates
You can include templates for preview environments and release flows within pipeline nodes. See pipeline nodes for more detail.
Project context
Every node for a project-level resource must have a project context. You can either set the project context directly in the resource node specification, or the node can inherit the project context from a parent workflow.
You can create and update resources across multiple projects by setting different project contexts for workflows or nodes.
You can set the context as an existing project from your team, or create a new project in the template. If you are using a reference to set the project context, the project node must run before the reference is used in the template.
If you run a template with nodes that require, but do not have, a project context, the template run will fail.
Set project context on a workflow
The example below creates a new project with a reference, before defining a workflow with that project as the context. Any resources included in this workflow will be created in that project.
{
"apiVersion": "v1.2",
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {},
"steps": [
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "New Project",
"description": "This is a new project.",
"color": "#0A5BA5",
"region": "europe-west"
}
},
{
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {
"projectId": "${refs.project.id}"
},
"steps": []
}
}
]
}
}
}
Set project context for a node
Project context can be supplied to nodes in the specification, either as a hardcoded project ID, or as a reference. This will override the project context inherited from a workflow, if any exists.
{
"kind": "DeploymentService",
"spec": {
"name": "nginx",
"projectId": "my-project",
"billing": {
"deploymentPlan": "nf-compute-50"
},
"deployment": {
"instances": 1,
"external": {
"imagePath": "library/nginx:latest"
}
},
"ports": [
{
"name": "port-01",
"internalPort": 80,
"public": true,
"protocol": "HTTP"
}
]
}
}
Arguments and argument overrides
You can include arguments in your template, referenced in the format ${args.<argument-name>}
, replacing <argument-name>
with your key. This is useful if you have a value you wish to dynamically generate, change on subsequent template runs, or which is used repeatedly throughout the template.
Arguments are stored in the argument object at the top-level of the template as key-value pairs. You can also set argument values in the UI using the template form's arguments override section.
It can be useful to include the keys for sensitive data in your arguments object, but you should not include their values.
Argument overrides
Argument overrides can be used to inject secure values into your template, or override existing argument values. Argument overrides are stored securely on Northflank, separately from your template.
Key-value pairs in the argumentOverrides
object will override arguments with the same key in the arguments
object. If the key specified in the overrides object does not exist in the arguments object, it will be inserted.
If you are using GitOps these will not be saved in your repository if you add the argument overrides in the Northflank UI using the template form. You should not commit argument overrides in your template.
You can supply argument overrides using the API by including an argumentOverride
object at the top-level of the template, containing the key-value pairs of arguments to override.
Run on creation overrides
If you are creating a template using the API you can also specify runOnCreationArgumentOverrides
in options
, which will only be used when the template is created if runOnCreation
is set to true
.
{
"name": "Example template arguments",
"description": "This is a sample template.",
"apiVersion": "v1",
"arguments": {
"ARGUMENT_1": "default_1",
"ARGUMENT_2": "default_2",
"ARGUMENT_3": ""
},
"argumentOverrides": {
"ARGUMENT_1": "hello",
"ARGUMENT_2": "world",
"ARGUMENT_3": ""
},
"options": {
"runOnCreation": true,
"runOnCreationArgumentOverrides": {
"ARGUMENT_1": "goodnight",
"ARGUMENT_2": "moon",
"ARGUMENT_3": ""
},
"autorun": false
},
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
...
]
}
}
}
Node references
You can add a reference property to nodes which allows you to refer to the output of that node later in the template. References are added to the top-level of a node, in the format "ref": "<string>"
, where <string>
is the name of the reference.
References to a node return a promise, which resolves to the relevant response from the node. References are accessed in the template using the refs
object, in the format ${refs.<reference-name>.<property>}
.
Check the response schemas in the API documentation.
This is useful if you have steps in your workflow that require confirmation that previous steps have been completed successfully, or details from resources created earlier.
The example below shows a node to create a build service, and later a node to start a build. In this case ${refs.builder.id}
will resolve to my-build-service
, which will be the Northflank ID generated from the build service's name. By using the reference to get id
you can change the specification of the build service without the need to update the rest of the template.
"steps": [
{
"kind": "BuildService",
"ref": "builder"
"spec": {
"name": "My build service",
...
}
},
{
"kind": "Build",
"spec": {
"id": "${refs.builder.id}",
"type": "service",
"branch": "main",
"reuseExistingBuilds": true,
},
"condition": "success"
},
]
Northflank DNS references
You can obtain the public DNS for a service from a reference in the following format: ${refs.<service-name>.ports.<array-number>.dns}
. Ports are an array of objects, you must provide the array position of the port you want to get the DNS for. For example if the first port on your service is a public HTTP port, you would obtain the DNS with the following: ${refs.<service-name>.ports.0.dns}
.
If you are using references to obtain the Northflank-generated DNS for a service, or connection details for an addon, you should include a condition node to make sure the service or addon is running. This will ensure the service or addon has obtained a subdomain before using the reference to obtain the DNS or connection details.
Dynamic domains
You can dynamically create subdomains in templates by configuring your domain to use wildcard redirect routing and certificate generation when you add it to Northflank.
You can then use the values from references and arguments to assign subdomains to your services, for example "${args.<argument-name>}.example.com"
or "${refs.<reference-name>.<property>}.example.com"
.
You can also accept requests to any subdomain of the parent domain using wildcard subdomains.
Functions
You can include functions in your template. Functions are called in the format: "${fn.<function-name>(<arguments>)}"
, for example "${fn.randomSecret(32, 'hex')}"
.
Functions are deterministic and will be evaluated on every template run, excluding the randomSecret
function.
Functions can include references and arguments that do not resolve to a value, unlike the top-level of a template where references and arguments must resolve. Unresolved references and arguments will be treated as false
if used as boolean arguments.
Functions are listed below, consisting of the function name, arguments and their types, and the purpose of the function.
General
Function | Arguments | Description |
---|---|---|
randomSecret | length: number , encoding: string: 'base64' or 'hex' | Returns a random base64 secret of the given length , and an optional encoding argument, either 'base64' (default) or 'hex'. This secret will be securely stored in the target resource and remain unchanged during subsequent executions of the template, unless it is manually removed. |
String manipulation
Function | Arguments | Description |
---|---|---|
toBase64 | string: string | Converts a UTF-8 encoded string to a base64-encoded string |
fromBase64 | base64: base64 | Converts a base64-encoded string to a UTF-8 encoded string |
slug | string: string | Converts a string to a slug (lowercase string with hyphens instead of spaces) |
indexOf | string: string , match: string | Returns the index of the first instance of the substring in the string, or -1 if not found |
search | string: string , match: string or regex | Returns the index of the first pattern match in the string, or -1 for no match |
replace | original: string , match: string or regex , replacement: string | Replace the first match in the original string with the replacement string |
replaceAll | original: string , match: string or regex , replacement: string | Replace all instances of the match in the original string with the replacement string |
slice | original: string , startIndex: integer , endIndex: integer | Returns the string between the indices of the original string |
length | string: string | Returns the length of the string as an integer |
Boolean functions
Boolean arguments can be provided as truthy and falsy values similar to JavaScript. They can accept booleans, strings, and numbers, and if a reference or argument does not resolve, it will be regarded as false.
Function | Arguments | Description |
---|---|---|
not | boolean: boolean | Not |
or | boolean1: boolean , boolean2: boolean , ... | Or, accepts any number of arguments |
and | boolean1: boolean , boolean2: boolean , ... | And, accepts any number of arguments |
if | boolean: boolean , then: any , else: any (optional) | If, returns then argument if true, otherwise returns else argument if provided |
eq | equal1: any , equal2: any , ... | Equals, accepts any number of arguments |
neq | not1: any , not2: any , ... | Not equals, accepts any number of arguments |
gt | num1: number , num2: number | Greater than |
lt | num1: number , num2: number | Lesser than |
gte | num1: number , num2: number | Greater than or equal to |
lte | num1: number , num2: number | Lesser than or equal to |
Maths
Function | Arguments | Description |
---|---|---|
add | a: number , b: number , ... | Add all arguments, accepts any number of arguments |
subtract | a: number , b: number , ... | Subtract all arguments, accepts any number of arguments |
multiply | a: number , b: number , ... | Multiply all arguments, accepts any number of arguments |
divide | a: number , b: number | Divide a by b |
remainder | a: number , b: number | The remainder of a divided by b |
exp | a: number , b: number | a to the power of b |
floor | a: number | The floor of a |
ceil | a: number | The ceiling of a |
Example
This example template uses references, arguments, and functions to programmatically build and deploy from a Git repository.
The Git account, repository, and branch are given as arguments to define the service names and retrieve the Git account and repository URL. References are then used to trigger a build and deploy it in the deployment service.
The resources assigned to the deployment service depend on the name of the branch, combining if
and eq
functions, as well as passing different runtime variables to the deployment.
{
"apiVersion": "v1",
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
{
"kind": "BuildService",
"ref": "builder",
"spec": {
"name": "${args.repository}-builder",
"billing": {
"deploymentPlan": "nf-compute-50"
},
"vcsData": {
"projectUrl": "https://github.com/${args.account}/${args.repository}",
"projectType": "github"
},
"buildSettings": {
"dockerfile": {
"buildEngine": "kaniko",
"dockerFilePath": "/Dockerfile",
"dockerWorkDir": "/"
}
},
"buildConfiguration": {
"prRestrictions": [
"*"
],
"branchRestrictions": [
"main"
]
}
}
},
{
"kind": "Build",
"ref": "build",
"spec": {
"id": "${refs.builder.id}",
"type": "service",
"branch": "${args.branch}"
}
},
{
"kind": "Condition",
"spec": {
"kind": "Build",
"spec": {
"type": "success",
"data": {
"buildId": "${refs.build.id}"
}
}
}
},
{
"kind": "DeploymentService",
"spec": {
"name": "${args.branch}-deployment",
"billing": {
"deploymentPlan": "${fn.if(fn.eq(args.branch, 'main'), 'nf-compute-100', 'nf-compute-50')}"
},
"deployment": {
"instances": "${fn.if(fn.eq(args.branch, 'main'), 3, 1)}",
"docker": {
"configType": "default"
},
"storage": {
"ephemeralStorage": {
"storageSize": 1024
}
},
"internal": {
"id": "${refs.builder.id}",
"branch": "${args.branch}",
"buildId": "${refs.build.id}"
}
},
"runtimeEnvironment": {
"ENVIRONMENT": "${fn.if(fn.eq(args.branch, 'main'), 'production', 'development')}"
}
}
}
]
}
}
}
Skip node execution
You can add a condition that will be checked before running a node in the template. You can set this in the visual editor in the template section of a node form, or by including the skipNodeExecution
field in the top level of a node.
If the value for skipNodeExecution
is true
the node will not be executed during the template run, if the value is false
the node will execute as normal.
You can use this to include nodes, or entire workflows, that you might not want to execute on every template run. For example, you may have a job to initialise a database when the template is first run, or you may have resources for your production environment that do not need to be included in your staging environment.
You can use template arguments, references, and functions to programmatically execute or skip nodes.
In the below example, a function to check equality is used to see if the argument provided for ENVIRONMENT
is DEVELOPMENT
. If it is DEVELOPMENT
the function will resolve to true
and the node will be skipped in the template run.
{
"kind": "",
"ref": "",
"skipNodeExecution": "${fn.eq(args.ENVIRONMENT, 'DEVELOPMENT')}",
"spec": {
...
}
}
Access repositories and images
You can access a Git repository for any service or job that builds from a repository, or deploy from an external container registry.
Git repository access
Your Git credentials are securely stored on the Northflank platform, and can be used by referring to the Git service (projectType
) and the username (accountLogin
) for your Git account in the vcsData
object.
The account must have access to the repository, and must be linked to your Northflank team.
{
"kind": "BuildService",
"name": "builder",
"spec": {
...
"vcsData": {
"projectUrl": "https://github.com/account/repository",
"projectType": "[GIT-SERVICE]",
"projectBranch": "main",
"accountLogin": "[USERNAME]"
}
}
}
External images credentials
Your credentials for external container registries are securely stored on the Northflank platform, and can be used by referring to the appropriate registry ID (credentials
) in the external
object.
You must have saved your registry credentials on Northflank, from which you can copy the required registry ID.
{
"kind": "DeploymentService",
"name": "deployment",
"spec": {
...
"deployment": {
...
"external": {
"imagePath": "[URL]/[USERNAME]/[IMAGE]:[TAG]",
"credentials": "[REGISTRY-ID]"
}
}
}
}
Types of node
The sections below contain information on the available node types that you can include in your template. Each section contains a list of nodes of that type, an example node specification, and references from the API.
The nodes are divided into the following categories:
- Workflow contains nodes that are to be run in sequence or parallel
- Team specifies team-level resources, such as projects and integrations
- Resource specifies a service, job, addon, or other resource to be created or updated
- Trigger initiates a task within a service, job, or addon
- Condition includes a condition node that checks the status of a resource or action
- Action contains an action node that specifies an action to run on a service, repository, or other entity
Workflow nodes
Workflow nodes specify whether the nodes contained within them are executed sequentially, or in parallel.
You can add nodes to the steps
array which will be executed in order in a sequential workflow, or at the same time for a parallel workflow.
Workflow nodes can be nested. You can, for example, run two sequential workflows simultaneously within a parallel workflow.
You can give workflow nodes a context
, which is a project ID. This can either be a hardcoded project ID (for example "my-project"
), or a reference that resolves to a project node's ID (for example "${refs.project.id}"
. Nodes contained within the workflow will inherit this context if they do not have a context defined themselves.
Workflow nodes take the following format, where nodes would be specified in the steps
array:
{
"kind": "workflow",
"context": {
"projectId": "<project-id>"
},
"spec": {
"type": "sequential | parallel",
"steps": []
}
}
- {object}Workflow node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofWorkflowspec
{object} requiredThe specification for the workflow node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Team nodes
Team nodes create and update resources and integrations on the team level. They do not require a project context to run.
Node | Kind | Description |
---|---|---|
Project | Project | Create or update a project |
BYOC integration | BYOCIntegration | Create or update a BYOC integration |
BYOC cluster | BYOCCluster | Create or update a BYOC cluster and node pools |
Subdomain path | SubdomainPath | Create a path for routing on a subdomain |
Tag | ResourceTag | Create a new tag in the team for tagging resources |
Message | Message | Create a message on a pull request on a git repository |
The example below creates a new project in the europe-west
region on Northflank managed cloud.
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "New Project",
"description": "This is a new project.",
"color": "#0A5BA5",
"region": "europe-west"
}
}
Project
- {object}Project node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofProjectspec
(multiple options: oneOf) requiredskipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
BYOC integration
- {object}BYOCIntegration node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofBYOCIntegrationspec
{object} requiredThe specification for the BYOCIntegration node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
BYOC cluster
- {object}BYOCCluster node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofBYOCClusterspec
{object} requiredThe specification for the BYOCCluster node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Subdomain path
- {object}SubdomainPath node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofSubdomainPathspec
{object} requiredThe specification for the SubdomainPath node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Tag
- {object}ResourceTag node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofResourceTagspec
{object} requiredThe specification for the ResourceTag node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Message
Message nodes can be configured to comment on a pull request using rich text, and include content from arguments and references. This can be used, for example, to alert users that a preview environment has been created, and share links to relevant endpoints for preview and testing.
- {object}Message node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofMessagespec
(multiple options: oneOf) requiredThe specification for the Message node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Resource nodes
Resource nodes can be used to create or update services, jobs, addons, and other resources on the Northflank platform. They can also be used to trigger builds, run jobs, and schedule addon backups.
Node | Kind | Description |
---|---|---|
Build service | BuildService | Create or update a build service |
Combined service | CombinedService | Create or update a combined service |
Deployment service | DeploymentService | Create or update a deployment service |
Cron job | CronJob | Create or update a cron job |
Manual job | ManualJob | Creates or update a manual job |
Addon | Addon | Creates or updates an addon |
Secret group | SecretGroup | Creates or updates a secret group |
Volume | Volume | Creates or updates a volume |
Build | Build | Triggers a build in a service or job, from a branch or a specific commit |
Run job | JobRun | Runs a job with the specified configuration |
Run backup | AddonBackup | Performs a backup on an addon |
The example below creates a deployment service called nginx
, deploying the nginx image from Docker Hub . It also publicly exposes port 80 using HTTP. Resource nodes must have a projectId set in the node, or inherit the project context from a workflow.
{
"kind": "DeploymentService",
"spec": {
"name": "nginx",
"projectId": "my-project",
"billing": {
"deploymentPlan": "nf-compute-50"
},
"deployment": {
"instances": 1,
"external": {
"imagePath": "library/nginx:latest"
}
},
"ports": [
{
"name": "port-01",
"internalPort": 80,
"public": true,
"protocol": "HTTP"
}
]
}
}
Combined service
- {object}CombinedService node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofCombinedServicespec
{object} requiredThe specification for the CombinedService node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Build service
- {object}BuildService node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofBuildServicespec
{object} requiredThe specification for the BuildService node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Deployment service
If you are deploying from a Northflank build service you can toggle between deploying the latest build or the latest commit from the build service.
Selecting latest build will deploy whatever the service has build most recently, regardless of the commit age. Selecting latest commit will deploy the most recent commit to the branch that has been built by the service.
- {object}DeploymentService node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofDeploymentServicespec
{object} requiredThe specification for the DeploymentService node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Cron job
- {object}CronJob node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofCronJobspec
{object} requiredThe specification for the CronJob node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Manual job
- {object}ManualJob node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofManualJobspec
{object} requiredThe specification for the ManualJob node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Addon
Fork an addon
You can fork an addon from an existing project, or one created earlier in the template using a reference. The addon must have a backup available created from the same major version.
You can use latest
to attempt to use the most recent backup, but if no backup exists the template run will fail.
Upgrade an addon
You can enable upgrade on version mismatch to allow a template to trigger an upgrade for an existing addon (disabled by default). If the addon version specified in the template is greater than the version of the existing addon, the addon will be upgraded. Addons must follow the upgrade path and cannot skip major versions. For example, to upgrade an addon from version 14 to version 16, you must first run the template with version 15 specified before updating to version 16.
- {object}Addon node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofAddonspec
(multiple options: anyOf) requiredThe provisioner type of the addonskipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Secret group
- {object}SecretGroup node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofSecretGroupspec
{object} requiredThe specification for the SecretGroup node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Volume
- {object}Volume node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofVolumespec
{object} requiredThe specification for the Volume node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Build
- {object}Build node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofBuildspec
{object} requiredThe specification for the Build node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
condition
stringone ofsuccess
OR
Run job
- {object}JobRun node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofJobRunspec
{object} requiredThe specification for the JobRun node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
condition
stringone ofsuccess
OR
Run backup
- {object}AddonBackup node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofAddonBackupspec
{object} requiredThe specification for the AddonBackup node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
condition
stringone ofsuccess
OR
Pipeline nodes
Pipeline nodes are used to create a pipeline in a project and populate the stages of the pipeline with deployment services, jobs, and addons.
You can also define a preview environment template for the pipeline, and release flow templates for each stage.
You can add resources to pipeline stages by including them as objects in a stages
array in the pipeline node spec with the names Development
, Staging
, and Production
. Each object in the nfObjects
array should refer to a resource by id
and type
("service"
, "job"
, or "addon"
).
References, arguments, and functions in nested templates
Normally composed references, arguments, and functions will not be resolved in release flow or preview environment templates in pipeline nodes when the template is executed. This is to preserve their functionality in the release flow and preview templates when they are created.
If you want to include references, arguments, or functions that will be executed when the template runs, so that the values are resolved in the release flow and preview environment templates when they are created, you can prefix them with template
.
For example:
"${refs.build.branch}"
would become"${template.refs.build.branch}"
"${args.SECRET}"
would become"${template.args.SECRET}"
"${fn.randomString(64)}"
would become"${template.fn.randomString(64)}"
{
"kind": "Pipeline",
"spec": {
"name": "My pipeline",
"preview": {
"kind": "PreviewEnv",
"spec": {
"apiVersion": "v1.2",
"spec": {...}
}
},
"stages": [
{
"name": "Development",
"nfObjects": [
{
"id": "devel-deployment",
"type": "service"
}
],
"releaseFlow": {
"kind": "ReleaseFlow",
"spec": {
"apiVersion": "v1.2",
"spec": {...}
}
}
},
{
"name": "Staging",
"nfObjects": [
{
"id": "staging-job",
"type": "job"
}
],
"releaseFlow": {
"kind": "ReleaseFlow",
"spec": {
"apiVersion": "v1.2",
"spec": {...}
}
}
},
{
"name": "Production",
"nfObjects": [
{
"id": "production-addon",
"type": "addon"
}
],
"releaseFlow": {
"kind": "ReleaseFlow",
"spec": {
"apiVersion": "v1.2",
"spec": {...}
}
}
}
]
}
}
- {object}Pipeline node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofPipelinespec
{object} requiredThe specification for the Pipeline node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Preview environment node
The pipeline specification can include preview
, a node with the kind PreviewEnv
. The spec for the PreviewEnv
node includes apiVersion
and spec
, where spec
is the content of the preview environment template.
- {object}preview
kind
stringThe kind of node.one ofPreviewEnvspec
{object} requiredThe preview environment template specification.
Release flow node
Release flow nodes can be included in pipeline stage objects, and include apiVersion
and spec
, where spec
is the content of the release flow template.
- {object}releaseFlow
kind
string requiredThe kind of node.one ofReleaseFlowspec
{object} requiredThe release flow template specification.
Condition nodes
You can use condition nodes to check the status of a resource or action in a template or in an individual workflow. The workflow or template will continue to run until the condition is met, or until it times out.
In a sequential workflow the condition will stop following steps from running until the condition is met. In a parallel workflow all the steps will run, but the workflow will not be marked as completed unless the condition is met.
Below is a list of available condition node types you can include in your template. Specific condition nodes must be contained within the parent Condition
node.
Node | Kind | Description |
---|---|---|
Await condition | Condition | Contains a condition node that must be met to continue a sequential workflow, or to mark a parallel workflow as successful |
Service condition | Service | Contains checks for services |
Addon condition | Addon | Contains checks for addons |
Backup condition | AddonBackup | Contains checks for addon backups |
Job run condition | JobRun | Contains checks for job runs |
Build condition | Build | Contains checks for builds |
VCS condition | VCS | Contains checks for Git repositories |
An example of a condition node specification that checks if the referenced build has completed successfully:
{
"kind": "Condition",
"spec": {
"kind": "Build",
"spec": {
"type": "success",
"data": {
"buildId": "${refs.build.id}"
}
}
}
}
- {object}Condition node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofConditionspec
(multiple options: oneOf) requiredThe specification for the Condition node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Action nodes
You can define actions to take on existing services, addons, or Git services using action nodes.
You can, for example, restart a service or addon, or clone an existing repository into a new one. This can be useful if you have deployed a service that requires restarting after initialising, or want to share a template and enable users to modify the source code.
Below is a list of available action node types you can include in your template. Specific action nodes must be contained within the parent Action
node.
Node | Kind | Description |
---|---|---|
Action | Action | Contains an action node that will perform the specified action on a resource |
Service action | Service | Perform an action on a service |
Addon action | Addon | Perform an action on an addon |
VCS action | VCS | Perform an action on a Git account |
The example below triggers a restart of a service:
{
"kind": "Action",
"spec": {
"kind": "Service",
"spec": {
"type": "restart",
"data": {
"serviceId": "service-to-restart"
}
}
}
}
Execute a command in a service
{
"kind": "Action",
"spec": {
"kind": "Service",
"spec": {
"type": "execute",
"data": {
"serviceId": "my-service",
"command": "sh -c \"echo ${MESSAGE}\""
}
}
}
}
Commands in action nodes do not invoke a shell by default. Learn more about executing commands in action nodes.
- {object}Action node
ref
stringAn identifier that can used to reference the output of this node later in the template.kind
string requiredThe kind of node.one ofActionspec
(multiple options: oneOf) requiredThe specification for the Action node.skipNodeExecution
(multiple options: oneOf)- stringone oftrue, false
- stringpattern.*\${.*}.*
OR
Example template
This template example will guide you through the building of a template to deploy Payload CMS which will:
- Define template settings
- Create a project and add a workflow with the project context
- Deploy an addon and a combined service that builds from a Git repository
- Create a secret group to store required environment variables and addon connection details
- Build an image from the repository and restart the service when the database is ready
Define template settings
The template is defined with the Northflank API version, the name and description for the template, and various options. In the visual editor, these can all be configured on the settings page. The empty spec object will contain our template content.
{
"apiVersion": "v1.2",
"name": "Payload Template",
"description": "Payload is a headless CMS and application framework built with TypeScript, Node.js, React and MongoDB.",
"arguments": {},
"argumentOverrides": {},
"options": {
"autorun": false,
"concurrencyPolicy": "queue"
},
"spec": {}
}
Create a project and workflow with project context
Inside the spec we have added a parent workflow, as all nodes must be contained within a workflow. It's a sequential workflow, so the nodes will be run in order, with the next node only being executed when the previous node has completed successfully.
Inside the workflow we have added two nodes to the workflow's steps, a project node and a workflow node. The workflow node uses the reference to the project node to get the project context ("${refs.project.id}"
). All nodes that create or update project resources within this workflow will execute in this project, unless they are given another context.
{
"apiVersion": "v1.2",
"name": "Payload Template",
"description": "Payload is a headless CMS and application framework built with TypeScript, Node.js, React and MongoDB.",
"arguments": {},
"argumentOverrides": {},
"options": {
"autorun": false,
"concurrencyPolicy": "queue"
},
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "Payload project",
"color": "#7FD1B9",
"region": "europe-west",
"description": "My example Payload project"
}
},
{
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {
"projectId": "${refs.project.id}"
},
"steps": []
}
}
]
}
}
}
Deploy an addon and combined service
We then add two nodes to the workflow node with the project context, a node to create a MongoDB addon with the reference database
and a node to create a combined service with the reference payload
. The combined service deploys the master
branch from the repository https://github.com/northflank-guides/deploy-payload-on-northflank
.
{
"apiVersion": "v1.1",
"name": "Payload Template",
"description": "Payload is a headless CMS and application framework built with TypeScript, Node.js, React and MongoDB.",
"arguments": {},
"argumentOverrides": {},
"options": {
"autorun": false,
"concurrencyPolicy": "queue"
},
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "Payload project",
"color": "#7FD1B9",
"region": "europe-west",
"description": "My example Payload project"
}
},
{
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {
"projectId": "${refs.project.id}"
},
"steps": [
{
"kind": "Addon",
"ref": "database",
"spec": {
"name": "Database",
"type": "mongodb",
"version": "7.0-latest",
"billing": {
"deploymentPlan": "nf-compute-20",
"storageClass": "ssd",
"storage": 4096,
"replicas": 1
},
"tlsEnabled": true,
"externalAccessEnabled": false,
"ipPolicies": [],
"pitrEnabled": false
}
},
{
"kind": "CombinedService",
"ref": "payload",
"spec": {
"name": "Payload",
"deployment": {
"instances": 1,
"storage": {
"ephemeralStorage": {
"storageSize": 1024
},
"shmSize": 64
},
"docker": {
"configType": "default"
}
},
"billing": {
"deploymentPlan": "nf-compute-20"
},
"ports": [
{
"name": "app",
"internalPort": 3000,
"public": true,
"protocol": "HTTP",
"security": {
"credentials": [],
"policies": []
},
"domains": []
}
],
"vcsData": {
"projectUrl": "https://github.com/northflank-guides/deploy-payload-on-northflank",
"projectType": "github",
"projectBranch": "master"
},
"buildSettings": {
"dockerfile": {
"buildEngine": "kaniko",
"dockerFilePath": "/Dockerfile",
"dockerWorkDir": "/"
}
},
"runtimeEnvironment": {},
"runtimeFiles": {},
"buildArguments": {},
"buildFiles": {},
"disabledCI": false,
"buildConfiguration": {
"pathIgnoreRules": [],
"isAllowList": false,
"ciIgnoreFlagsEnabled": false
}
}
}
]
}
}
]
}
}
}
Create a secret group
We now add a secret group in the workflow to contain environment variables that the Payload application. These variables are:
PAYLOAD_PUBLIC_BASE_DNS
This variable gets the Northflank-generated domain name for the Payload port using the reference "https://${refs.payload.ports.0.dns}"
, with the required https://
protocol added.
PAYLOAD_SECRET
This is a random secret value used to encrypt Payload API keys. The value in the secret group is obtained from the template arguments object, using "${args.PAYLOAD_SECRET}"
. The secret is generated using the Northflank function "${fn.randomSecret(256)}"
in the template's argument overrides. Once generated, it will be securely stored on Northflank so future template runs do not overwrite the value. All sensitive secrets should be stored as argument overrides, and not within the template or as template arguments, as these are not secure.
MONGODB_URI
The URI for the MongoDB addon is obtained using the reference to the addon, "${refs.database.id}"
, and setting MONGODB_URI
as an alias of the Northflank-defined key MONGO_SRV
.
{
"apiVersion": "v1.2",
"name": "Payload Template",
"description": "Payload is a headless CMS and application framework built with TypeScript, Node.js, React and MongoDB.",
"arguments": {
"PAYLOAD_SECRET": ""
},
"argumentOverrides": {
"PAYLOAD_SECRET": "${fn.randomSecret(256)}"
},
"options": {
"autorun": false,
"concurrencyPolicy": "queue"
},
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "Payload project",
"color": "#7FD1B9",
"region": "europe-west",
"description": "My example Payload project"
}
},
{
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {
"projectId": "${refs.project.id}"
},
"steps": [
{
"kind": "Addon",
"ref": "database",
"spec": {
"name": "Database",
"type": "mongodb",
"version": "7.0-latest",
"billing": {
"deploymentPlan": "nf-compute-20",
"storageClass": "ssd",
"storage": 4096,
"replicas": 1
},
"tlsEnabled": true,
"externalAccessEnabled": false,
"ipPolicies": [],
"pitrEnabled": false
}
},
{
"kind": "CombinedService",
"ref": "payload",
"spec": {
"name": "Payload",
"deployment": {
"instances": 1,
"storage": {
"ephemeralStorage": {
"storageSize": 1024
},
"shmSize": 64
},
"docker": {
"configType": "default"
}
},
"billing": {
"deploymentPlan": "nf-compute-20"
},
"ports": [
{
"name": "app",
"internalPort": 3000,
"public": true,
"protocol": "HTTP",
"security": {
"credentials": [],
"policies": []
},
"domains": []
}
],
"vcsData": {
"projectUrl": "https://github.com/northflank-guides/deploy-payload-on-northflank",
"projectType": "github",
"projectBranch": "master"
},
"buildSettings": {
"dockerfile": {
"buildEngine": "kaniko",
"dockerFilePath": "/Dockerfile",
"dockerWorkDir": "/"
}
},
"runtimeEnvironment": {},
"runtimeFiles": {},
"buildArguments": {},
"buildFiles": {},
"disabledCI": false,
"buildConfiguration": {
"pathIgnoreRules": [],
"isAllowList": false,
"ciIgnoreFlagsEnabled": false
}
}
},
{
"kind": "SecretGroup",
"spec": {
"name": "secrets",
"secretType": "environment-arguments",
"priority": 10,
"secrets": {
"variables": {
"PAYLOAD_PUBLIC_BASE_DNS": "https://${refs.payload.ports.0.dns}",
"PAYLOAD_SECRET": "${args.PAYLOAD_SECRET}"
}
},
"addonDependencies": [
{
"addonId": "${refs.database.id}",
"keys": [
{
"keyName": "MONGO_SRV",
"aliases": [
"MONGODB_URI"
]
}
]
}
],
"restrictions": {
"restricted": false,
"nfObjects": [],
"tags": []
}
}
}
]
}
}
]
}
}
}
Restart the service when the database is ready
Next we add another workflow node. This is a parallel workflow, so it will execute both the node simultaneously. Once both nodes have successfully completed, the workflow will be marked as completed and the next node in the sequential workflow will run.
Inside the parallel workflow are two condition nodes, one checks that the addon has finished provisioning and is ready to use, and the other checks that the combined service is running, which means it has built and deployed an image.
Finally, we add an action node to restart the service to ensure it is deployed with the necessary environment variables from the secret group.
{
"apiVersion": "v1.2",
"name": "Payload Template",
"description": "Payload is a headless CMS and application framework built with TypeScript, Node.js, React and MongoDB.",
"arguments": {
"PAYLOAD_SECRET": ""
},
"argumentOverrides": {
"PAYLOAD_SECRET": "${fn.randomSecret(256)}"
},
"options": {
"autorun": false,
"concurrencyPolicy": "queue"
},
"spec": {
"kind": "Workflow",
"spec": {
"type": "sequential",
"steps": [
{
"kind": "Project",
"ref": "project",
"spec": {
"name": "Payload project",
"color": "#7FD1B9",
"region": "europe-west",
"description": "My example Payload project"
}
},
{
"kind": "Workflow",
"spec": {
"type": "sequential",
"context": {
"projectId": "${refs.project.id}"
},
"steps": [
{
"kind": "Addon",
"ref": "database",
"spec": {
"name": "Database",
"type": "mongodb",
"version": "7.0-latest",
"billing": {
"deploymentPlan": "nf-compute-20",
"storageClass": "ssd",
"storage": 4096,
"replicas": 1
},
"tlsEnabled": true,
"externalAccessEnabled": false,
"ipPolicies": [],
"pitrEnabled": false
}
},
{
"kind": "CombinedService",
"ref": "payload",
"spec": {
"name": "Payload",
"deployment": {
"instances": 1,
"storage": {
"ephemeralStorage": {
"storageSize": 1024
},
"shmSize": 64
},
"docker": {
"configType": "default"
}
},
"billing": {
"deploymentPlan": "nf-compute-20"
},
"ports": [
{
"name": "app",
"internalPort": 3000,
"public": true,
"protocol": "HTTP",
"security": {
"credentials": [],
"policies": []
},
"domains": []
}
],
"vcsData": {
"projectUrl": "https://github.com/northflank-guides/deploy-payload-on-northflank",
"projectType": "github",
"projectBranch": "master"
},
"buildSettings": {
"dockerfile": {
"buildEngine": "kaniko",
"dockerFilePath": "/Dockerfile",
"dockerWorkDir": "/"
}
},
"runtimeEnvironment": {},
"runtimeFiles": {},
"buildArguments": {},
"buildFiles": {},
"disabledCI": false,
"buildConfiguration": {
"pathIgnoreRules": [],
"isAllowList": false,
"ciIgnoreFlagsEnabled": false
}
}
},
{
"kind": "SecretGroup",
"spec": {
"name": "secrets",
"secretType": "environment-arguments",
"priority": 10,
"secrets": {
"variables": {
"PAYLOAD_PUBLIC_BASE_DNS": "https://${refs.payload.ports.0.dns}",
"PAYLOAD_SECRET": "${args.PAYLOAD_SECRET}"
}
},
"addonDependencies": [
{
"addonId": "${refs.database.id}",
"keys": [
{
"keyName": "MONGO_SRV",
"aliases": [
"MONGODB_URI"
]
}
]
}
],
"restrictions": {
"restricted": false,
"nfObjects": [],
"tags": []
}
}
},
{
"kind": "Workflow",
"spec": {
"type": "parallel",
"steps": [
{
"kind": "Condition",
"spec": {
"kind": "Addon",
"spec": {
"type": "running",
"data": {
"addonId": "${refs.database.id}"
}
}
}
},
{
"kind": "Condition",
"spec": {
"kind": "Service",
"spec": {
"type": "running",
"data": {
"addonId": "${refs.payload.id}"
}
}
}
}
]
}
},
{
"kind": "Action",
"spec": {
"kind": "Service",
"spec": {
"type": "restart",
"data": {
"serviceId": "${refs.payload.id}"
}
}
}
}
]
}
}
]
}
}
}
Next steps
Run a template
Run templates manually or automatically.
Update a template
Update a template and resources within a project.
GitOps on Northflank
Use templates and release flows in a Git repository to trigger changes to your config and resources.
Share a template
Share templates with your team or the public.
Manage template versions on Northflank
Use the template drafts system to review, accept, or reject proposed changes to your team's Northflank templates.