Managed NATS
Managed NATS provides private messaging between applications in the same SatuSky organization. This guide creates a Core NATS instance, downloads its connection credentials without displaying them, and verifies publish/subscribe behavior from a Node.js application.
Core NATS is for live messaging, request/reply, and event fan-out. Messages are not persisted when no subscriber is listening.
Prerequisites
Section titled “Prerequisites”1ctl auth statusshows the expected account and organization- A deployed application in the same organization
- Node.js 20 or later for the example client
The active 1ctl organization determines where NATS is created. Managed NATS is private; it is not exposed through a public domain.
1. Create Core NATS
Section titled “1. Create Core NATS”Choose a name that is unique within your organization:
NATS_NAME=events1ctl nats create "$NATS_NAME"Creation is asynchronous. Inspect the durable deployment record:
1ctl nats get "$NATS_NAME"1ctl nats listCheck live readiness:
1ctl nats status "$NATS_NAME"If status briefly reports Deployment status not found immediately after creation, wait and try again. Do not create a second instance with a different name just to work around a readiness lookup.
2. Download credentials safely
Section titled “2. Download credentials safely”Create a local credentials directory that is excluded from source control:
printf '\n.secrets/\n' >> .gitignore1ctl nats credentials "$NATS_NAME" --output-dir .secrets/natsThe CLI creates the directory with mode 0700 and writes client-url.txt and client-token.txt with mode 0600. It refuses existing files and symbolic links rather than silently overwriting credentials.
Never commit this directory, paste the token into satusky.toml, or use --stdout in CI logs.
3. Attach NATS to an application
Section titled “3. Attach NATS to an application”From the application directory, store the private URL and token as application secrets:
1ctl secret create \ --kv NATS_URL="$(cat .secrets/nats/client-url.txt)" \ --kv NATS_TOKEN="$(cat .secrets/nats/client-token.txt)"Restart the application so its next container receives the new environment values:
1ctl app restart YOUR_APP_NAMEReplace YOUR_APP_NAME with the [app].name from that application’s satusky.toml.
Once the secrets are attached, remove the local copies using your operating system’s secure file-removal procedure. The values remain available to the deployed application through NATS_URL and NATS_TOKEN.
4. Publish and receive a message
Section titled “4. Publish and receive a message”Install the official JavaScript client in your Node.js application:
npm install natsCreate nats-check.mjs:
import { connect, StringCodec } from 'nats';
const connection = await connect({ servers: process.env.NATS_URL, token: process.env.NATS_TOKEN,});
const codec = StringCodec();const subject = 'orders.created';const subscription = connection.subscribe(subject, { max: 1 });
connection.publish(subject, codec.encode('order-123'));
for await (const message of subscription) { console.log(codec.decode(message.data));}
await connection.drain();Run this check inside the deployed application or as part of its startup smoke test:
node nats-check.mjsExpected output:
order-123This verifies authentication, private service discovery, publishing, and subscription. It does not verify persistence: Core NATS delivers live messages and does not retain them for a later consumer.
5. Diagnose connectivity
Section titled “5. Diagnose connectivity”Inspect the managed service first:
1ctl nats get "$NATS_NAME"1ctl nats status "$NATS_NAME"Then inspect the application receiving the credentials:
1ctl app status YOUR_APP_NAME1ctl logs --tail 50Check these application-level causes before rotating credentials:
- The NATS service and client application use the same SatuSky organization.
- The application was restarted after its NATS secrets were attached.
- The client uses both
NATS_URLandNATS_TOKEN. - Publishers and subscribers use the same subject spelling.
Do not expose the client port with a custom domain. NATS traffic stays on the organization’s private network.
6. Create JetStream HA
Section titled “6. Create JetStream HA”JetStream is intended for durable streams, replay, and consumers that receive messages published while they were offline. The CLI exposes a three-replica profile:
JETSTREAM_NAME=events-ha1ctl nats create "$JETSTREAM_NAME" \ --jetstream \ --storage-size 20Gi \ --storage-class ceph-blockWait for the durable record and live service to become ready:
1ctl nats get "$JETSTREAM_NAME"1ctl nats status "$JETSTREAM_NAME"The deployment uses three NATS replicas and three persistent volumes. Download and attach its credentials using the same procedure as Core NATS:
1ctl nats credentials "$JETSTREAM_NAME" --output-dir .secrets/nats-ha1ctl secret create \ --kv NATS_URL="$(cat .secrets/nats-ha/client-url.txt)" \ --kv NATS_TOKEN="$(cat .secrets/nats-ha/client-token.txt)"1ctl app restart YOUR_APP_NAMECreate jetstream-check.mjs in the Node.js application:
import { connect, StorageType, StringCodec } from 'nats';
const connection = await connect({ servers: process.env.NATS_URL, token: process.env.NATS_TOKEN,});
const manager = await connection.jetstreamManager();const jetstream = connection.jetstream();const codec = StringCodec();
try { await manager.streams.add({ name: 'GUIDE_EVENTS', subjects: ['guide.events'], storage: StorageType.File, num_replicas: 3, });} catch (error) { if (error.api_error?.err_code !== 10058) { throw error; }}
await jetstream.publish('guide.events', codec.encode('persisted-event'));const info = await manager.streams.info('GUIDE_EVENTS');console.log(`stored messages: ${info.state.messages}`);
await connection.drain();Run it inside the deployed application:
node jetstream-check.mjsExpected output includes:
stored messages: 1Run the script again later and the count increases because file-backed messages survive client and NATS pod restarts. Platform verification also confirms that a published message remains consumable after a JetStream replica is replaced.
7. Delete NATS
Section titled “7. Delete NATS”Delete a Core NATS instance when no application depends on it:
1ctl nats delete "$NATS_NAME" --yesCore NATS has no persistent message volume.
JetStream deletion retains persistent volumes by default so stream data can be recovered. To permanently delete the deployment and its generated volumes:
1ctl nats delete "$JETSTREAM_NAME" --purge-retained --yes--purge-retained permanently deletes the retained volumes. The platform scopes the purge to volumes owned by that exact deployment. Use it only after confirming that no stream data must be recovered.
What this guide teaches
Section titled “What this guide teaches”- Core NATS is a private, token-authenticated organization service.
- Credentials belong in application secrets, not source control or
satusky.toml. - Publish/subscribe proves live messaging but not persistence.
- JetStream HA uses three replicas and file-backed persistent volumes for durable streams.
- NATS deletion retains JetStream data unless
--purge-retainedis explicitly selected.
Command reference
Section titled “Command reference”See 1ctl nats and the generated references for create, credentials, and delete.