Skip to content

NAIS deploy

This section will take you through the deployment of your application using NAIS deploy. NAIS deploy enables you to deploy your application from any continuous integration platform. Our primary supported platform is GitHub Actions, but you can also deploy from CircleCI, Travis CI, Jenkins, or other tools.

If you experience any trouble along the way, please take a look at the troubleshooting page.

How it works

Your application is assumed to be present in the form of a Docker image when using the NAIS deploy tool. The NAIS deploy tool is used to create a deployment request. The Docker image will be deployed to Kubernetes, and the deploy tool will wait until your deployment is rolled out, gets an error, or a timeout occurs. Underway, deployment statuses are continually posted back to GitHub Deployment API. Deployment logs can be viewed on Kibana. The link to the logs will be provided by the deploy tool.

Set it up

  1. Your application must have a repository on GitHub containing a nais.yaml and Dockerfile.
  2. Your GitHub team must have write access on that repository.
  3. Your GitHub team's identifier must match the Kubernetes team label in your nais.yaml. There is an example file below.
  4. Retrieve your team API key. Save the key as a secret named NAIS_DEPLOY_APIKEY in your GitHub repository.
  5. Follow the guide below.
  6. When things break, see the help page.

Deploy with GitHub Actions

A GitHub Actions pipeline is called a Workflow. You can set up workflows by adding a YAML file to your application's Git repository.

In this example, the workflow is set up in the file deploy.yaml. The workflow will build a Docker image and push it to GitHub Container Registry. Next, if the code was pushed to the main branch AND the build job succeeded, the application will be deployed to NAIS.

Official GitHub documentation: Automating your workflow with GitHub Actions.

Get started by creating the following structure in your application repository:

├── .github/
│   └── workflows/
│       └── deploy.yaml
├── Dockerfile
└── nais.yaml

Add the example files below, then commit and push. This will trigger the workflow, and you can follow its progress under the Actions tab on your GitHub repository page.

name: Build, push, and deploy

on: [push]

    name: Build and push Docker container
    runs-on: ubuntu-latest
      contents: read
      id-token: write
      image: ${{ steps.docker-build-push.outputs.image }}
    - uses: actions/checkout@v3
    - name: Push docker image to GAR
      uses: nais/docker-build-push@v0
      id: docker-build-push
        team: myteam # Replace
        identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }} # Provided as Organization Secret
        project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }} # Provided as Organization Variable

    name: Deploy to NAIS
    needs: build
    runs-on: ubuntu-latest
    - uses: actions/checkout@v3
    - uses: nais/deploy/actions/deploy@v1
        APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
        CLUSTER: target-cluster # Replace
        RESOURCE: nais.yaml
        VAR: image=${{ }}
kind: Application
  name: myapplication
  namespace: myteam
    team: myteam
  image: {{ image }}
  #       ^--- interpolated from the ${{ env.docker_image }} variable in the action

In this nais.yaml file, {{ image }} will be replaced by the $docker_image environment variable set in the action. You can add more by using a comma separated list, or even put all your variables in a file; see action configuration below.

FROM nginxinc/nginx-unprivileged

You have to write your own Dockerfile, but if you're just trying this out, you can use the following example file.

Action configuration

Environment variable Default Description
APIKEY (required) NAIS deploy API key. Obtained from console.
CLUSTER (required) Which NAIS cluster (On-premises or GCP) to deploy into.
DRY_RUN false If true, run templating and validate input, but do not actually make any requests.
ENVIRONMENT (auto-detect) The environment to be shown in GitHub Deployments. Defaults to CLUSTER:NAMESPACE for the resource to be deployed if not specified, otherwise falls back to CLUSTER if multiple namespaces exist in the given resources.
OWNER (auto-detect) Owner of the repository making the request.
PRINT_PAYLOAD false If true, print templated resources to standard output.
QUIET false If true, suppress all informational messages.
REF master (auto-detect) Commit reference of the deployment. Shown in GitHub's interface.
REPOSITORY (auto-detect) Name of the repository making the request.
RESOURCE (required) Comma-separated list of files containing Kubernetes resources. Must be JSON or YAML format.
RETRY true Automatically retry deploying if deploy service is unavailable.
TEAM (auto-detect) Team making the deployment.
TIMEOUT 10m Time to wait for deployment completion, especially when using WAIT.
VAR Comma-separated list of template variables in the form key=value. Will overwrite any identical template variable in the VARS file.
VARS /dev/null File containing template variables. Will be interpolated with the $RESOURCE file. Must be JSON or YAML format.
WAIT true Block until deployment has completed with either success, failure or error state.

Note that OWNER and REPOSITORY corresponds to the two parts of a full repository identifier. If that name is navikt/myapplication, those two variables should be set to navikt and myapplication, respectively.

Deploy with other CI

You can still use NAIS deploy even if not using GitHub Actions. Our deployment command line tool supports all CI tools such as Jenkins, Travis or Circle. Use can either use a Docker image or one the binaries to make deployments.

docker run -it --rm -v $(pwd):/nais \
  /app/deploy \
    --apikey="$NAIS_DEPLOY_APIKEY" \
    --cluster="$CLUSTER" \
    --owner="$OWNER" \
    --repository="$REPOSITORY" \
    --resource="/nais/path/to/nais.yaml" \
    --vars="/nais/path/to/resources" \
    --wait=true \

Here we use the current directory as a volume for the CLI, and we have to append /nais to the path to our manifest.

So if our original manifest is under /home/cooluser/workspace/bestapp/nais.yaml, we then need to --resource="/nais/nais.yaml", and not only --resource="nais.yaml".

deploy \
--apikey="$NAIS_DEPLOY_APIKEY" \
--cluster="$CLUSTER" \
--owner="$OWNER" \
--repository="$REPOSITORY" \
--resource="/path/to/nais.yaml" \
--vars="/path/to/resources" \


--apikey string          NAIS Deploy API key.
--cluster string         NAIS cluster to deploy into.
--deploy-server string   URL to API server.
--dry-run                Run templating, but don't actually make any requests.
--environment string     Environment for GitHub deployment. Auto-detected from nais.yaml if not specified.
--owner string           Owner of GitHub repository. (default "navikt")
--print-payload          Print templated resources to standard output.
--quiet                  Suppress printing of informational messages except errors.
--ref string             Git commit hash, tag, or branch of the code being deployed. (default "master")
--repository string      Name of GitHub repository.
--resource strings       File with Kubernetes resource. Can be specified multiple times.
--team string            Team making the deployment. Auto-detected if possible.
--var strings            Template variable in the form KEY=VALUE. Can be specified multiple times.
--vars string            File containing template variables.
--wait                   Block until deployment reaches final state (success, failure, error).

All of these options can be set using environment variables, such as $APIKEY and $PRINT_PAYLOAD.

Proxy server

If you are running NAIS deploy from an internal Jenkins server you need to set up an HTTP proxy as the deploy things run on public addresses.

When using the CLI binary you can wrap steps in your pipeline with injected environment variables.

stage('Deploy') {
  withEnv(['HTTPS_PROXY=']) {

When using NAIS deploy docker image, pass the environment variable to Docker run.

sh "docker run --env HTTPS_PROXY=''  ..." ;


Templates use Handlebars 3.0 syntax. Both the template and variable file supports either YAML or JSON syntax.

A practical example follows. Create a nais.yaml file:

kind: Application
  name: {{app}}
  namespace: {{namespace}}
    team: {{team}}
  image: {{image}}
  {{#each ingresses as |url|}}
    - {{url}}

Now, create a vars.yaml file containing variables for your deployment:

app: myapplication
namespace: myteam
team: myteam

Run the deploy tool to see the final results:

$ deploy --dry-run --print-payload --resource nais.yaml --vars vars.yaml | jq ".resources[0]"
  "apiVersion": "",
  "kind": "Application",
  "metadata": {
    "labels": {
      "team": "myteam"
    "name": "myapplication",
    "namespace": "default"
  "spec": {
    "image": "",
    "ingresses": [

Escaping and raw resources

If you do not specify the --vars or --var command-line flags, your resource will not be run through the templating engine, so these resources will not need templating.

Handlebars content may be escaped by prefixing a mustache block with \, such as:


Real-world example:

kind: Alert
  name: {{app}}
    team: {{team}}
  - action: Se `kubectl describe pod \{{ $labels.kubernetes_pod_name }}` for events, og `kubectl logs \{{ $labels.kubernetes_pod_name }}` for logger
    alert: {{app}}-fails
    description: '\{{ $ }} er nede i \{{ $labels.kubernetes_namespace }}'
    expr: up{app=~"{{app}}",job="kubernetes-pods"} == 0
    for: 2m
    severity: danger
    sla: respond within 1h, during office hours
      channel: '#{{team}}'

Will result in:

deploy --dry-run --print-payload --resource alert.yaml --vars vars.yaml | jq .resources
    "apiVersion": "",
    "kind": "Alert",
    "metadata": {
      "labels": {
        "team": "myteam"
      "name": "myapplication"
    "spec": {
      "alerts": [
          "action": "Se `kubectl describe pod {{ $labels.kubernetes_pod_name }}` for events, og `kubectl logs {{ $labels.kubernetes_pod_name }}` for logger",
          "alert": "myapplication-fails",
          "description": "{{ $ }} er nede i {{ $labels.kubernetes_namespace }}",
          "expr": "up{app=~\"myapplication\",job=\"kubernetes-pods\"} == 0",
          "for": "2m",
          "severity": "danger",
          "sla": "respond within 1h, during office hours"
      "receivers": {
        "slack": {
          "channel": "#myteam"

PS: Templating will not be run if you do not use VARS and/or VAR, meaning \{{ }} will not be handled by nais/deploy.

Default environment variables

These environment variables will be injected into your application container

variable example source
NAIS_APP_NAME myapp from nais.yaml
NAIS_NAMESPACE default metadata.namespace from nais.yaml
NAIS_APP_IMAGE navikt/myapp:69 spec.image from nais.yaml
NAIS_CLUSTER_NAME prod-fss naiserator runtime context
NAIS_CLIENT_ID prod-fss:default:myapp concatenation of cluster, namespace and app name

Environment variables for loading CA bundles into your application will also be injected:

variable example source
NAV_TRUSTSTORE_PATH /etc/ssl/certs/java/cacerts CA bundle containing the most commonly used CA root certificates
NAV_TRUSTSTORE_PASSWORD xxxxx Password for the CA bundle

Build status badge

Use the following URL to create a small badge for your workflow/action.{github_id}/{repository}/workflows/{workflow_name}/badge.svg

Last update: 2023-08-22
Created: 2019-11-13