Skip to content

Deployment Guide

This guide describes how to deploy Pleiades 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 ghcr.io, cosign signature, SBOM

Container registry

Images are published to ghcr.io/joe-robinson/pleiades (GitHub Container Registry). No separate registry credentials are required — the workflow uses GITHUB_TOKEN.

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 ghcr.io/joe-robinson/pleiades: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).
  • Signed: keyless Sigstore signature using the GitHub Actions OIDC identity. Verify with:
cosign verify \
  --certificate-identity-regexp "https://github.com/joe-robinson/pleiades/.github/workflows/ci.yml.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/joe-robinson/pleiades:latest

Version metadata

Every binary and image includes build-time version info logged at startup:

{"level":"INFO","msg":"starting pleiades","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.

Adding CI to a fork

No Secrets Required

No secrets are needed — the workflow uses GITHUB_TOKEN (automatically available).

  1. Fork the repository.
  2. Push a commit or open a PR; the test job runs automatically.
  3. To push images, ensure the fork's package visibility settings allow GITHUB_TOKEN write access to packages.

Build from source

go build -trimpath -ldflags="-s -w" -o gslbd   ./cmd/gslbd
go 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 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 -d

The compose stack: - Mounts deploy/config.yaml read-only at /etc/gslb/config.yaml - Persists the SQLite database in a named Docker volume (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-key
export GSLB_LICENSE_SECRET=your-secret
docker 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

# Run as root from the repository root
sudo bash deploy/install.sh

The script: 1. Builds binaries if not already built 2. Installs them to /usr/local/bin/ 3. Creates a dedicated gslbd system user (no shell, no home) 4. Creates /etc/gslb/ and /var/lib/gslbd/ with correct ownership 5. Copies deploy/config.yaml as a starting point 6. Installs and enables the systemd unit

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 -f

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 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/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/gslbd
sudo systemctl restart gslbd

Upgrade & rollback — all methods

Data 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 image
docker pull ghcr.io/joe-robinson/pleiades: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/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: ghcr.io/joe-robinson/pleiades@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 tag
kubectl set image deployment/gslbd \
  gslbd=ghcr.io/joe-robinson/pleiades: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

Pleiades 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.
  • See docs/GitOps.md 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 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