Core: 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:latest

Image properties

  • Multi-arch: linux/amd64 and linux/arm64 in 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.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

  1. Fork the repository.

  2. Push a commit or open a PR; the test job runs automatically (no secrets needed).

  3. To push images, add REGISTRY_USER and REGISTRY_TOKEN as 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, upstream rqlite/rqlite image, started with -fk) alongside gslbd; gslbd waits for rqlite's /readyz healthcheck before starting and connects to it via rqlite.httpAddr: rqlited:4001 in deploy/config.yaml

  • Mounts deploy/config.yaml read-only at /etc/gslb/config.yaml

  • Persists the rqlite database in a named Docker volume (rqlite-data) and gslbd state in gslbd-data

  • Passes GSLB_LICENSE_KEY / GSLB_LICENSE_SECRET from 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:

  1. Downloads the pre-built release archive from code.starstorm.dev

  2. Verifies the SHA256 checksum

  3. Installs gslbd and gslbctl to /usr/local/bin/ (previous binaries saved as .bak)

  4. Creates a dedicated gslbd system user (no shell, no home)

  5. Creates /etc/gslb/ and /var/lib/gslbd/ with correct ownership

  6. Copies config.yaml.example as a starting point (skipped if config already exists)

  7. 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 gslbd system 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=yes

  • ProtectSystem=strict — filesystem read-only except StateDirectory and ConfigurationDirectory

  • PrivateTmp=yes, PrivateDevices=yes

  • RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

  • SystemCallFilter=@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 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

  1. 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';"
  2. Record the current image/binary tag — needed for rollback.

  3. 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 gslbd per site. Set cluster.id to the shared cluster name and a unique node.id per node.

  • Set state.nats.servers to local NATS URLs and configure healthPolicy as desired.

  • See docs/StateSync.md for full topology and NATS configuration reference.

GitOps

  • Point gitops.repoURL to a signed configuration repo and set pathPrefix to your cluster directory.

  • Set requireSignature: true and list trusted GPG key fingerprints in allowedSigners.

  • 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 an ssh://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


Was this article helpful?