Skip to content

Docker Compose recipe for running NATS with JWT authentication, NSC bootstrap, JetStream, and WebSocket support.

License

Notifications You must be signed in to change notification settings

teyfix/nats-docker-compose-recipe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NATS Service

This repository defines the NATS messaging service with JWT authentication enabled. The setup is fully containerized and designed to be reproducible across environments.

Quick Start

1. Clone the repository

Clone the repository and navigate to the directory.

git clone https://github.com/teyfix/nats-docker-compose-recipe.git
cd nats-docker-compose-recipe

2. Update enviroment variables in .env as needed

Although repository has the .env file with default values, you can override them as needed.

NATS_OPERATOR="my_operator"
NATS_ACCOUNT="my_account"
NATS_VER="2.10"
NATS_BOX_VER="0.19.2"

3. Bootstrap and start NATS

Bootstrap the system and start the NATS server. We are starting nats-export container so the signing keys will be exported after the NATS server starts.

docker compose up nats-init nats-server nats-push nats-export

4. Run the example client

# 3. Run the example client
bun install
bun run dev

Table of Contents


What This Service Does

At a high level, this service:

  • Runs a NATS server with:

    • Core NATS (TCP clients)
    • JetStream for persistence and replay
    • WebSocket support for browser-based clients
  • Bootstraps NATS Operator and Account state automatically using nsc

  • Generates and exports signing keys for external services

  • Exposes NATS, WebSocket, and monitoring endpoints through Traefik

  • Stores all state (JetStream + NSC) in Docker volumes

This allows application services to authenticate using JWTs, without NATS needing to consult an external database or authority.


Architecture Overview

Containers

  • nats-init

    • One-time bootstrap container

    • Creates:

      • Operator
      • System account
      • Application account
      • Account signing key
    • Generates the JWT resolver configuration

    • Idempotent: exits immediately if state already exists

  • nats-server

    • The actual NATS server

    • JetStream enabled

    • Loads resolver configuration generated by nats-init

    • Exposes:

      • TCP clients (4222)
      • WebSocket clients (9222)
      • Monitoring (8222)
  • nats-push

    • Pushes operator and account JWTs into the running server
    • Runs nsc push -A
    • Ensures the server has the latest account claims
  • nats-export

    • Extracts account signing keys for external use
    • Writes them to .env-style files
    • Intended for trusted backend services only

Configuration Details

NATS Server (nats.conf)

  • Client Port: 4222

  • Monitoring: http://:8222

  • WebSocket: 0.0.0.0:9222 (no TLS, TLS handled by Traefik)

  • JetStream:

    • Memory: 2G
    • Disk: 10Gi
    • Data directory: /data
  • Graceful shutdown:

    • Lame duck mode enabled for safe restarts

JWT resolution is handled via an NSC-generated resolver included at startup.


Authentication Model

This service uses NATS JWT-based authentication:

  • Operator and Account are created automatically
  • Clients authenticate using user JWTs
  • NATS does not perform per-room or per-subject authorization checks beyond JWT claims
  • Authorization logic lives entirely in application services

This means:

  • No database tables are required for NATS auth
  • Room join/leave enforcement is handled at the app layer
  • NATS acts purely as a message router + persistence layer

Security Model (Read This Carefully)

This setup uses JWT-based authorization, where trust is derived from cryptographic keys rather than server-side state.

Trust boundaries

  • Operator

    • Root authority
    • Defines global trust
    • Should almost never be used outside bootstrapping
  • Account

    • Represents an isolated namespace
    • Owns users and permissions
    • Signs user JWTs via an account signing key
  • User JWTs

    • Short-lived credentials
    • Define exact publish/subscribe permissions
    • Can be safely issued by application services

Critical rules

  • NATS never stores user secrets

  • Account signing keys are powerful

    • Anyone with this key can mint arbitrary users
  • User JWTs should be short-lived

  • nats-export is intentionally dangerous

    • It extracts private signing keys
    • Intended for CI or trusted backend services only
    • Never expose exported keys to browsers or clients

If an attacker obtains:

  • Account signing seed → they can create any user
  • User credentials → damage is limited by JWT permissions and expiration

This design favors stateless authorization, clear authority, and predictable failure modes.


Traefik Exposure

The service is exposed through Traefik with:

  • TCP (TLS): nats.<domain> → port 4222
  • WebSocket (HTTPS): https://nats.<domain>/ws
  • Monitoring UI (HTTPS): https://nats.<domain>/

TLS termination is handled by Traefik using the configured certificate resolver.


Volumes

  • nats_data – JetStream persistence
  • nsc_data – NSC state (operator, accounts, keys)
  • nsc_config – Generated resolver configuration

These volumes allow the system to restart without losing identity or message state.


Environment Variables

Required:

  • NATS_OPERATOR – Operator name
  • NATS_ACCOUNT – Account name

Optional / infrastructure-dependent:

  • NATS_VER
  • NATS_BOX_VER
  • TRAEFIK_* variables

Exported signing keys are written to:

  • svc/nats/fs/nats-export/secrets/.env.operator
  • svc/nats/fs/nats-export/secrets/.env.account

Typical Use Cases

  • Real-time messaging between backend services
  • Browser clients connecting via WebSockets
  • Temporary or long-lived rooms backed by JetStream
  • Event-driven workflows with replay and durability

Allowing clients to connect

After obtaining the account credentials with nats-export container, you can generate NATS credentials for users and allow them to connect themselves.

Here is the trimmed version of src/client.ts:

import { connect, credsAuthenticator } from "nats.ws";
import { encodeUser } from "nats-jwt";
import { createUser, fromSeed } from "nkeys.js";

const encodeText = (text: string) => new TextEncoder().encode(text);
const decodeText = (text: Uint8Array) => new TextDecoder().decode(text);

// Can be obtained with `nats-export` container
const accountKey = process.env.NATS_ACCOUNT_KEY!;
const accountSeed = process.env.NATS_ACCOUNT_SECRET!;

// The user we want to create credentials for.
const userId = "john-doe";

// Create user keypair
const userKP = createUser();
const userSeed = decodeText(userKP.getSeed());

// Load account keypair (issuer)
const accountKP = fromSeed(encodeText(accountSeed));

// UNIX timestamp for expiration
const exp = Math.floor(Date.now() / 1000) + 30 * 60;

// Encode & sign user JWT
const jwt = await encodeUser(
  `user-${userId}`, //
  userKP,
  accountKP,
  {
    issuer_account: accountKey,
    pub: { allow: [`users.${userId}.>`], deny: [] },
    sub: { allow: [`users.${userId}.>`], deny: [] },
  },
  { exp }
);

const creds = `
-----BEGIN NATS USER JWT-----
${jwt}
------END NATS USER JWT------

-----BEGIN USER NKEY SEED-----
${userSeed}
------END USER NKEY SEED------
`;

const client = await connect({
  authenticator: credsAuthenticator(encodeText(creds)),
});

Notes

  • Clustering is currently disabled but pre-configured in comments
  • WebSocket TLS is intentionally disabled at NATS level
  • All authority is derived from JWTs, not server-side state

Startup Order

  1. nats-init initializes operator/account
  2. nats-server starts with resolver config
  3. nats pushes NSC state
  4. nats-export optionally exports signing keys

Health Check

The NATS server is considered healthy when:

wget -q --spider http://localhost:8222/healthz

returns successfully.


This setup prioritizes stateless authorization, simple operations, and clear separation of concerns between messaging infrastructure and application logic.

About

Docker Compose recipe for running NATS with JWT authentication, NSC bootstrap, JetStream, and WebSocket support.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published