NATS Server — Docker Image
A minimal Docker image for running the fossil-nats server in a container.
Image size: 24 MB (based on nats:2.10.22-alpine, no extra packages added).
See NATS Server — Setup & Architecture for the full design.
How It Works
right
BLD: box "docker build" fill 0xfff0cc width 1.4 height 0.4 rad 4px
move 0.15
arrow color 0x888888
move 0.15
IMG: box "fossil-nats:2.10.22" color white fill 0x2a4a8a width 1.8 height 0.4 rad 4px
down
ENV: box "OPERATOR_JWT" fill 0xd0f0d0 width 1.4 height 0.35 rad 4px at IMG.s + (-1.0, -0.6)
SYS: box "SYS_ACCOUNT_KEY" fill 0xd0f0d0 width 1.4 height 0.35 rad 4px at IMG.s + (0.6, -0.6)
TLS: box "/var/nats/tls/ (ro)" fill 0xd0eeff width 1.4 height 0.35 rad 4px at IMG.s + (-1.0, -1.1)
JWT: box "/var/nats/jwt/ (rw)" fill 0xd0eeff width 1.4 height 0.35 rad 4px at IMG.s + (0.6, -1.1)
JST: box "/var/nats/jetstream/ (rw)" fill 0xd0eeff width 1.7 height 0.35 rad 4px at IMG.s + (-0.15, -1.6)
arrow from ENV.n to IMG.s + (-1.0, -0.05) color 0x888888 <-
arrow from SYS.n to IMG.s + (0.6, -0.05) color 0x888888 <-
arrow from TLS.n to IMG.s + (-1.0, -0.05) color 0x888888 <-
arrow from JWT.n to IMG.s + (0.6, -0.05) color 0x888888 <-
arrow from JST.n to IMG.s + (-0.15, -0.05) color 0x888888 <-
text "env vars" small at IMG.s + (-0.2, -0.35)
text "volumes" small at IMG.s + (-0.2, -0.85)
right
EP: box "docker-entrypoint.sh" fill 0xf0f0f0 width 1.8 height 0.4 rad 4px at IMG.e + (0.8, -0.3)
arrow from IMG.e right 0.15 then to EP.w color 0x888888
move 0.15
arrow color 0x888888
NS: box "nats-server -c /tmp/nats-server.conf" fill 0xd0f0d0 width 2.8 height 0.4 rad 4px
→ /pikchrshowThe entrypoint:
- Sources
/etc/nats/operator.envif present (mounted volume) - Validates
OPERATOR_JWTandSYS_ACCOUNT_KEYare set - Renders
nats-server.conf.template→/tmp/nats-server.confusingsed exec nats-server -c /tmp/nats-server.conf
No extra packages are installed — config substitution uses sed from busybox (already in nats:alpine).
Files
| File | Purpose |
|---|---|
nats/Dockerfile |
Image definition |
nats/docker-entrypoint.sh |
Startup script — renders config, starts server |
nats/nats-server.conf.template |
Config template with $OPERATOR_JWT / $SYS_ACCOUNT_KEY |
nats/.dockerignore |
Excludes creds/ from build context |
Build
cd ~/Projects/infra/nats
sudo docker build -t fossil-nats:2.10.22 .
Run
Get the required values
# Operator JWT (already in /etc/nats/operator.env on the server)
OPERATOR_JWT=$(sudo sed 's/OPERATOR_JWT=//' /etc/nats/operator.env)
# SYS account public key
SYS_ACCOUNT_KEY=$(nsc describe account --name SYS 2>&1 \
| grep 'Account ID' \
| sed 's/.*| Account ID *| *\([^ ]*\) .*/\1/')
Start the container
sudo docker run -d \
--name fossil-nats \
-p 4222:4222 \
-e OPERATOR_JWT="$OPERATOR_JWT" \
-e SYS_ACCOUNT_KEY="$SYS_ACCOUNT_KEY" \
-v /var/nats/tls:/var/nats/tls:ro \
-v /var/nats/jwt:/var/nats/jwt \
-v /var/nats/jetstream:/var/nats/jetstream \
fossil-nats:2.10.22
Or mount operator.env as a file instead of env vars:
sudo docker run -d \
--name fossil-nats \
-p 4222:4222 \
-e SYS_ACCOUNT_KEY="$SYS_ACCOUNT_KEY" \
-v /etc/nats/operator.env:/etc/nats/operator.env:ro \
-v /var/nats/tls:/var/nats/tls:ro \
-v /var/nats/jwt:/var/nats/jwt \
-v /var/nats/jetstream:/var/nats/jetstream \
fossil-nats:2.10.22
Verify
# Check logs
sudo docker logs fossil-nats
# Connect and list streams
nats stream ls \
--server tls://sync.craftdesign.group:4222 \
--creds ~/Projects/infra/nats/creds/edgesync-admin.creds
# Publish a test message
nats pub fossil.edgesync.test "hello" \
--server tls://sync.craftdesign.group:4222 \
--creds ~/Projects/infra/nats/creds/edgesync-admin.creds
Network Requirements
Before running the container, the operator must complete these steps:
1. Open port 4222 in the firewall
On vps-nbg1 (UFW):
sudo ufw allow 4222/tcp
sudo ufw status | grep 4222
On another host (varies by provider):
- Open TCP port 4222 inbound in your cloud provider's security group / firewall rule.
- There is no other NATS port to open for single-node TLS-only setup.
2. Add a DNS A record
Clients connect using the hostname in the TLS cert. The cert is currently issued for sync.craftdesign.group. For a new host, either:
| Option | When to use | Steps |
|---|---|---|
| Reuse the existing cert | Deploying on vps-nbg1 or a machine that can serve sync.craftdesign.group |
Mount /var/nats/tls — no DNS change needed on the host |
| Issue a new cert for your domain | Deploying on a new server with its own hostname | Run install.sh (Phase 1) on that server — it issues a cert for the DOMAIN you set |
| Self-signed cert (dev/test only) | Local dev machine, no public DNS | Generate a self-signed cert and mount it; clients must use --tlsca |
For the standard case (new server, new domain):
- Create DNS A record:
your-nats.example.com→<server-ip> - Set
DOMAIN=your-nats.example.comininstall.shbefore running - The TLS cert is issued automatically via acme.sh DNS-01
3. Verify DNS before starting
# From any machine — must resolve before TLS cert is issued
nslookup your-nats.example.com
# or
dig +short your-nats.example.com
TLS Note
The cert baked into the server config is for sync.craftdesign.group (Let's Encrypt, auto-renewed via acme.sh on the host). When running the container:
- On vps-nbg1: mount
/var/nats/tls— the cert is already there, and the DNS resolves correctly. - Elsewhere: replace
/var/nats/tlswith a cert valid for your hostname, and update thecert_file/key_filepaths innats-server.conf.templateif they differ.
Clients must connect using the hostname the cert was issued for (sync.craftdesign.group), not by IP, for TLS verification to pass.
On This Server (vps-nbg1)
NATS runs under systemd (not Docker) in production — see NATS Server — Operations Runbook. The Docker image is provided for:
- Deploying to a second node in a future cluster
- Running NATS on a dev machine or CI runner that connects as a leaf node
- Testing config changes in isolation before applying to the production systemd instance