Pulse/docs/INSTALL.md
pulse-triage[bot] d96ab64e01 Keep manual update help from running an unknown helper
Use the ownership-checked existing-installation procedure for LXC and systemd update help instead of unconditional /bin/update commands. Explain chart selection without asserting a latest-pointer cause, and preserve exact signed downloads, private editions and existing state.

Contract-Neutral: Documentation-only update guidance and regression checks; no installer, updater, API or frontend runtime behaviour changes.
Change-source: pulse-maintainer
2026-10-09 08:00:25 +01:00

23 KiB

📦 Installation Guide

Pulse offers flexible installation options from Docker to enterprise-ready Kubernetes charts.

Paid Pulse Pro / Relay / legacy customers: GitHub release assets and the public rcourtman/pulse Docker image are Community builds. They can accept an activation key, but they do not include the private Pulse Pro runtime hooks. Use https://pulserelay.pro/download.html with your activation key to get the private Pulse Pro Docker image or Linux archive. For Docker Compose, use the PULSE_IMAGE-aware image line shown below, or replace a hardcoded rcourtman/pulse image line with the private image shown on the download page.

Windows code-signing status

Pulse was accepted into the SignPath Foundation open-source programme on 2026-08-06, and the public repository is connected to its SignPath project. The production release certificate is still awaiting issuance (CSR PENDING), so Windows community release artifacts remain unsigned until the certificate is active and the non-publishing production proof run has passed. Release notes identify Windows artifacts that are not Authenticode-signed; published checksums and detached Pulse signatures remain mandatory. Test-signed artifacts use an untrusted certificate and are never published as production releases.

See the Code Signing Policy for build provenance, approval roles, signing scope, and reporting requirements. Release downloads are published on the GitHub Releases page.

Verify release build provenance

New release packets include release-build-provenance.sigstore.json, the Sigstore bundle emitted by the hosted workflow that assembled and validated the candidate. Verify a downloaded asset against that exact workflow and the release source commit with GitHub CLI 2.97.0 or newer:

export PULSE_VERSION=vX.Y.Z
export PULSE_ASSET=pulse-vX.Y.Z-linux-amd64.tar.gz
gh release download "${PULSE_VERSION}" --repo rcourtman/Pulse \
  --pattern "${PULSE_ASSET}" \
  --pattern release-build-provenance.sigstore.json
SOURCE_SHA="$(gh api "repos/rcourtman/Pulse/releases/tags/${PULSE_VERSION}" \
  --jq .target_commitish)"
printf '%s\n' "${SOURCE_SHA}" > release-source-sha.txt
gh attestation verify "${PULSE_ASSET}" \
  --repo rcourtman/Pulse \
  --bundle release-build-provenance.sigstore.json \
  --signer-workflow github.com/rcourtman/Pulse/.github/workflows/build-release-candidate.yml \
  --source-digest "${SOURCE_SHA}" \
  --deny-self-hosted-runners \
  --predicate-type https://slsa.dev/provenance/v1

For an offline target, also run gh attestation trusted-root > trusted_root.jsonl on the connected trusted machine and transfer that file with the asset, bundle, and release-source-sha.txt. On the offline target, restore SOURCE_SHA="$(cat release-source-sha.txt)" and add --custom-trusted-root trusted_root.jsonl to the verification command. Refresh the trusted root whenever importing newly signed material; an old copy cannot report later key revocation or rotation.

Proxmox VE (LXC installer)

If you run Proxmox VE, the easiest and most “Pulse-native” deployment is the official installer which creates and configures a lightweight LXC container.

Replace vX.Y.Z with the exact release tag you want, then run this on your Proxmox host:

(
set -e
export PULSE_VERSION=vX.Y.Z
pulse_installer_dir="$(mktemp -d)"
trap 'rm -rf "$pulse_installer_dir"' EXIT
cd "$pulse_installer_dir"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
ssh-keygen -Y verify \
  -f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
  -I pulse-installer \
  -n pulse-install \
  -s install.sh.sshsig < install.sh
bash install.sh --version "${PULSE_VERSION}"
)

The signed server installer uses the vX.Y.Z Pulse release, which contains the Linux server archive; it does not ask for a GitHub personal access token. A helm-chart-* release contains a Kubernetes chart, not that archive. If a different helper selects a Helm-chart release or asks for a GitHub token, stop instead of supplying one or blindly retrying. On the Proxmox host, check pct list for a partly created Pulse container first; do not run a fresh installer over an existing container without checking its state.

Note

: The GitHub install.sh is the server installer. The agent installer is served from your Pulse server at /install.sh (see Settings → Infrastructure → Install on a host). Do not use the GitHub server installer to install or update pulse-agent.

Docker

Ideal for containerized environments or testing.

docker run -d \
  --name pulse \
  -p 7655:7655 \
  -v pulse_data:/data \
  -e PULSE_DEPLOYMENT_METHOD=docker_run \
  --restart unless-stopped \
  rcourtman/pulse:vX.Y.Z

For a new container, continue with bootstrap-token setup in your browser; do not add an example password to the command.

Docker Compose

Create a docker-compose.yml file:

services:
  pulse:
    image: ${PULSE_IMAGE:-rcourtman/pulse:vX.Y.Z}
    container_name: pulse
    restart: unless-stopped
    ports:
      - "7655:7655"
    volumes:
      - pulse_data:/data
    environment:
      - PULSE_DEPLOYMENT_METHOD=docker_compose

volumes:
  pulse_data:

The PULSE_IMAGE variable lets paid Docker users switch the same compose file to the private Pulse Pro image shown on https://pulserelay.pro/download.html without rebuilding the file around a second deployment path.

Leave authentication overrides unset for a new install and complete bootstrap-token setup in your browser. Do not deploy a shared example password. If automation must skip setup, use a private deployment-managed credential source. The authentication guide explains the visibility and override limits; its docker run --env-file example is not a Compose interpolation recipe.

For an existing installation, preserve its image, data mounts and managed configuration; do not reset authentication to repeat first-time setup. If you used a shared example password, replace it in your deployment's credential source. A deployment-supplied password takes precedence over changes made in Pulse's password-change UI. Hashing it inside Pulse does not remove the original value from Docker's environment or your deployment file. Never share full docker inspect or resolved Compose output.

Note

: Docker monitoring requires the unified agent on the Docker host with socket access; the Pulse server container does not need /var/run/docker.sock. See UNIFIED_AGENT.md.


🛠️ Installation Methods

1. Kubernetes (Helm)

Deploy to your cluster using our Helm chart.

helm repo add pulse https://rcourtman.github.io/Pulse
helm repo update
helm upgrade --install pulse pulse/pulse \
  --namespace pulse \
  --create-namespace

See KUBERNETES.md for ingress and persistence configuration.

2. Bare Metal / Systemd

For Linux servers (VM or bare metal), use the official installer:

(
set -e
export PULSE_VERSION=vX.Y.Z
pulse_installer_dir="$(mktemp -d)"
trap 'rm -rf "$pulse_installer_dir"' EXIT
cd "$pulse_installer_dir"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
ssh-keygen -Y verify \
  -f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
  -I pulse-installer \
  -n pulse-install \
  -s install.sh.sshsig < install.sh
sudo bash install.sh --version "${PULSE_VERSION}"
)

Note

: This installs the Pulse server. Use the /install.sh endpoint from Settings → Infrastructure → Install on a host for installing or upgrading pulse-agent on monitored hosts.

Manual or custom systemd services (advanced)

For a new Pulse server, use the signed installer above rather than copying one binary and writing a minimal service unit. The installer creates the pulse service account, prepares the release tree and data ownership, and installs the server service and update assets together. Its server unit uses User=pulse, Group=pulse, NoNewPrivileges=true, PrivateTmp=true, ProtectSystem=strict and ProtectHome=true, with writes limited to the installation and data directories. Pulse's server does not need a root service; the separate host agent has different privilege requirements (see Agent Security).

For an existing manual or custom installation, first inspect its effective service settings locally. This read-only command does not print environment values or credentials:

systemctl show pulse.service \
  --property=LoadState \
  --property=User \
  --property=Group \
  --property=NoNewPrivileges \
  --property=PrivateTmp \
  --property=ProtectSystem \
  --property=ProtectHome

Use your actual server unit name if it differs; older installations may use pulse-backend.service. Interpret these settings only when LoadState=loaded; LoadState=not-found means the selected unit is missing, not that a root service is running. For a loaded service, an empty User= means systemd runs it as root. Missing hardening is not repaired just by downloading a new binary: the installer preserves existing service units during updates, including custom units.

Do not overwrite a working unit, change only its service user, or reinstall over an existing data directory to match these settings. Before a migration, follow the recovery guide, preserve the data directory and its encryption key, and record the active data path and deployment-managed configuration privately. Check that the intended service account can access that state and that required integrations work with the chosen sandbox. Do not loosen data permissions, remove authentication or share a full unit/environment dump as a shortcut.


🔐 First-Time Setup

Pulse is secure by default. On first launch, you must retrieve a Bootstrap Token to create your admin account.

Step 1: Get the Token

Platform Command
Docker docker exec pulse /app/pulse bootstrap-token
Docker app UIs (Unraid, Portainer, TrueNAS apps) Open the Pulse container's console and run /app/pulse bootstrap-token
Kubernetes kubectl exec -it <pod> -- /app/pulse bootstrap-token
Systemd sudo pulse bootstrap-token
Proxmox LXC pct exec <ctid> -- /usr/local/bin/pulse bootstrap-token (run on the Proxmox host; the installer prints this command with your container ID at the end of the install)

The Proxmox path must be absolute. pct exec runs with PATH=/sbin:/bin:/usr/sbin:/usr/bin, which does not include /usr/local/bin, so a bare pulse fails with No such file or directory.

Important: Paste the token string printed by the command above. Do not paste the raw .bootstrap_token file contents directly. In v6 that file may contain an encrypted JSON snapshot rather than the usable setup token.

Step 2: Create Admin Account

  1. Open http://<your-ip>:7655
  2. Paste the Bootstrap Token.
  3. Complete the Quick Security Setup wizard.
    • Set your Admin Username and Password (or let Pulse generate one).
    • Pulse generates an API token for agents and automations.
    • Copy the credentials before leaving the page.
  4. Open Settings → Infrastructure → Install on a host and install the unified agent only on hosts where you need agent-provided telemetry. For Proxmox, start with API-only monitoring when inventory, node status, VM/container status, and storage metrics are enough; use agents for inside-guest Docker/Podman visibility, host SMART/temperature data, local ZFS/Ceph/mdadm detail, or other telemetry that requires local host access. See Agent Security.

API-only does not mean guest-agent-free. VM filesystem and memory requests through QEMU Guest Agent can share the channel used by freeze-enabled backups. Read-only permissions do not prove backup safety. Do not add permissions, enable or restart an agent, or send manual guest-agent probes during a backup, freeze or thaw. Use the existing backup safety precaution. Stopping Pulse also stops its monitoring and alerts. An OK backup task does not prove successful thaw.

Note

: If you configure authentication via environment variables (PULSE_AUTH_USER/PULSE_AUTH_PASS), the bootstrap token is automatically removed and this step is skipped.


🔄 Updates

The Pulse server and installed Pulse Agents have independent update paths.

Pulse server updates

Automatic Updates (Systemd/LXC only)

Pulse can update the server runtime to the latest stable version.

Enable via UI: Settings → System → Updates

Manual Update

An update briefly interrupts monitoring and alert delivery. Before changing the server, record its running version and edition, save the existing deployment definition privately, and keep a consistent full-state backup of every effective data path with its matching keys. A configuration export or an updater snapshot alone is not a complete data backup. See update preparation and recovery limits.

Platform Procedure
Docker / Compose Follow Docker server updates from the original deployment, selecting the exact image and updating only the Pulse service.
Kubernetes Follow Helm update precautions for the existing release, namespace, saved values and PVC; retain the chosen chart version and image edition.
Systemd / Proxmox LXC with the Pulse-owned helper sudo /bin/update

Use /bin/update --version vX.Y.Z for an exact target only when the helper was installed by the Pulse server installer. On Proxmox community-scripts containers, /bin/update can belong to a different updater that ignores --version. If the helper is absent or its owner is unknown, stop before executing it. For a public Community server, review the signed server-installer flow with PULSE_VERSION set to the exact target tag, first confirming the existing service and data/config paths; do not apply its default paths over a custom deployment or replace a private Pro runtime with a public build. Work inside the existing Pulse LXC, not on the Proxmox host. The same ownership check applies to rollback. After the service restarts, verify the installed version with GET /api/version.

If a helper selects helm-chart-* instead of a Pulse server release, follow Helm-chart selection checks before another attempt. A chart is not a server archive, and adding --version to an unknown or download-failing helper does not prove an exact update. Do not recreate the LXC, provide a GitHub token or bypass signatures to clear that failure.

For Docker without Compose, docker restart keeps the old image running. Select the target in the existing saved deployment and pull it successfully before stopping the current container. Recreate through that deployment with the same data mount, ports, credentials and other settings, not a fresh example command. If data is stored only in the container's writable layer, or an anonymous volume could be removed by --rm, stop here until it has a consistent backup and a checked persistent-data recovery path. Do not delete or prune volumes, or start a second Pulse against the same writable data.

After rollout, check the running image, Pulse server version, service health and ordinary monitoring and alert delivery. If the update fails or its result is uncertain, check the current state before retrying; follow Rollback without assuming that reverting an image restores data.

The public image and commands above install the Community runtime. If the instance uses the private Pro runtime, keep it on the private image or archive shown by https://pulserelay.pro/download.html; replacing it with a public GitHub asset or rcourtman/pulse image removes the private runtime hooks.

Pulse Agent updates

Eligible v6 agents check the Pulse server for updates and apply them asynchronously. A current server version therefore does not prove every agent is current. v5 agents, PVE host agents, agents with auto-update disabled, and agents whose authentication, connection state, download, trust, or self-test checks fail require manual handling.

Open an outdated-agent notice, or use Agent Doctor at /settings/infrastructure?agentDoctor=1, to review the agents Pulse currently sees and copy the platform-specific command for each host. This surface provides commands for the operator to run on the host; it does not remotely execute the update. Use Settings → Infrastructure → Install on a host for a first install or a v5-to-v6 in-place upgrade.

Rollback

An update error does not establish which version is running. Check the running version and service health before retrying or rolling back, and preserve the failed installation and update logs.

In-app update snapshots and updater-script backups have different contents and lifetimes. Neither is guaranteed to include all active data; an older binary also may not understand data migrated by a newer version. Update History is not a full-state recovery tool, and a recorded backup path is not proof of a complete backup.

Check the version-specific snapshot scope and use the stopped-service recovery procedure when data needs restoring. Keep matching data and keys together, verify the backup privately and preserve reversible copies of the failed state. Do not replace live runtime data or delete it to make a rollback fit.


🗑️ Uninstall

Removing or stopping the Pulse server also stops monitoring and alert delivery. This is not an update, rollback or password-reset procedure. Keep persistent data by default; deleting it erases configuration, history, credentials and the keys needed to decrypt that installation's data.

Before removing anything, identify the actual service, container or Helm release and every effective data path privately. Keep a consistent, private full-state backup, including the matching encryption key and deployment configuration. A configuration export alone is not a full backup. Let any in-progress Pulse update finish before removing its installation. Do not post backups, environment files or full container inspections in an issue.

Docker and Compose: retain the data mount

The commands below assume /data is on a persistent named volume or bind mount, as in this guide's examples. Do not use them for state stored only in the container's writable layer or temporary storage until it has a consistent backup. A container created with --rm can also delete anonymous volumes when stopped. Container removal is not a backup.

For the docker run example, stop the container normally before removing it:

docker stop pulse
docker rm pulse

For Compose, run this from the existing project, using its actual Pulse service name:

docker compose stop pulse
docker compose rm pulse

These commands do not request volume deletion. Keep the named volume or bind directory, original image/runtime and deployment settings; a reinstall must reattach the same data mount. Compose normally prefixes volume names with its project name, so creating a new project or an empty pulse_data volume can look like data loss. Do not add -v/--volumes, remove the volume or run volume pruning as part of a data-preserving removal.

Kubernetes: check claim ownership before uninstalling

Do not assume helm uninstall pulse -n pulse retains data. The default Pulse chart creates a PersistentVolumeClaim without a keep policy; Helm removal can delete that claim, and the storage reclaim policy can delete its backing data. persistence.existingClaim refers to a separately managed claim, whose lifecycle must be checked separately. With persistence.enabled=false, the chart uses temporary emptyDir storage, lost when the pod is removed.

After verifying persistent storage and its backup, you can stop the default deployment without uninstalling the chart:

kubectl scale deployment pulse \
  --namespace pulse \
  --replicas=0

Use the actual deployment and namespace. Record the previous replica count and suspend any controller that would recreate pods. Before permanently uninstalling, verify the live claim's ownership, retention and reclaim policy and test recovery from the private backup; do not delete a PVC or namespace as a troubleshooting step.

Systemd / Proxmox LXC: disable without erasing data

Identify the active service and any updater first; legacy installs can use pulse-backend, and custom installs can have other names. In an LXC, run these steps inside the Pulse container, not on the Proxmox host.

For the default signed installation, disable its update timer if present:

sudo systemctl disable --now \
  pulse-update.timer

Then stop and disable the server:

sudo systemctl disable --now \
  pulse.service

Disable other deployment-managed updaters through their owner too. These commands leave the binary, service account, units and data in place; they disable Pulse, not fully uninstall it. Keep /etc/pulse (or the actual custom data directory), its keys and authentication sources together. Do not remove the service account while retained files still need its ownership.

Complete removal is a separate, destructive choice. The signed server installer's --uninstall deletes its configuration/data directory without a keep-data prompt; it is not a data-preserving alternative. Use it only after checking the effective install/config paths and a tested private backup, when you intend that erasure. Do not delete /bin/update unless you have verified it belongs to Pulse; community-scripts containers can use a different helper.

Removing the server does not remove agents on monitored hosts. Use each agent's uninstall procedure, or stop an orphaned agent, so it does not keep retrying the absent server.