Installation
Northplane is one static binary (northplaned) plus the np CLI and the np-agent host agent.
Pick the variant that matches how you run services; they all produce the same server with the same
data layout, so you can start with one and move to another later.
| Variant | Best for | TLS | You get |
|---|---|---|---|
Release tarball (+ northplaned init, systemd) |
VMs and bare metal, Linux/macOS | your certificate pair, or a reverse proxy with trustProxy |
northplaned, np, np-agent in /usr/local/bin |
| Docker image | container hosts, Kubernetes, quick trials | your certificate pair, a proxy, or NORTHPLANE_TLS_INSECURE=true locally |
northplaned + np in a distroless image (no np-agent) |
| Docker Compose with bundled Caddy | a single box that should just have HTTPS | automatic (Let’s Encrypt or an internal CA) | Northplane + Caddy, optional PostgreSQL |
| Build from source | development, unreleased versions, other platforms | as above | bin/northplaned, bin/np, bin/np-agent, bin/np-gen |
Releases live on GitHub, the container image on
GHCR (ghcr.io/myfoxit/northplane); both are public — no login, no token.
Install the binaries or the image
Section titled “Install the binaries or the image”Release assets
Section titled “Release assets”Releases are tagged v*. Each release carries:
| Asset | Contents |
|---|---|
northplane_<tag>_linux_amd64.tar.gz, northplane_<tag>_linux_arm64.tar.gz |
northplaned, np, np-agent, LICENSE |
northplane_<tag>_darwin_amd64.tar.gz, northplane_<tag>_darwin_arm64.tar.gz |
northplaned, np, np-agent, LICENSE |
northplane_<tag>_windows_amd64.zip |
np.exe, np-agent.exe, LICENSE — no northplaned (it needs Unix process groups for plugin execution) |
checksums.txt |
sha256sum lines for every asset |
The asset name keeps the leading v of the tag (northplane_v1.2.0_linux_amd64.tar.gz) while the
version baked into the binaries strips it (northplaned version → northplaned 1.2.0).
Binaries are static (CGO_ENABLED=0), so they run on any glibc or musl distribution.
install.sh
Section titled “install.sh”The one-line installer resolves the newest release through the GitHub API, downloads the matching
tarball and checksums.txt, verifies the SHA-256 and installs northplaned np np-agent:
curl -fsSL https://raw.githubusercontent.com/myfoxit/northplane/main/install.sh | shWhat it does, exactly:
- Supports Linux and macOS on
x86_64/amd64andaarch64/arm64; needscurl,tarandsha256sumorshasum. - Installs into
/usr/local/bin. If that is not writable it usessudo(the password prompt comes from/dev/tty, so this also works in thecurl … | shform); withoutsudoit falls back to~/.local/bin(created if missing, with a note if it is not onPATH). - Picks the newest release; if only pre-releases exist it takes the newest of those.
- Is safe to re-run: existing binaries are replaced.
- Writes no configuration and no service unit; it ends with the two next steps —
northplaned servefor a loopback trial andsudo northplaned init+systemctl enable --now northplanedfor a service.
| Variable | Effect |
|---|---|
NP_VERSION=v1.2.3 |
install that release instead of the newest (1.2.3 is accepted too) |
NP_INSTALL_DIR=/opt/bin |
install directory (no fallback to ~/.local/bin when set) |
NP_BINARIES="np-agent" |
install a subset — what the Admin → Agents tab shows for monitored hosts |
Manual equivalent (any release, any platform from the matrix):
tag=v1.2.0 os=linux arch=amd64curl -fsSLO "https://github.com/myfoxit/northplane/releases/download/${tag}/northplane_${tag}_${os}_${arch}.tar.gz"curl -fsSLO "https://github.com/myfoxit/northplane/releases/download/${tag}/checksums.txt"grep " northplane_${tag}_${os}_${arch}.tar.gz\$" checksums.txt | sha256sum -c - # macOS: shasum -a 256 -c -tar -xzf "northplane_${tag}_${os}_${arch}.tar.gz"sudo install -m 0755 northplaned np np-agent /usr/local/bin/northplaned versionA first trial needs no configuration: northplaned serve listens on 127.0.0.1:8443 (plain HTTP
is allowed on loopback) and stores data under your user’s data directory. For a permanent install
continue with Set up as a service.
Image facts
Section titled “Image facts”| Item | Value |
|---|---|
| Image | ghcr.io/myfoxit/northplane — tags latest, main-<12-char sha> (pushed for every green CI run of main), and for releases X.Y.Z and X.Y; every tag is multi-arch (linux/amd64 + linux/arm64) |
| Base | gcr.io/distroless/static-debian12:nonroot — no shell, runs as uid/gid 65532 |
| Contents | /usr/local/bin/northplaned, /usr/local/bin/np (no np-agent) with the UI and this documentation embedded |
| Defaults | ENV NORTHPLANE_DATA_DIR=/var/lib/northplane, ENV NORTHPLANE_LISTEN=:8443, VOLUME /var/lib/northplane, EXPOSE 8443, ENTRYPOINT ["/usr/local/bin/northplaned"], CMD ["serve"] |
| Health | probe from outside: GET /healthz → ok, GET /readyz → JSON with subsystems (the image has no shell for a HEALTHCHECK) |
Because the listener is bound to all interfaces, the container refuses to start unless one of
these is set: NORTHPLANE_TLS_CERT_FILE + NORTHPLANE_TLS_KEY_FILE (PEM, readable by uid 65532),
NORTHPLANE_TRUST_PROXY=true (a TLS-terminating proxy in front that sets X-Forwarded-Proto), or
NORTHPLANE_TLS_INSECURE=true (development only). The error is
no TLS configured on a non-loopback listener — set tls.certFile/keyFile, or trustProxy behind a TLS-terminating proxy, or tls.insecure for dev.
Run it
Section titled “Run it”docker run -d --name northplane --restart unless-stopped \ -p 8443:8443 \ -v northplane-data:/var/lib/northplane \ -v /etc/northplane/certs:/certs:ro \ -e NORTHPLANE_TLS_CERT_FILE=/certs/fullchain.pem \ -e NORTHPLANE_TLS_KEY_FILE=/certs/privkey.pem \ -e NORTHPLANE_BASE_URL=https://monitoring.example.net \ ghcr.io/myfoxit/northplane:latest- Any
config.yamlkey can be set through itsNORTHPLANE_*environment variable (see Configuration); a file mounted at/etc/northplane/config.yamlis picked up automatically because that path wins whenever it exists. - A bind-mounted data directory must be writable by uid 65532 (
chown 65532:65532 /srv/northplane). A named volume inherits the ownership from the image. - The secrets-at-rest master key defaults to
/var/lib/northplane/secret.keyinside the volume. The production stacks mount a host-side key read-only and point at it withNORTHPLANE_SECRET_KEY_FILE=/etc/northplane/secret.key; if that path turns out unusable the server logsconfigured secretKeyFile unusable — falling back to the data directory. Back the key up — without it encrypted secrets are unreadable (Secrets). - Publish additional ports only for listeners you enable:
9162/udp(SNMP traps),2023(ESPA),8123(ESPA-X),4573(FastAGI) — see the ports table in the Deployment overview. - Set
NP_DEFAULT_ADMIN_EMAIL/NP_DEFAULT_ADMIN_PASSWORDto choose the break-glass admin, orNP_DEFAULT_ADMIN_DISABLED=1to use/setup; otherwise read the generated password fromdocker logs northplane(Quickstart). - To run
npfrom the image:docker exec northplane /usr/local/bin/np --server https://127.0.0.1:8443 --insecure --token np_… get hosts(http://when the container runs withNORTHPLANE_TLS_INSECURE=true;--insecureonly skips certificate verification for a certificate that does not match127.0.0.1).
Build your own image from a checkout with make docker (tags northplane:<VERSION>, default
1.0.0-dev) or docker build --build-arg VERSION=<v> -t northplane .. The Dockerfile builds the
UI (Node 22), the documentation and the Go binaries in separate stages.
The bundled stack
Section titled “The bundled stack”The repository root ships docker-compose.yml and caddy/Caddyfile: Northplane on the Compose
network only, Caddy publishing 80/443 and terminating TLS.
services: northplane: image: ghcr.io/myfoxit/northplane:latest # build: . # uncomment to build from source instead of the published image restart: unless-stopped environment: NORTHPLANE_LISTEN: ":8443" NORTHPLANE_TRUST_PROXY: "true" # Caddy terminates TLS and sets X-Forwarded-* NORTHPLANE_BASE_URL: "https://${DOMAIN:-localhost}" # NORTHPLANE_STORAGE_DSN: "postgres://np:np@db:5432/northplane?sslmode=disable" volumes: - northplane-data:/var/lib/northplane expose: - "8443" caddy: image: caddy:2-alpine restart: unless-stopped depends_on: [northplane] ports: ["80:80", "443:443"] environment: DOMAIN: "${DOMAIN:-localhost}" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config healthcheck: test: ["CMD", "wget", "-qO-", "http://northplane:8443/healthz"] interval: 30s timeout: 5s retries: 3volumes: northplane-data: caddy-data: caddy-config:# DOMAIN=localhost (default) → Caddy issues an internal self-signed cert.# DOMAIN=monitoring.example.net (public DNS → this host) → automatic Let's Encrypt.{$DOMAIN:localhost} { reverse_proxy northplane:8443}-
Get the two files (clone the repository or copy them) and log in to GHCR — or uncomment
build: .to build the image from the checkout. -
Local trial:
docker compose up -d→https://localhostwith Caddy’s internal CA (accept the browser warning once). Production:DOMAIN=monitoring.example.net docker compose up -dwith an A record pointing at the host and ports 80/443 reachable — Caddy obtains and renews a Let’s Encrypt certificate.NORTHPLANE_BASE_URLfollowsDOMAINautomatically. -
First login: the stack sets
NP_DEFAULT_ADMIN_DISABLED: "1", so/setupis open — create the admin there. For unattended installs replace that line withNP_DEFAULT_ADMIN_EMAIL/NP_DEFAULT_ADMIN_PASSWORDbefore the first start (or drop it to get a seededadmin@localhostwhose generated password appears once indocker compose logs northplane). -
Watch it:
docker compose ps,docker compose logs -f northplane,docker compose logs -f caddy(ACME activity). Caddy’s healthcheck probeshttp://northplane:8443/healthzevery 30 s because the distroless Northplane container cannot probe itself.
Optional PostgreSQL: uncomment the db service (postgres:16, volume pg-data), set
NORTHPLANE_STORAGE_DSN and add db to depends_on — see PostgreSQL.
The deploy/ directory holds the CI-managed production variants of the same idea:
deploy/docker-compose.yml + deploy/Caddyfile (bundled Caddy with DOMAIN, a bare-IP
https://{$SERVER_IP} site with an internal certificate, ACME_EMAIL, a host-side secret.key
bind mount, separate data directories for demo and real mode) and deploy/docker-compose.vm.yml
(no Caddy — an external proxy terminates TLS). They are documented in
Docker Compose deployment and
Proxmox VM.
Set up as a service with northplaned init
Section titled “Set up as a service with northplaned init”northplaned init turns a bare binary into a permanent install. Run it as root on the target
host:
sudo northplaned init # --dir /etc/northplane --data /var/lib/northplane --user northplanesudo systemctl enable --now northplaned| Flag | Default | Meaning |
|---|---|---|
--dir |
/etc/northplane as root; ~/.config/northplane (macOS: ~/Library/Application Support/northplane) otherwise |
configuration directory |
--data |
/var/lib/northplane as root; ~/.local/share/northplane / $XDG_DATA_HOME/northplane (macOS: ~/Library/Application Support/northplane) otherwise |
data directory written into the config |
--user |
northplane |
system account the service runs as (created when missing — root on Linux only) |
It creates both directories (0750) and writes three files; it refuses to overwrite an existing
config.yaml (<path> exists — refusing to overwrite):
| File | Mode | Content |
|---|---|---|
<dir>/config.yaml |
0640 | the commented bootstrap template: listen: "127.0.0.1:8443", dataDir, secretKeyFile, empty storage.dsn (= SQLite), empty tls, commented oidc/ldap/federation blocks, ai.provider: none, backup.target: "". Shown verbatim in Configuration. |
<dir>/secret.key |
0600 | 32 random bytes as 64 hex characters — the AES-256-GCM master key for secrets at rest. Back it up. |
northplaned.service |
0644 | the systemd unit below — written straight to /etc/systemd/system/ when init runs as root on a Linux host with systemd, otherwise next to the config for manual use |
As root on Linux, init additionally creates the locked system user (useradd --system --no-create-home --shell nologin), hands it the configuration directory, config.yaml,
secret.key and the data directory, and installs the unit — so the printed next step is literally
systemctl enable --now northplaned. On other systems (or without useradd) it prints what is left
to do by hand.
[Unit]Description=Northplane monitoring serverDocumentation=https://github.com/myfoxit/northplaneAfter=network-online.targetWants=network-online.target
[Service]ExecStart=/usr/local/bin/northplaned serve -config /etc/northplane/config.yamlRestart=on-failureRestartSec=2User=northplaneGroup=northplaneStateDirectory=northplaneNoNewPrivileges=yesProtectSystem=strictProtectHome=yesPrivateTmp=yesReadWritePaths=/var/lib/northplane
[Install]WantedBy=multi-user.targetExecStart uses the path of the binary that ran init. There is deliberately no WatchdogSec:
northplaned does not speak sd_notify, and a watchdog without keep-alives would restart the
service every interval.
The command ends with:
next steps: 1. review /etc/northplane/config.yaml (listen/TLS, storage backend, OIDC) 2. systemctl enable --now northplaned 3. open http://127.0.0.1:8443/setup in the browser to create the admin account (or headless: northplaned bootstrap-admin -config /etc/northplane/config.yaml)A complete first start on a systemd host:
-
Install the binaries (installer, tarball or source build) and run
sudo northplaned init. -
Review
/etc/northplane/config.yaml. To serve the network setlisten: ":8443"and eithertls.certFile/tls.keyFileortrustProxy: truebehind a TLS-terminating proxy; the loopback default exists so that plaintext is never exposed by accident. SetbaseUrlto the public URL (used in notification links, ack links and OIDC redirects). Keys and defaults: Configuration, TLS options: TLS and proxy. -
Start it and watch the log:
Terminal window sudo systemctl enable --now northplanedjournalctl -u northplaned -fEnvironment variables such as
NP_DEFAULT_ADMIN_DISABLED=1orNP_DEFAULT_ADMIN_PASSWORD=…go into a drop-in (sudo systemctl edit northplaned→[Service]/Environment=…). -
Create the admin: open
/setup(only open while no local user and no API token exist and the default-admin seeding is disabled — see Quickstart), or runsudo -u northplane northplaned bootstrap-admin -config /etc/northplane/config.yamlfor a headless*:*token.
If secret.key is not readable by the service user (for example after moving files by hand), the
server warns configured secretKeyFile unusable — falling back to the data directory and generates
a second key under /var/lib/northplane/secret.key — workable, but then the key in
/etc/northplane is not the one in use. No SIGHUP reload exists: configuration changes need
systemctl restart northplaned. Shutdown is graceful (SIGTERM, 30 s budget for in-flight requests
and workers).
Build from source
Section titled “Build from source”Prerequisites: Go 1.25 and Node.js 22 (npm). The Go build is CGO-free (pure-Go SQLite), so no C toolchain is needed.
git clone https://github.com/myfoxit/northplane.git && cd northplanemake all # = make web (UI) + make docs (this manual) + make build./bin/northplaned version| Target | What it does |
|---|---|
make web |
npm ci + Vite build in web/, copies web/dist to internal/web/dist (embedded via go:embed) |
make docs |
npm ci + Astro/Starlight build in docs/ (fails on broken links), stages docs/dist into internal/docs/dist |
make build |
go build -ldflags "-X main.version=$(VERSION)" → bin/northplaned, bin/np, bin/np-agent, bin/np-gen; VERSION defaults to 1.0.0-dev |
make docker |
builds the container image northplane:$(VERSION) |
make test / make race |
go vet + go test (CI runs the race detector) |
make dev |
hot-reload development loop: Vite on :5173, auto-rebuilt backend on 127.0.0.1:8443, demo data seeded (NP_DEV_DEMO=0 to skip) |
A plain go build ./cmd/northplaned also works, but it embeds whatever internal/web/dist is
committed (which may be stale) and no documentation — /docs/ then answers 501 documentation not embedded in this build — run make docs. Release-style static cross builds:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath \ -ldflags "-s -w -X main.version=1.2.0" -o northplaned ./cmd/northplanedCross-compiling northplaned for Windows is not supported (CI skips it); np and np-agent build
for windows/amd64. The development workflow (worktrees, lint, tests, e2e) is described in
Development setup.
Platform matrix
Section titled “Platform matrix”| Linux amd64 / arm64 | macOS amd64 / arm64 | Windows amd64 | |
|---|---|---|---|
northplaned |
yes — tarball, container image, source | yes — tarball, source | no (not built: plugin execution needs Unix process groups) |
np |
yes | yes | yes (np.exe in the release zip) |
np-agent |
yes — load, memory, disk, processes, network, plugins | yes — same as Linux | yes (np-agent.exe) — memory, disk, CPU, processes, plugins; no load or network collectors; runs as a console process under sc.exe |
install.sh |
yes | yes | no — use the release zip |
| Service manager | systemd unit from northplaned init; agent unit snippet in Admin → Agents |
launchd plist snippet for the agent in Admin → Agents | sc.exe create snippet for the agent |
| Container | linux/amd64, linux/arm64 |
via Docker Desktop (Linux image) | via Docker Desktop (Linux image) |
builtin:icmp uses unprivileged datagram ICMP first and falls back to a raw socket; on Linux
without root or cap_net_raw it reports UNKNOWN - icmp socket: …. Nagios plugins for
exec: checks are looked up under pluginsDir, auto-detected from /usr/lib/nagios/plugins,
/usr/lib64/nagios/plugins, /usr/local/libexec/nagios, /opt/homebrew/libexec, then
<dataDir>/plugins.
PostgreSQL instead of SQLite
Section titled “PostgreSQL instead of SQLite”SQLite (default, storage.dsn: "") is the fully supported and CI-green backend and needs nothing
else. PostgreSQL is selected by a DSN:
storage: dsn: "postgres://np:secret@db:5432/northplane?sslmode=require" eventRetentionMonths: 12or NORTHPLANE_STORAGE_DSN=postgres://…. Facts to know before you choose it:
- Driver
pgx; schema migrations run automatically on every start (also bynorthplaned migrate). Pool: 16 open / 8 idle connections. Events are stored in monthly partitions; thejanitorenforceseventRetentionMonthsnightly. - The NP-TSDB stays on local disk under
<dataDir>/tsdbregardless of the relational backend — the data directory (and itssecret.key) is still required and still needs backups. northplaned backupdoes not dump PostgreSQL; the manifest records the schema version and a note that relational backup is the operator’s job (pg_dump/PITR). The TSDB is still copied.- Known caveat: the audit-log chain verification (
POST /api/v1/audit:verify,np audit verify) fails on PostgreSQL becausejsonbnormalises the stored JSON that the row hash was computed over. The CI job for PostgreSQL is non-blocking for this reason. - Moving an existing SQLite install: stop the server, run
northplaned storage migrate --to "postgres://…" -config /etc/northplane/config.yaml, pointstorage.dsnat the target, start again (offline copy; the TSDB is untouched).
The Compose file has a commented postgres:16 service ready to uncomment. Details, table layout
and sizing: Storage.
Where files live
Section titled “Where files live”| What | Location |
|---|---|
| Config file | -config <path>; default /etc/northplane/config.yaml as root; for other users /etc/northplane/config.yaml if it exists, else ~/.config/northplane/config.yaml (Linux) / ~/Library/Application Support/northplane/config.yaml (macOS). A missing file is fine — defaults and NORTHPLANE_* variables apply. |
Data directory (dataDir) |
/var/lib/northplane as root; $XDG_DATA_HOME/northplane or ~/.local/share/northplane (Linux); ~/Library/Application Support/northplane (macOS); /var/lib/northplane in the container. Holds core.db (+ -wal/-shm), events-YYYYMM.db segments, tsdb/, artifacts/, optionally plugins/ and the fallback secret.key. |
| Secret key | secretKeyFile from the config (northplaned init writes <config dir>/secret.key); fallback <dataDir>/secret.key, generated on first start. |
| Binaries | /usr/local/bin/{northplaned,np,np-agent} (tarball/installer), bin/ (source build), /usr/local/bin/{northplaned,np} in the image. |
| Agent config | /etc/northplane/agent.yaml (root or if it exists), else ~/.config/northplane/agent.yaml; Windows C:\ProgramData\northplane\agent.yaml — see Agent. |
Upgrading
Section titled “Upgrading”Upgrades are in-place: replace the binary (or pull the new image tag) and restart; pending schema
migrations are applied automatically on start and the embedded UI and docs are always the matching
version. Back up secret.key and the data directory (or run northplaned backup) first, and read
Upgrades for rollback notes per variant.
Uninstalling
Section titled “Uninstalling”There is no uninstall script. Remove what the variant created:
# systemd installsudo systemctl disable --now northplanedsudo rm /etc/systemd/system/northplaned.service && sudo systemctl daemon-reloadsudo rm /usr/local/bin/northplaned /usr/local/bin/np /usr/local/bin/np-agentsudo rm -r /etc/northplane /var/lib/northplane # config, secret.key, database, TSDBsudo userdel northplane
# docker rundocker rm -f northplane && docker volume rm northplane-data
# docker compose (also removes the Caddy volumes)docker compose down -vAgents are removed on their hosts the same way (systemctl disable --now np-agent,
/etc/northplane/agent.yaml, the binary); revoke their API tokens under Admin → API tokens.