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 |
|
Push to |
|
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 |
|---|---|
| Every push to |
| Every push to |
| When a |
Pull the latest image:
docker pull registry.starstorm.dev/nexus-gslb/gslbd:latest
Image 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 |
|---|---|
| Your Forgejo username |
| A Forgejo PAT with |
2. Push the main branch
git remote add origin https://code.starstorm.dev/Nexus-GSLB/nexus.gitgit push origin main
This 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.0git push origin v0.1.0
This 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/gslbdgo build -trimpath -ldflags="-s -w" -o gslbctl ./cmd/gslbctl
Option 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 deploykubectl 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 -d
The 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.yamlMounts
deploy/config.yamlread-only at/etc/gslb/config.yamlPersists the rqlite database in a named Docker volume (
rqlite-data) and gslbd state ingslbd-dataPasses
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-keyexport GSLB_LICENSE_SECRET=your-secretdocker compose up -d
DNSSEC 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/gslbd
Then 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: 7
Option 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.sh
The script:
Downloads the pre-built release archive from
code.starstorm.devVerifies 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 ownershipCopies
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 startsudo nano /etc/gslb/config.yamlsudo nano /etc/gslb/gslbd.env # GSLB_LICENSE_KEY=... GSLB_LICENSE_SECRET=...sudo systemctl start gslbdjournalctl -u gslbd -f
DEB / RPM packages
Each release also publishes .deb and .rpm packages for amd64 and arm64. Download from the releases page:
# Debian / Ubuntuwget https://code.starstorm.dev/Nexus-GSLB/nexus/releases/download/v0.1.0/nexus-gslb_0.1.0_linux_amd64.debsudo dpkg -i nexus-gslb_0.1.0_linux_amd64.deb# RHEL / Fedora / Rockywget https://code.starstorm.dev/Nexus-GSLB/nexus/releases/download/v0.1.0/nexus-gslb-0.1.0-1.amd64.rpmsudo rpm -i nexus-gslb-0.1.0-1.amd64.rpm
Both package types:
Create the
gslbdsystem userInstall 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-serviceCredentials 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 binarygo build -trimpath -ldflags="-s -w" -o gslbd ./cmd/gslbdsudo install -m 755 gslbd /usr/local/bin/gslbd# 3. Restartsudo systemctl restart gslbdjournalctl -u gslbd -f# 4. Verifydig @127.0.0.1 -p 5353 A <your-domain>curl http://localhost:8080/api/v1/health
Rollback (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/gslbdsudo systemctl restart gslbd
Upgrade & rollback — all methodsData 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 imagedocker 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# Verifydocker compose logs --tail=20 gslbdcurl http://localhost:8080/api/v1/health
Rollback:
# Pin to the previous image tag in docker-compose.yml, then:docker compose up -d
Or 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 -d
Upgrade: 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 tagkubectl set image deployment/gslbd \ gslbd=registry.starstorm.dev/nexus-gslb/gslbd:v1.2.3# Watch rollout progresskubectl rollout status deployment/gslbd# Verifykubectl get pods -l app=gslbddig @<pod-ip> -p 5353 A <your-domain>
Rollback:
# Instant rollback to the previous ReplicaSetkubectl rollout undo deployment/gslbd# Or to a specific revisionkubectl rollout history deployment/gslbdkubectl 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.pem
See docs/Security.md for key management guidance.
Validation
After deployment:
# DNS resolutiondig @<node> -p 5353 A <your-domain># API healthcurl http://<node>:8080/api/v1/health# Metrics (if enabled)curl http://<node>:9090/metrics | grep gslbd_dns