Skip to main content
Template version 3.0.0 is a breaking change, and two of the changes need action before you install.
  • The template no longer creates a GVC. It deploys into the GVC you install into. The gvc.name and gvc.locations values are gone; locations moved to a top-level locations list.
  • Never upgrade a 2.x release onto 3.0.0 in place. A 2.x release owns the GVC it created, and Helm deletes what a chart stops declaring — the upgrade destroys that GVC and every workload, volume set and identity inside it, including your JetStream data. Migrate to a new release instead: Migrating from 2.x.
  • NATS is now closed to the internet by default. allowCIDR was 0.0.0.0/0, and this template configures no NATS authentication, so a default install published an unauthenticated message bus. It is now []. See Public Access.

Overview

NATS is an open-source, high-performance, lightweight messaging system optimized for cloud-native architectures. It supports pub/sub, queueing, and request/reply patterns. This template deploys a NATS cluster into an existing GVC: one cluster per configured location, joined into a cross-region super cluster over NATS gateways when you configure more than one. JetStream can be enabled for durable streams, consumers, a key-value store, and an object store, with a dedicated volume per server. A WebSocket listener is available for browser clients, served by Control Plane on port 443 once you open allowCIDR.

What Gets Created

  • Stateful NATS Workload (RELEASE_NAME-nats) — one NATS cluster per configured location, running that location’s replicas servers. Uses replicaDirect addressing, which is what the route and gateway URLs are built from. Binds the client port (4222), the cluster port (6222), the WebSocket port (8080) when enabled, and the gateway port (7222) when more than one location is configured.
  • Volume Set (RELEASE_NAME-nats-vs) (only when JetStream is enabled) — per-server xfs storage at /data/nats for the JetStream store.
  • Secret (RELEASE_NAME-nats-secret) — an opaque startup script that generates each server’s nats.conf at boot from the location it is running in.
  • Secret (RELEASE_NAME-nats-extra-data) (only when nats_extra_config is set) — the extra configuration, appended verbatim to the generated config.
  • Identity & Two Policies — an identity bound to the workload, with reveal on this release’s secrets and nothing else, plus view on the one GVC you install into so each server can confirm at boot that the GVC really has every location you listed.
This template does not create a GVC. Every resource lands in the GVC you install into, so cpln workload exec, cpln logs and uninstalling all work against that GVC, and uninstalling can never delete it. Nothing runs in a GVC location you did not list in locations.

Prerequisites

A GVC must already exist, and it must contain every location you list in locations. The requirement is one-directional — the GVC may have more locations than you list, and no NATS server runs in those. Check what a GVC has before installing:
The locations are under spec.staticPlacement.locationLinks. To add a missing one:
Every workload in a GVC runs in every location that GVC has, so add locations to a shared GVC deliberately.
A location the GVC does not have is not caught at install time. The install succeeds — the platform stores the placement without validating it — and the servers declared there never start, while the servers that did start keep retrying route and gateway URLs that can never answer. Each server reads the GVC at boot and reports it:
With JetStream enabled, a server with an empty store refuses to bootstrap on this; a server whose store already holds data warns and keeps serving, so the check can never take a live cluster down. With JetStream off it only ever warns. Read it with a server-side filter:
Nothing else is required for a default install — this template takes no prerequisite secrets.

Installation

To install, follow the instructions for your preferred method:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Migrating from 2.x

Template versions through 2.0.0 created their own GVC, so that GVC is part of the 2.x release’s manifest. Version 3.0.0 does not declare it — and Helm deletes what a chart stops declaring.
An in-place upgrade from 2.x to 3.0.0 destroys the deployment. Measured on a sibling template with the guard removed: the upgrade deleted the GVC and every workload, volume set and identity inside it in 6 seconds, and reported that the release had been upgraded successfully. Reading the GVC back afterwards returned 404. With JetStream enabled, the volume set holds your streams.The chart ships a render-time refusal so this cannot happen by accident: any leftover gvc key in your values aborts the upgrade before a single API call is made, leaving your cluster untouched and running.
The guard cannot cover one case: an upgrade run with no values at all. A 2.x release installed on pure defaults has no gvc key for the chart to see, so nothing fires and the deletion proceeds. Do not run an upgrade of a 2.x release against the 3.0.0 chart under any circumstances — migrate to a new release instead.
1

Choose the GVC for the new release

Create or pick a GVC and make sure it has exactly the locations you intend to list in locations. See Prerequisites.
2

Install 3.0.0 as a NEW release

Use a different release name: secret names are org-wide, so a same-named release collides with the 2.x one even in another GVC. Translate your old gvc.locations into the top-level locations list, and note that allowCIDR now defaults to closed — if the 2.x deployment served browser clients over WebSocket, set your own ranges explicitly.
3

Move the traffic

Point publishers and subscribers at the new release. With JetStream, mirror the streams across with the nats CLI before cutting over — a stream is not carried by the chart.
4

Uninstall the old release against the GVC you installed it into

Not the GVC it created — the GVC you passed at install time is where Helm tracks the release, and uninstalling from there takes the created GVC with it.
Values that moved or were removed in 3.0.0. The chart names each one at render time rather than ignoring it, so an old values file produces an error instead of a silent misconfiguration:

Configuration

The default values.yaml for this template:

Locations and Cluster Sizing

Each entry pairs a location with a server count. Every location listed must already exist in the GVC you install into; extra GVC locations are ignored. Listing the same location twice is rejected at render, as is a location with replicas: 0 — to stop running somewhere, remove the entry. The default is one location with three servers: the smallest shape that is both a real NATS cluster and installable on any single-location GVC.
Only the 2-server shape is refused, because it is the one reached by accident: scaling from 1 to 2 looks like an improvement and is not. A 3-server roster whose GVC is missing one location becomes this shape at runtime, and the startup script warns when it sees exactly 2 servers running.
Adding a second location does not change the quorum arithmetic — it buys geographic locality. Each location is its own NATS cluster, so clients connect to servers near them, and gateways forward only the traffic that has interested subscribers on the other side. JetStream’s meta group spans the whole super cluster, so size the total server count against the table above, and give each location at least 3 servers if you want streams to survive losing a whole location.
GVC locations you did not list run nothing. Their deployment reads This workload location is deactivated because maxScale is set to 0. That is the mechanism that keeps a shared GVC safe — it is what a healthy install looks like, not a fault. One consequence is worth knowing: the workload-level ready badge then reports false permanently, because that location’s deployment never becomes ready, while the per-location status stays true. Gate any script on the per-location status, not the rollup.
Cross-region gateway traffic is billed, which is why a single location is the default.

Image and Resources

  • image — the NATS container image. Defaults to the official nats:2.11.6-alpine; bump the tag to upgrade NATS.
  • resources.cpu / resources.memory — allocated to each NATS server. The defaults are small; raise them for real throughput.

Listeners

Every listener binds 0.0.0.0 on its configured port. Monitoring is fixed on 8222 and is not published as a container port. The template refuses at render time to bind a port Control Plane reserves for system use, and to put two listeners on the same port. Both failures are otherwise invisible to helm template: a reserved port is rejected by the API at install, and a port collision is accepted and then crash-loops the container with address already in use.

Public Access

allowCIDR lists the inbound CIDR ranges allowed to reach the WebSocket endpoint from the internet. It applies to the WebSocket port only — the client (4222), cluster (6222) and gateway (7222) ports are never exposed publicly.
This template configures no NATS authentication, so opening allowCIDR publishes an unauthenticated message bus. Measured against an open deployment: an anonymous outside client was handed the cluster name, every server name and their private IPs, and could publish and subscribe freely. Version 3.0.0 changed the default from 0.0.0.0/0 to [] for this reason.Open it only alongside an authorization { ... } block in nats_extra_config, and prefer the narrowest ranges that cover your clients.
Do not pin a single /32. Measured during testing: a client behind a corporate proxy pool moved to a different address, and a perfectly healthy deployment began returning 403 to it. Widening the range restored access. Use a range wide enough to cover your egress, not just the address you happen to have today.
To reach a closed deployment without opening it at all, tunnel to it with cpln port-forward:

JetStream

Without JetStream, NATS is in-flight only: a message with no connected subscriber is gone. Enabling it adds durable streams, durable consumers, a key-value store and an object store, and creates the volume set. Sizing is decided by the total server count — see the table under Locations and Cluster Sizing.
Streams default to num_replicas: 1, so stream data lives on a single server. A stream only survives losing a server if you set num_replicas to 3 or more when you create the stream — that is per-stream, in your application, not a setting in this template.

Volume Set

The volumeset block applies only when jetstream.enabled: true.
  • volumeset.capacity — initial volume size in GiB per server, minimum 10. Each server gets its own volume at /data/nats.
Uninstalling the release deletes the volume set and the JetStream data on it.

Extra NATS Configuration

nats_extra_config is appended verbatim to the generated server configuration at startup. Use it for anything the template does not expose — payload limits, accounts, authorization:
This block is injected verbatim and is not validated at render time. A syntax error here is a container that will not start, which is easy to mistake for an infrastructure problem. Check the workload log for the NATS server’s own parse error.

Internal Access

This list is not only about client traffic. The same internal firewall governs the route connections between servers in a location and the gateway connections between locations. A workload-list naming only your applications would cut the cluster off from itself: routes never establish, each server runs alone, and JetStream loses its meta group — while every replica still reports ready: true, because this workload has no readiness probe and readiness says nothing about clustering.The template adds its own workload to the list for you, so you only need to list your clients. For the same reason, internalAccess.type: none is refused at render for any deployment with more than one server; it is valid only for a single server reached over the public WebSocket endpoint.

Connecting to NATS

Use the fully-qualified .GVC_NAME.cpln.local form — the bare workload name does not resolve reliably from every workload type.
Read the public hostname, never assemble it. The shape of a canonical endpoint varies between GVCs, so a hand-built hostname is a guess:
The value is status.canonicalEndpoint. Control Plane terminates TLS and serves the WebSocket listener on port 443, so connect with wss:// on the default port.

Important Notes

  • Never upgrade a 2.x release onto 3.0.0 in place — it deletes the GVC the 2.x chart created and everything in it. Migrate to a new release: Migrating from 2.x.
  • The GVC must contain every location you list, and may contain more. A missing location is not caught at install; each server reports it at boot. With JetStream on, a server with an empty store refuses to bootstrap and one that already holds data warns and keeps serving.
  • A GVC location you did not list runs nothing, and makes the workload’s rollup ready badge read false permanently while per-location status stays true. That is the expected result of pinning the location list, not a fault.
  • There is no public access by default, and no NATS authentication. Opening allowCIDR without an authorization { ... } block in nats_extra_config publishes an unauthenticated bus.
  • A firewall change takes roughly 30 seconds to several minutes to propagate. After changing allowCIDR or internalAccess, keep re-testing rather than concluding the knob is broken.
  • A rolling restart of a stateful NATS tier is slow — between about 5 and 9 minutes for 3 servers, one replica at a time, with a JetStream leader election in the middle if JetStream is on. The bus keeps serving throughout; a restart that is still converging after 5 minutes has not hung.
  • JetStream data lives on the volume set and is deleted when you uninstall. Streams also default to num_replicas: 1 — set it higher per stream if a stream must survive a server loss.
  • nats_extra_config is injected verbatim, so a syntax error there stops the container rather than failing the render.

External References

NATS Documentation

Official NATS documentation

JetStream

NATS JetStream persistence and streaming

Clustering

Route connections between servers in a cluster

Super Clusters and Gateways

Joining clusters across locations with gateways

Configuration Reference

Every NATS server configuration option

NATS Template

View the source files, default values, and chart definition