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 openallowCIDR.
What Gets Created
- Stateful NATS Workload (
RELEASE_NAME-nats) — one NATS cluster per configured location, running that location’sreplicasservers. UsesreplicaDirectaddressing, 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/natsfor the JetStream store. - Secret (
RELEASE_NAME-nats-secret) — an opaque startup script that generates each server’snats.confat boot from the location it is running in. - Secret (
RELEASE_NAME-nats-extra-data) (only whennats_extra_configis set) — the extra configuration, appended verbatim to the generated config. - Identity & Two Policies — an identity bound to the workload, with
revealon this release’s secrets and nothing else, plusviewon 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 inlocations. 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:
spec.staticPlacement.locationLinks. To add a missing one:
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.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.
Configuration
The defaultvalues.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 withreplicas: 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.
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.Image and Resources
image— the NATS container image. Defaults to the officialnats: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 binds0.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.
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.cpln port-forward:
JetStream
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
Thevolumeset 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.
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:
Internal Access
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
readybadge readfalsepermanently while per-location status staystrue. 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
allowCIDRwithout anauthorization { ... }block innats_extra_configpublishes an unauthenticated bus. - A firewall change takes roughly 30 seconds to several minutes to propagate. After changing
allowCIDRorinternalAccess, 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_configis 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