Deploying Egret Nest Dashboard
The dashboard is a single static Go binary with embedded SQLite. The store is a
single writer - run exactly one instance (no horizontal scaling). Back up the
SQLite file (egret-nest backup <path>) rather than replicating.
1. Docker (quickest)
docker run -d -p 8080:8080 -v egret-nest-data:/data \
-e EGRET_NEST_SECRET_KEY=$(openssl rand -hex 32) \
ghcr.io/nx1x/egret-nest:v0.1.1
Pin a specific released tag (e.g. v0.1.1) for reproducible deploys. The floating
tags (latest, v0, v0.1) are a convenience only - they move under you and are not for
pinned/reproducible deployments. EGRET_NEST_SECRET_KEY is required: without it (and
without EGRET_NEST_ALLOW_PLAINTEXT_TOTP=1) the server refuses to start rather than store
TOTP seeds in plaintext.
The image is published to GitHub Container Registry (ghcr.io/nx1x/egret-nest, signed
with SLSA build provenance) and mirrored on Docker Hub (nx1x/egret-nest). Swap the
image reference to pull from either.
Beta: the Docker Hub mirror is newly wired into the release workflow and has not been fully validated across a release cycle yet. GHCR is the canonical, signed source; prefer it if you need the SLSA attestation.
Open http://localhost:8080 → first visit is /setup (create the admin).
2. docker compose (with optional nginx TLS)
cp .env.example .env # then edit: set secrets + BASE_URL
docker compose up -d # dashboard only, on 127.0.0.1:8080
docker compose --profile tls up -d # + nginx on :443 (TLS termination)
For the tls profile:
- put
fullchain.pem+privkey.pemindeploy/nginx/certs/, - set
server_nameindeploy/nginx/conf.d/egret-nest.conf, - in
.envsetEGRET_NEST_BEHIND_PROXY=1andEGRET_NEST_BASE_URL=https://your.domain(so the app trustsX-Forwarded-Protoand issuesSecure/__Host-cookies).
nginx terminates TLS, rate-limits /ingest + /webhook/github, and caps the body
at 8 MiB (matching the ingest limit). The app owns its security headers (CSP,
X-Frame-Options, HSTS-when-secure); nginx does not duplicate them.
3. Kubernetes (Helm)
helm install egret-nest deploy/helm/egret-nest \
--set config.baseURL=https://egret.example.com \
--set ingress.enabled=true --set ingress.host=egret.example.com \
--set secrets.secretKey=$(openssl rand -hex 32)
The chart runs 1 replica (SQLite) with the Recreate strategy, a hardened
pod (non-root, read-only rootfs, all caps dropped, seccomp RuntimeDefault), a
default-deny NetworkPolicy (networkPolicy.enabled: true), a PVC for /data, and
a Secret for the sensitive env. TLS is handled by your ingress controller (e.g.
cert-manager) - the app sits behind it with config.behindProxy: true, and
ingress.sslRedirect: true keeps the ingress forcing HTTPS and setting
X-Forwarded-Proto=https (which the app requires to issue Secure / __Host-
cookies).
Secrets - recommended: do NOT put secret values in values.yaml (they land in
plaintext in your release manifests / history). Instead point existingSecret at a
Secret you manage out-of-band with the same EGRET_NEST_* keys, sourced from
SOPS, External Secrets Operator, or Vault:
helm install egret-nest deploy/helm/egret-nest \
--set config.baseURL=https://egret.example.com \
--set ingress.enabled=true --set ingress.host=egret.example.com \
--set existingSecret=egret-nest-secrets
Setting the inline secrets.* values is supported for quick trials only.
Configuration reference
All configuration is via env (EGRET_NEST_*) - see the table in
cmd/egret-nest/main.go and AUTH.md.
Highlights:
| Env | Purpose |
|---|---|
EGRET_NEST_DB |
SQLite path (default /data/egret-nest.db in the image) |
EGRET_NEST_SECRET_KEY |
32-byte hex/base64 key encrypting TOTP seeds + UI-stored SSO client secrets at rest. Required - the server refuses to start without it, unless EGRET_NEST_ALLOW_PLAINTEXT_TOTP=1 |
EGRET_NEST_ALLOW_PLAINTEXT_TOTP |
1 = start without SECRET_KEY, storing TOTP seeds unencrypted at rest (not recommended) |
EGRET_NEST_BASE_URL + EGRET_NEST_BEHIND_PROXY=1 |
required when behind a TLS proxy / for SSO redirects |
EGRET_NEST_TLS_CERT + EGRET_NEST_TLS_KEY |
serve HTTPS natively (PEM paths, TLS 1.2+) instead of terminating at a proxy - set both or neither |
EGRET_NEST_WEBHOOK_SECRET |
enables the HMAC-verified POST /webhook/github |
EGRET_NEST_METRICS_TOKEN |
enables token-gated /metrics (≥32 chars) |
EGRET_NEST_RETENTION_DAYS / _AUDIT_RETENTION_DAYS |
pruning windows (0 = keep) |
EGRET_NEST_GITHUB_* / EGRET_NEST_OIDC_* |
SSO providers (see AUTH.md) |
EGRET_NEST_SETUP_TOKEN |
one-time token required at /setup (unset = a random one is generated + logged) |
EGRET_NEST_OPEN_INGEST |
1 = accept POST /ingest with no token - dev only, never in production |
Backup & upgrade
- Backup:
docker exec <container> /egret-nest backup /data/backup.db(or run thebackupsubcommand in the pod). The snapshot is a credential-bearing, unencrypted SQLite file (password/session/token hashes; TOTP seeds - encrypted only ifEGRET_NEST_SECRET_KEYis set). Encrypt it before it leaves the host (age -p,gpg -c) and store it off-account (not the same cloud creds that run the service). Schedule it (cron/CronJob) and test a restore periodically- a restore is just stopping the service and swapping the DB file back in. Target an RPO/RTO you can meet (e.g. daily backup → RPO 24h).
- Upgrade: pull the new image / bump
image.tagand restart. Schema migrations are idempotent and apply on startup; the DB upgrades in place.
Supply-chain
- Base images (
golang,distroless,nginx) are digest-pinned; Renovate maintains the digests (see.github/renovate.json5). - The release workflow builds from the pinned digests, scans with Trivy (fails on
HIGH/CRITICAL), then pushes by digest to
ghcr.io/nx1x/egret-nest(mirrored to Docker Hubnx1x/egret-nest) with SLSA build provenance.