v1

Infrastructure as Code /

Write a template

Templates are written as nested JSON that consist of different types of node. When you create a template you can also visually preview the content of your template.

The main types of nodes are:

  • Structure, which defines sequential or parallel workflows
  • Resource, which contains specifications of project, services, jobs, addons, or other resources, to be created or updated
  • Condition, which checks the status of a resource or action
  • Action, which specifies an action to run on a service, repository, etc

This guide will first introduce how to get automatically generated template specifications from your existing resources and projects. It will then run through the structure of a template and different types of nodes before providing some examples of templates.

You can find more detailed schema for Northflank templates and template nodes in the API documentation for templates.

Properties not mentioned in the application documentation are only applicable to the Northflank API, and should not be included in templates managed using the Northflank application or a Git repository.

Template specifications

The simplest way to start writing a template is to get the template specification for your existing resources or an entire project.

You can also create new projects or resources in order to copy the specification for use in a template, rather than writing the specification from scratch.

View project template

You can click the options button in the project header to get the template for an entire project.

Select view project template to see the specification for your entire project. You can copy this and use the content to create a new template, and edit it where required.

View resource specification

To view the specification for individual resources, such as services, addons, jobs you can click the options button in the resource's header.

Select view specification and ensure the view is toggled to template. You can copy the specification and insert it into your template.

Template structure

Your template must include information about the template itself, such as apiVersion and name (and optionally a description). You must also include a project object, which defines the project to create or update, or an existing project ID to run the template in, and the spec object, which contains the workflows, conditions, resources, and actions of the template.

If you create or edit a template via the Northflank application the template name, description, and project are configured in the template form, and are hidden from the JSON view. The Northflank editor also includes code hints, autocompletion, and error checking to help you include required properties in the correct format.

The top level of a template looks like this:

{
  "apiVersion": "v1",
  "name": "template-name",
  "description": "Your template description",
  "project": {},
  "spec": {}
}

If you are writing a template as a JSON file the required fields, listed in the attributes below, must be included.

  • {object}
    • apiVersion

      string required

      The version of the Northflank API to run the template against.

      one of
      v1
    • name

      string required

      Name of the template.

    • description

      string

      Description of the template.

    • project

      {object} required

      Contains the ID of an existing project, or describes a new project to be created by the template.

      • spec

        {object} required

        Contains nodes representing actions to be performed as part of the template.

      View the entire schema in the API documentation.

      Template project

      You can provide either the ID of an existing project to run the template in, or specify a project to be created or updated. You cannot update the region or cluster of a project.

      A new project specification looks like this:

      {
        "project": {
          "spec": {
            "name": "My new project",
            "description": "A new project created by this template",
            "color": "#57637A",
            "region": "europe-west"
          }
        }
      }
      

      If you are using another cloud provider, you can provide clusterId instead of region, where clusterId is the ID of the cluster you want to use.

      To use an existing project you can provide only the project ID:

      {
        "id": "your-project-id"
      }
      

      The project object can contain one of the following:

      • (multiple options: oneOf)

        Details of the project the template will run in.

        • {object}

          Use an existing project

          • id

            string required

            The ID of the project to use.

          OR

        • {object}

          Create a new project

          • spec

            (multiple options: oneOf) required

      View the entire schema in the API documentation.

      If you run a template that creates a project with the same name as an existing project in the account, it will update the project according to the template. If the template specifies a region different to the existing project, the template run will fail.

      If you run a template that specifies (but does not create) a project that doesn't exist in the account, the template run will fail.

      Types of node

      Below is a list of available node types you can include in your template. You can view the entire schema in the API documentation.

      Nodekinddescription
      WorkflowWorkflowDefine a sequential or parallel workflow in your template
      ConditionConditionContains a condition node that must be met to continue a sequential workflow, or to mark a parallel workflow as successful
      Addon conditionAddonContains checks for addons, must be within a condition node
      Build conditionBuildContains checks for builds, must be within a condition node
      VCS conditionVCSContains checks for Git repositories, must be within a condition node
      ActionActionContains an action node that will perform the specified action on a resource
      Service actionServicePerform an action on a service, must be within an action node
      Addon actionAddonPerform an action on an addon, must be within an action node
      VCS actionVCSPerform an action on a Git account, must be within an action node
      Build serviceBuildServiceCreate or update a build service
      Combined serviceCombinedServiceCreate or update a combined service
      Deployment serviceDeploymentServiceCreate or update a deployment service
      Cron jobCronJobCreate or update a cron job
      Manual jobManualJobCreates or update a manual job
      AddonAddonCreates or updates an addon
      Secret groupSecretGroupCreates or updates a secret group
      PipelinePipelineCreates or updates a pipeline
      VolumeVolumeCreates or updates a volume
      BuildBuildTriggers a build in a service or job, from a branch or a specific commit
      Deployment serviceDeploymentServiceCreates or updates a deployment service
      Job runJobRunRuns a job with the specified configuration

      Workflow nodes

      Workflow nodes specify whether execute the nodes contained within them 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.

      Workflow nodes take the following format:

      {
        "kind": "workflow",
        "spec": {
          "type": "sequential | parallel",
          "steps": []
        }
      }
      

      View the entire schema in the API documentation.

      Resource nodes

      Resource nodes can be used to create or update services, jobs, addons, secret groups, pipelines, volumes, and other resources on the Northflank platform.

      Check the API documentation for a full list of nodes and their specifications.

      Example of a deployment service specification:

      {
        "kind": "DeploymentService",
        "spec": {
          "name": "nginx",
          "billing": {
            "deploymentPlan": "nf-compute-50"
          },
          "deployment": {
            "instances": 1,
            "external": {
              "imagePath": "library/nginx:latest"
            }
          },
          "ports": [
            {
              "name": "port-01",
              "internalPort": 80,
              "public": true,
              "protocol": "HTTP"
            }
          ]
        }
      }
      

      The above example creates a deployment service called nginx, deploying the nginx image from Docker Hub . It also publicly exposes port 80 using HTTP.

      View the entire schema in the API documentation.

      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.

      An example of a condition node specification that checks if the specified addon is running:

      {
        "kind": "Condition",
        "spec": {
          "kind": "Addon",
          "spec": {
            "type": "running",
            "data": {
              "addonId": "addon-to-check"
            }
          }
        }
      }
      

      View the entire schema in the API documentation.

      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.

      {
        "kind": "Action",
        "spec": {
          "kind": "Service",
          "spec": {
            "type": "restart",
            "data": {
              "serviceId": "service-to-restart"
            }
          }
        }
      }
      

      View the entire schema in the API documentation.

      Node references and arguments

      You can add a reference (ref) property to nodes to refer to the output of a node. Add your ref as a string and it will return a promise, which resolves to the relevant response. 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.

      For example, if you are deploying an app that requires a database you could use the reference to the database creation node to ensure that the database has been created and get the connection details to pass to your deployment.

      You can see this implemented in some of the example templates.

      Arguments

      You can include arguments in your template, referenced in the format ${args.argName}. You can then set their values in the template form arguments override section, by creating key-value pairs with the argument name: "argName": "value".

      Argument overrides are stored outside of the template. If you are using GitOps, these will not be saved in your repository.

      To manage arguments using the API you can create arguments and argumentOverride objects, containing the key-value pairs.

      Functions

      You can include functions in your template. If you use a function in a template it will be saved as a function call. The function will be evaluated every time you run the template, which means the value can change if a template is used to update resources.

      Example of a sequential template

      This example demonstrates the deployment of a Postgres addon and a WikiJS image from Docker Hub .

      The template goes through the following steps:

      1. Include the details for the template: apiVersion, name, and description
      2. Create a project for the template to run in, specifying the name, region, description, and color. If a project with this name exists in the region, it will update the resources in the project as given in the template when run. If a project with this name exists in a different region, the template run will fail.
      3. Add a sequential workflow to include the steps to run in the template:
        1. Create a Postgres addon
        2. Create a secret group with the addon connection details and relevant aliases to provide the database connection details to the application
        3. Create a deployment service, pulling the external image from Docker Hub. It has two ports configured, a public HTTP port and a private TCP port to connect to the database.

      When the template runs, it will wait for the previous step in the workflow to complete before executing the next. The database is created first in order to create a secret group with the addon connection details. As the secret group is unrestricted, the deployment created afterwards will then inherit the connection detail secrets with the necessary aliases for the application.

      {
        "apiVersion": "v1",
        "name": "WikiJS",
        "description": "",
        "project": {
          "spec": {
            "name": "WikiJS",
            "region": "europe-west",
            "color": "#AF3B6E",
            "description": ""
          }
        },
        "spec": {
          "kind": "Workflow",
          "spec": {
            "type": "sequential",
            "steps": [
              {
                "kind": "Addon",
                "spec": {
                  "name": "incandescent-reading",
                  "description": "",
                  "type": "postgres",
                  "version": "14.5.0",
                  "billing": {
                    "deploymentPlan": "nf-compute-50",
                    "storageClass": "ssd",
                    "storage": 4096,
                    "replicas": 1
                  },
                  "tlsEnabled": false,
                  "externalAccessEnabled": false,
                  "ipPolicies": [],
                  "pitrEnabled": false
                }
              },
              {
                "kind": "SecretGroup",
                "spec": {
                  "name": "Example Secret",
                  "description": "",
                  "secretType": "environment",
                  "priority": 10,
                  "secrets": {
                    "variables": {
                      "HA_ACTIVE": "true",
                      "DB_TYPE": "postgres"
                    }
                  },
                  "addonDependencies": [
                    {
                      "addonId": "incandescent-reading",
                      "keys": [
                        {
                          "keyName": "USERNAME",
                          "aliases": [
                            "DB_USER"
                          ]
                        },
                        {
                          "keyName": "PASSWORD",
                          "aliases": [
                            "DB_PASS"
                          ]
                        },
                        {
                          "keyName": "DATABASE",
                          "aliases": [
                            "DB_NAME"
                          ]
                        },
                        {
                          "keyName": "HOST",
                          "aliases": [
                            "DB_HOST"
                          ]
                        },
                        {
                          "keyName": "PORT",
                          "aliases": [
                            "DB_PORT"
                          ]
                        },
                        {
                          "keyName": "TLS_ENABLED",
                          "aliases": [
                            "DB_SSL"
                          ]
                        }
                      ]
                    }
                  ],
                  "restrictions": {
                    "restricted": false,
                    "nfObjects": []
                  }
                }
              },
              {
                "kind": "DeploymentService",
                "spec": {
                  "name": "cuddly-bucket",
                  "description": "",
                  "billing": {
                    "deploymentPlan": "nf-compute-50"
                  },
                  "deployment": {
                    "instances": 1,
                    "storage": {
                      "ephemeralStorage": {
                        "storageSize": 1024
                      }
                    },
                    "external": {
                      "imagePath": "requarks/wiki:latest"
                    }
                  },
                  "ports": [
                    {
                      "name": "p01",
                      "internalPort": 3000,
                      "public": true,
                      "protocol": "HTTP",
                      "security": {
                        "credentials": [],
                        "policies": []
                      },
                      "domains": [],
                      "disableNfDomain": false
                    },
                    {
                      "name": "p02",
                      "internalPort": 3443,
                      "protocol": "TCP",
                      "public": false,
                      "security": {
                        "credentials": [],
                        "policies": []
                      },
                      "domains": [],
                      "disableNfDomain": false
                    }
                  ],
                  "healthChecks": [],
                  "runtimeEnvironment": {}
                }
              }
            ]
          }
        }
      }
      

      Example of a parallel template

      This example demonstrates the deployment of a MongoDB addon and the build and deployment of a Payload server .

      The project resources can be initialised simultaneously, so the services and addon are created at the same time. Conditions are then used to check that the database is running and that the build has completed, before creating a secret group and deploying the build. This means that the resources can be created faster, and steps are only executed when they should succeed.

      The template goes through the following steps:

      1. Include the details for the template: apiVersion, name, and description
      2. Create a project for the template to run in, specifying the name, region, description, and color. If a project with this name exists in the region, it will update the resources in the project as given in the template when run. If a project with this name exists in a different region, the template run will fail.
      3. Add a sequential workflow to include the steps to run in the template:
        1. Add a parallel workflow to:
          1. Create a MongoDB addon
          2. Create a deployment service with no instances
          3. Add a sequential workflow to:
            1. Create a build service
            2. Begin a build of the latest commit to the master branch using the build service
        2. After the parallel workflow has executed a sequential workflow is added:
          1. The condition node checks the MongoDB addon has initialised and is running
          2. A secret group is created containing the connection details for the addon and the DNS entry for the deployment service
          3. The condition node checks the build has completed
          4. The deployment service is updated to deploy the master branch from the build service, and scaled to 1 instance, now inheriting the necessary secrets
      {
        "apiVersion": "v1",
        "name": "Parallel template",
        "description": "",
        "project": {
          "spec": {
            "name": "Parallel template",
            "region": "europe-west",
            "color": "#6F2DBD",
            "description": ""
          }
        },
        "spec": {
          "kind": "Workflow",
          "spec": {
            "type": "sequential",
            "steps": [
              {
                "kind": "Workflow",
                "spec": {
                  "type": "parallel",
                  "steps": [
                    {
                      "kind": "Addon",
                      "spec": {
                        "name": "payload-db",
                        "description": "",
                        "type": "mongodb",
                        "version": "5.0.11",
                        "billing": {
                          "deploymentPlan": "nf-compute-50",
                          "storageClass": "ssd",
                          "storage": 4096,
                          "replicas": 1
                        },
                        "tlsEnabled": false,
                        "externalAccessEnabled": false,
                        "ipPolicies": [],
                        "pitrEnabled": false
                      }
                    },
                    {
                      "kind": "Workflow",
                      "spec": {
                        "type": "sequential",
                        "steps": [
                          {
                            "kind": "BuildService",
                            "spec": {
                              "name": "payload-builder",
                              "billing": {
                                "deploymentPlan": "nf-compute-100-1"
                              },
                              "vcsData": {
                                "projectUrl": "https://github.com/northflank-guides/deploy-payload-on-northflank",
                                "projectType": "github"
                              },
                              "buildSettings": {
                                "dockerfile": {
                                  "buildEngine": "kaniko",
                                  "dockerFilePath": "/Dockerfile",
                                  "dockerWorkDir": "/"
                                }
                              },
                              "buildConfiguration": {
                                "prRestrictions": ["*"],
                                "branchRestrictions": ["master"]
                              }
                            }
                          },
                          {
                            "kind": "Build",
                            "ref": "payloadBuild",
                            "spec": {
                              "id": "payload-builder",
                              "type": "service",
                              "branch": "master"
                            }
                          }
                        ]
                      }
                    },
                    {
                      "kind": "DeploymentService",
                      "ref": "payloadDeploy",
                      "spec": {
                        "name": "payload-deployment",
                        "billing": {
                          "deploymentPlan": "nf-compute-50"
                        },
                        "deployment": {
                          "instances": 0,
                          "storage": {
                            "ephemeralStorage": {
                              "storageSize": 1024
                            }
                          }
                        },
                        "ports": [
                          {
                            "name": "p01",
                            "internalPort": 3000,
                            "public": true,
                            "protocol": "HTTP"
                          }
                        ],
                        "healthChecks": []
                      }
                    }
                  ]
                }
              },
              {
                "kind": "Workflow",
                "spec": {
                  "type": "sequential",
                  "steps": [
                    {
                      "kind": "Condition",
                      "spec": {
                        "kind": "Addon",
                        "spec": {
                          "type": "running",
                          "data": {
                            "addonId": "payload-db"
                          }
                        }
                      }
                    },
                    {
                      "kind": "SecretGroup",
                      "spec": {
                        "name": "payload-secrets",
                        "description": "",
                        "secretType": "environment-arguments",
                        "priority": 10,
                        "addonDependencies": [
                          {
                            "addonId": "payload-db",
                            "keys": [
                              {
                                "keyName": "MONGO_SRV",
                                "aliases": ["MONGODB_URI"]
                              }
                            ]
                          }
                        ],
                        "secrets": {
                          "variables": {
                            "PAYLOAD_SECRET": "${fn.randomSecret(32)}",
                            "PAYLOAD_PUBLIC_BASE_DNS": "https://${refs.payloadDeploy.ports.0.dns}"
                          }
                        }
                      }
                    },
                    {
                      "kind": "Condition",
                      "spec": {
                        "kind": "Build",
                        "spec": {
                          "type": "success",
                          "data": {
                            "buildId": "${refs.payloadBuild.internalId}"
                          }
                        }
                      }
                    },
                    {
                      "kind": "DeploymentService",
                      "spec": {
                        "name": "payload-deployment",
                        "deployment": {
                          "instances": 1,
                          "internal": {
                            "id": "payload-builder",
                            "branch": "master"
                          }
                        },
                        "billing": {
                          "deploymentPlan": "nf-compute-50"
                        }
                      }
                    }
                  ]
                }
              }
            ]
          }
        }
      }
      

      © 2022 Northflank Ltd. All rights reserved.