Run with Compose or systemd
Run with Compose
Section titled “Run with Compose”Save this as docker-compose.yml. Provision an auth code secret of at least
32 characters through your secret manager. Set AUTH_SECRET_PATH to its mounted
source file, readable by UID/GID 65532.
services: pitot: image: ghcr.io/labstack/pitot:2026.11.0-alpha container_name: pitot restart: unless-stopped ports: - "127.0.0.1:7520:7520" volumes: - pitot-data:/var/lib/pitot/data environment: PITOT_AUTH_CODE_SECRET_FILE: /run/secrets/auth_code secrets: - auth_code healthcheck: test: ["CMD", "pitot", "healthcheck"] interval: 30s timeout: 3s start_period: 40s retries: 3secrets: auth_code: file: ${AUTH_SECRET_PATH}volumes: pitot-data:Start with docker compose up -d. Read the setup URL with
docker compose logs pitot, then complete first boot.
The image listens on :7520 and keeps data in /var/lib/pitot/data.
The port mapping above exposes it only on the host’s loopback interface.
Use TLS and a restricted network route for remote clients.
The ingest token is issued during administrator setup and stored in control
state as a hash. An automated install can set the first token instead with
PITOT_INGEST_TOKEN or PITOT_INGEST_TOKEN_FILE; see
send telemetry. Give the token to your
exporters through their secret delivery mechanism.
A named volume preserves data across container replacement. Do not remove it when upgrading. The healthcheck marks an unhealthy container; Docker does not restart it solely because that check fails.
Run with systemd
Section titled “Run with systemd”Install the binary as /usr/local/bin/pitot. Create a dedicated system user
and group named pitot, with no interactive login. Store the auth secret
at /etc/pitot/auth_code, readable only by root. systemd passes it to the
service as a credential.
Save this as /etc/systemd/system/pitot.service:
[Unit]Description=PitotAfter=network.target
[Service]Type=simpleUser=pitotGroup=pitotStateDirectory=pitotWorkingDirectory=/var/lib/pitotEnvironment=PITOT_ADDR=127.0.0.1:7520Environment=PITOT_DATA_DIR=/var/lib/pitot/dataLoadCredential=pitot_auth_code:/etc/pitot/auth_codeEnvironment=PITOT_AUTH_CODE_SECRET_FILE=%d/pitot_auth_codeExecStart=/usr/local/bin/pitotRestart=on-failureRestartSec=5s
[Install]WantedBy=multi-user.targetStateDirectory creates /var/lib/pitot for the service user. Pitot creates
its data subdirectory. Load and start the unit:
sudo systemctl daemon-reloadsudo systemctl enable --now pitotsudo journalctl -u pitotUse the setup URL from the journal. Restrict journal access because that URL
is an administrator credential. Check http://127.0.0.1:7520/readyz after startup.
See hardening before exposing the listener.
Listen on port 443
Section titled “Listen on port 443”Some TLS-terminating proxies connect to the origin only on port 443. For
example, Cloudflare’s gRPC proxying
requires the origin to listen on 443 with TLS and HTTP/2 advertised through
ALPN, which Pitot’s direct TLS provides.
On Cloudflare, also enable gRPC in the zone’s network settings (otherwise
Cloudflare answers gRPC requests with 403 Forbidden), set the DNS record to
proxied, and set the SSL/TLS encryption mode to Full (strict). Cloudflare
accepts Full, but only Full (strict) verifies the origin certificate.
Linux reserves ports below 1024 for privileged processes, so grant the
unprivileged pitot user only the capability to bind them rather than running
as root.
Add a drop-in with sudo systemctl edit pitot:
[Service]Environment=PITOT_ADDR=:443LoadCredential=pitot_tls_cert:/etc/pitot/tls/cert.pemLoadCredential=pitot_tls_key:/etc/pitot/tls/key.pemEnvironment=PITOT_TLS_CERT_FILE=%d/pitot_tls_certEnvironment=PITOT_TLS_KEY_FILE=%d/pitot_tls_keyAmbientCapabilities=CAP_NET_BIND_SERVICECapabilityBoundingSet=CAP_NET_BIND_SERVICENoNewPrivileges=trueAmbientCapabilities gives the process CAP_NET_BIND_SERVICE although it runs
as pitot. CapabilityBoundingSet drops every other capability, and
NoNewPrivileges stops the process and its children from gaining more.
The later PITOT_ADDR replaces the unit’s loopback address; :443 listens on
every interface, so restrict the port to the proxy’s networks with a firewall.
For Cloudflare, allow only Cloudflare’s published IP ranges.
The certificate and key are passed as credentials, like the auth secret, so
they can stay readable only by root. systemd copies credentials when the
service starts, and Pitot loads the certificate once, so restart the service
after renewing the certificate.
Restart and confirm the listener runs as pitot with only that capability:
sudo systemctl daemon-reloadsudo systemctl restart pitotsystemctl show pitot -p User -p AmbientCapabilities -p CapabilityBoundingSetsudo ss -ltnp 'sport = :443'grep Cap /proc/"$(systemctl show pitot -p MainPID --value)"/statusss should show the pitot process on port 443, and CapAmb and CapEff
should both be 0000000000000400 (CAP_NET_BIND_SERVICE alone). Check
readiness on the host with a name in the certificate:
curl --resolve pitot.example.com:443:127.0.0.1 https://pitot.example.com/readyzIf the certificate comes from a private CA or is a Cloudflare Origin CA
certificate, which public trust stores reject, pass that CA’s certificate with
--cacert, for example --cacert /etc/pitot/tls/origin-ca.pem.