Deployment Reference
This guide describes how to deploy Nexus GSLB in development and production, including Kubernetes, Docker, systemd bare-metal, and a multi-region topology using NATS super-clusters.
| Method | When to use |
|---|---|
| Kubernetes | Production — cloud or on-prem clusters, multi-replica, full lifecycle management |
| Docker Compose | Single-host or local dev/staging |
| systemd | Bare-metal Linux without a container runtime |
Prerequisites
- Go 1.25+ to build from source.
- A Git repository hosting your configuration (Gitea/GitLab CE recommended), with signed commits.
- Optional: NATS 2.x cluster(s) with JetStream enabled for state sync.
CI/CD pipeline
The repository ships a GitHub Actions workflow at .github/workflows/ci.yml.
What it does
| Trigger | Jobs |
|---|---|
| Every push and pull request | test: go mod verify, go vet ./..., go test -race -timeout 5m ./... |
Push to main or v*.*.* tag |
docker: multi-arch image build + push to registry.starstorm.dev, SBOM |
Container registry
Images are published to registry.starstorm.dev/nexus-gslb/gslbd (Forgejo Container Registry at code.starstorm.dev). The CI workflow requires two repository secrets: REGISTRY_USER (your Forgejo username) and REGISTRY_TOKEN (a Forgejo personal access token with package:write scope — create under Account Settings → Applications).
!!! tip "Pin Images in Production" Use specific SHA tags (e.g., sha-<short-sha>) instead of latest for reproducible deployments.
| Tag pattern | When created |
|---|---|
latest |
Every push to main |
sha-<short-sha> |
Every push to main |
1.2.3 / 1.2 |
When a v1.2.3 Git tag is pushed |
Pull the latest image:
docker pull registry.starstorm.dev/nexus-gslb/gslbd:latestImage properties
- Multi-arch:
linux/amd64andlinux/arm64in a single manifest. - Build provenance: SLSA provenance attestation attached (verifiable with
cosign verify-attestation). - SBOM: Software Bill of Materials attached as an OCI attestation (CycloneDX).
Version metadata
Every binary and image includes build-time version info logged at startup:
{"level":"INFO","msg":"starting nexus","version":"1.2.3","commit":"abc1234...","built":"2026-04-24T10:00:00Z"}
This is injected via -ldflags "-X main.version=... -X main.commit=... -X main.buildDate=..." during the Docker build.
First-time repository setup
Follow these steps once when setting up the canonical repository on Forgejo:
1. Add CI secrets
In Forgejo: repo → Settings → Secrets → Actions → add:
| Secret | Value |
|---|---|
REGISTRY_USER |
Your Forgejo username |
REGISTRY_TOKEN |
A Forgejo PAT with package:write scope (Account Settings → Applications) |
2. Push the main branch
git remote add origin https://code.starstorm.dev/Nexus-GSLB/nexus.git
git push origin mainThis triggers the test + docker jobs. Monitor progress at code.starstorm.dev/Nexus-GSLB/nexus/actions. Images appear at code.starstorm.dev/Nexus-GSLB/-/packages once the docker job passes.
3. Cut a release
git tag v0.1.0
git push origin v0.1.0This triggers the release job: cross-compiles 5 platforms, builds DEB/RPM packages, and uploads all archives + a checksums file to a Forgejo release at code.starstorm.dev/Nexus-GSLB/nexus/releases.
Adding CI to a fork
- Fork the repository.
- Push a commit or open a PR; the
testjob runs automatically (no secrets needed). - To push images, add
REGISTRY_USERandREGISTRY_TOKENas repository secrets pointing at your own Forgejo instance.
Build from source
go build -trimpath -ldflags="-s -w" -o gslbd ./cmd/gslbd
go build -trimpath -ldflags="-s -w" -o gslbctl ./cmd/gslbctlOption A: Kubernetes
Manifests are in deploy/kubernetes/. See docs/user/Kubernetes.md for the full guide including scaling, DNSSEC, secrets management, and ServiceMonitor setup.
# Quick deploy
kubectl apply -k deploy/kubernetes/Option B: Docker
A production-ready multi-stage Dockerfile and docker-compose.yml are included in the repository root.
Quick start
# Edit deploy/config.yaml first, then:
docker compose up -dThe compose stack:
- Runs
rqlited(the required distributed-SQLite datastore, upstreamrqlite/rqliteimage, started with-fk) alongsidegslbd;gslbdwaits for rqlite's/readyzhealthcheck before starting and connects to it viarqlite.httpAddr: rqlited:4001indeploy/config.yaml - Mounts
deploy/config.yamlread-only at/etc/gslb/config.yaml - Persists the rqlite database in a named Docker volume (
rqlite-data) and gslbd state ingslbd-data - Passes
GSLB_LICENSE_KEY/GSLB_LICENSE_SECRETfrom the host environment
Exposed ports:
| Port | Protocol | Purpose |
|---|---|---|
| 5353 | UDP + TCP | DNS |
| 8080 | TCP | REST API |
| 9090 | TCP | Prometheus metrics |
License credentials
Set credentials via environment variables (preferred over config file):
export GSLB_LICENSE_KEY=your-key
export GSLB_LICENSE_SECRET=your-secret
docker compose up -dDNSSEC key files
Mount PEM files into the container alongside the config:
# docker-compose.yml (extend the volumes block)
volumes:
- ./deploy/config.yaml:/etc/gslb/config.yaml:ro
- ./keys/ksk.pem:/etc/gslb/ksk.pem:ro
- ./keys/zsk.pem:/etc/gslb/zsk.pem:ro
- gslbd-data:/var/lib/gslbdThen reference them in config.yaml:
dnssec:
enabled: true
zone: "gslb.example.com."
ksk:
pemFile: "/etc/gslb/ksk.pem"
zsk:
pemFile: "/etc/gslb/zsk.pem"
signatureValidityDays: 7Option C: systemd (bare-metal)
An automated install script and hardened unit file are provided in deploy/.
Automated install
# Install latest release (detects amd64 / arm64 automatically)
curl -fsSL https://code.starstorm.dev/Nexus-GSLB/nexus/raw/branch/main/deploy/install.sh | sudo bash
# Or pin to a specific version:
sudo bash <(curl -fsSL .../install.sh) v0.1.0
# Or from a local archive (inside the extracted tarball):
sudo bash deploy/install.shThe script:
- Downloads the pre-built release archive from
code.starstorm.dev - Verifies the SHA256 checksum
- Installs
gslbdandgslbctlto/usr/local/bin/(previous binaries saved as.bak) - Creates a dedicated
gslbdsystem user (no shell, no home) - Creates
/etc/gslb/and/var/lib/gslbd/with correct ownership - Copies
config.yaml.exampleas a starting point (skipped if config already exists) - Installs and enables the systemd unit
To pin a version set NEXUS_VERSION=v0.1.0 in the environment before running.
After install:
# Edit config and credentials, then start
sudo nano /etc/gslb/config.yaml
sudo nano /etc/gslb/gslbd.env # GSLB_LICENSE_KEY=... GSLB_LICENSE_SECRET=...
sudo systemctl start gslbd
journalctl -u gslbd -fDEB / RPM packages
Each release also publishes .deb and .rpm packages for amd64 and arm64. Download from the releases page:
# Debian / Ubuntu
wget https://code.starstorm.dev/Nexus-GSLB/nexus/releases/download/v0.1.0/nexus-gslb_0.1.0_linux_amd64.deb
sudo dpkg -i nexus-gslb_0.1.0_linux_amd64.deb
# RHEL / Fedora / Rocky
wget https://code.starstorm.dev/Nexus-GSLB/nexus/releases/download/v0.1.0/nexus-gslb-0.1.0-1.amd64.rpm
sudo rpm -i nexus-gslb-0.1.0-1.amd64.rpmBoth package types:
- Create the
gslbdsystem user - Install binaries to
/usr/local/bin/ - Install and enable the systemd unit
- Install example config to
/etc/gslb/config.yaml(not overwritten on upgrade)
Manual unit file reference
The unit file at deploy/gslbd.service runs with the following hardening:
NoNewPrivileges=yesProtectSystem=strict— filesystem read-only exceptStateDirectoryandConfigurationDirectoryPrivateTmp=yes,PrivateDevices=yesRestrictAddressFamilies=AF_INET AF_INET6 AF_UNIXSystemCallFilter=@system-service- Credentials via
EnvironmentFile=/etc/gslb/gslbd.env(not in config.yaml)
Port 53 note: the default DNS port is 5353 and requires no special capabilities. To bind to port 53, uncomment AmbientCapabilities=CAP_NET_BIND_SERVICE in the unit file.
Upgrade (systemd)
# 1. Take a pre-upgrade backup (if auto-backup is not enabled)
sudo -u gslbd gslbd -config /etc/gslb/config.yaml & # temporary
# Or trigger manually via API:
# curl -X POST http://localhost:8080/api/v1/restart # restarts; backup runs on next tick
# 2. Build and install new binary
go build -trimpath -ldflags="-s -w" -o gslbd ./cmd/gslbd
sudo install -m 755 gslbd /usr/local/bin/gslbd
# 3. Restart
sudo systemctl restart gslbd
journalctl -u gslbd -f
# 4. Verify
dig @127.0.0.1 -p 5353 A <your-domain>
curl http://localhost:8080/api/v1/healthRollback (systemd)
Keep the previous binary at /usr/local/bin/gslbd.prev:
# Before upgrading:
sudo cp /usr/local/bin/gslbd /usr/local/bin/gslbd.prev
# To roll back:
sudo install -m 755 /usr/local/bin/gslbd.prev /usr/local/bin/gslbd
sudo systemctl restart gslbdUpgrade & rollback — all methods
Data safety before any upgrade
- Backup the database — if
backup.enabled: false, take a manual snapshot:# SQLite online backup (safe while daemon is running) sqlite3 /var/lib/gslbd/gslbd.db "VACUUM INTO '/tmp/pre-upgrade-$(date +%Y%m%d).db';" - Record the current image/binary tag — needed for rollback.
- Test the config against the new version before restarting (schema migrations are additive and run automatically; the validator will reject missing required fields with a clear error message).
Upgrade: Docker Compose
# Pull the new image
docker pull registry.starstorm.dev/nexus-gslb/gslbd:latest # or a specific tag
# Restart with the new image (brief ~2 s gap while container restarts)
docker compose up -d
# Verify
docker compose logs --tail=20 gslbd
curl http://localhost:8080/api/v1/healthRollback:
# Pin to the previous image tag in docker-compose.yml, then:
docker compose up -dOr if you recorded the old digest:
docker compose stop gslbd
# edit docker-compose.yml: image: registry.starstorm.dev/nexus-gslb/gslbd@sha256:<old-digest>
docker compose up -dUpgrade: Kubernetes (zero-downtime)
The Kubernetes Deployment uses RollingUpdate strategy with a readinessProbe on /api/v1/health. New pods must pass readiness before old pods are terminated, so DNS queries are never dropped.
# Update to a new version tag
kubectl set image deployment/gslbd \
gslbd=registry.starstorm.dev/nexus-gslb/gslbd:v1.2.3
# Watch rollout progress
kubectl rollout status deployment/gslbd
# Verify
kubectl get pods -l app=gslbd
dig @<pod-ip> -p 5353 A <your-domain>Rollback:
# Instant rollback to the previous ReplicaSet
kubectl rollout undo deployment/gslbd
# Or to a specific revision
kubectl rollout history deployment/gslbd
kubectl rollout undo deployment/gslbd --to-revision=<N>Schema migrations
Nexus GSLB applies SQLite schema changes automatically on startup using ALTER TABLE ... ADD COLUMN IF NOT EXISTS. Migrations are:
- Additive only — columns are never dropped or renamed in patch/minor releases.
- Idempotent — safe to run multiple times; re-applying a migration is a no-op.
- Backward compatible — the previous binary can read a database migrated by the newer version (new columns have sensible defaults).
This means you can safely upgrade without schema planning, and can roll back the binary without touching the database.
Configuration file
Default path: /etc/gslb/config.yaml. Override with -config flag.
See docs/Configuration.md for all fields and examples. A complete annotated example is at deploy/config.yaml.
Ports
| Port | Protocol | Purpose |
|---|---|---|
| 5353 | UDP + TCP | DNS (default; change to 53 in production) |
| 8080 | TCP | REST API (disabled by default) |
| 9090 | TCP | Prometheus metrics (disabled by default) |
Multi-region topology
- Run 3+ NATS servers per region with JetStream enabled and super-cluster gateways configured.
- Deploy one
gslbdper site. Setcluster.idto the shared cluster name and a uniquenode.idper node. - Set
state.nats.serversto local NATS URLs and configurehealthPolicyas desired. - See
docs/StateSync.mdfor full topology and NATS configuration reference.
GitOps
- Point
gitops.repoURLto a signed configuration repo and setpathPrefixto your cluster directory. - Set
requireSignature: trueand list trusted GPG key fingerprints inallowedSigners. - The config directory (
/etc/gslb) must be group-writable by the service user (chmod 2770, setgid) so the daemon can atomically rewrite its own config; for Forgejo/Gitea sources use anssh://host:222/...URL (the built-in SSH port), not the scp-style form. - See GitOpsUserGuide for setup and verification steps.
DNSSEC
Online signing (Algorithm 13, ECDSA P-256) is supported. Enable by setting dnssec.enabled: true and providing KSK/ZSK PEM files. Export the DS record for your registrar:
gslbctl dnssec ds --zone example.com. --ksk-file /etc/gslb/ksk.pemSee docs/Security.md for key management guidance.
Validation
After deployment:
# DNS resolution
dig @<node> -p 5353 A <your-domain>
# API health
curl http://<node>:8080/api/v1/health
# Metrics (if enabled)
curl http://<node>:9090/metrics | grep gslbd_dns