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:
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).
- 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).
- Fork the repository.
- Push a commit or open a PR; the
testjob runs automatically. - To push images, ensure the fork's package visibility settings allow
GITHUB_TOKENwrite 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.
Option B: Docker¶
A production-ready multi-stage Dockerfile and docker-compose.yml are included in the repository root.
Quick start¶
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):
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¶
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=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/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¶
- Backup the database — if
backup.enabled: false, take a manual snapshot: - 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 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:
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
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. - See
docs/GitOps.mdfor 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:
See docs/Security.md for key management guidance.
Validation¶
After deployment: