npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rpi-deploy

v0.23.0

Published

Deployment tool for Docker Compose projects on Raspberry Pi. Builds the Rust CLI from source on install.

Readme

rpi

Deploy anything to your Pi.

npm ci license

rpi.iiskelo.com · Releases · Quick Start · Commands · rpi.toml


rpi (npm package rpi-deploy) deploys Docker Compose projects from Git to a Raspberry Pi — or any Linux host with systemd. An agent runs on the Pi; the CLI runs on your machine or in CI and reaches the agent through an SSH tunnel to a Unix socket, so the Pi exposes nothing but SSH. On rpi deploy the agent clones or fetches the repository, builds the Compose stack, starts the containers, runs a health check, and — if configured — publishes the service to the internet through a Cloudflare Tunnel.

░░
▒▒▒▒    r p i
▓▓▓▓▓▓  deploy · myboard
▓▓▓▓
██

✓ fetch (2.1s)
✓ build (48.3s)
✓ start (5.6s)
✓ health (1.2s)
✓ route (0.8s)
✓ gc (0.3s)

▸ deployed ✓ myboard  →  https://myboard.example.com · 2 services (58.4s)

Highlights

  • One-command setupsudo rpi agent setup bootstraps the Pi (user, dirs, systemd unit); rpi setup and rpi init wizards configure the client and the project.
  • No open ports — the CLI tunnels over your existing SSH access; the agent listens on a Unix socket only.
  • Staged pipeline viewfetch → build → start → health → route → gc, each stage collapsing into a timed ✓ build (48.3s) summary.
  • Private repos without friction — a deploy-key preflight verifies repo access before the pipeline and registers a read-only deploy key through your local gh (the token never leaves your machine, the private key never leaves the Pi). Without gh it prints the key and continues by itself once you add it — even picking up a gh auth login you run mid-wait.
  • Encrypted secrets.env plus arbitrary secret files, sent encrypted, stored age-encrypted on the agent, written 0600 into the checkout at deploy time.
  • Cloudflare Tunnel ingress — one command installs cloudflared, creates or adopts the tunnel, and manages DNS entirely through the Cloudflare API. Hand-built tunnels are adopted without a rewrite or downtime.
  • Stable ports & health checks — the agent allocates a stable host port per project, writes a Compose override, and probes HTTP (or TCP) before declaring success.
  • Latest-wins deploy queue — a newer deploy supersedes the one in flight; rpi deploy --cancel aborts.
  • Admin commands — declare [commands] in rpi.toml and run them inside the service container with rpi command.
  • Ops built inrpi logs, rpi stats (-w for a live dashboard TUI: CPU / memory / temperature cards with mini charts, plus a per-service table with status pills and memory bars), rpi status, rpi doctor, rpi agent logs, rpi gc.
  • Version-skew aware — the CLI and agent handshake on connect, gate commands against advertised agent features, and print a banner instead of a confusing error when they're out of sync.
  • Fast installnpm install -g rpi-deploy downloads a checksum-verified prebuilt binary (Windows x64, Linux x64/aarch64) in seconds, falling back to a source build elsewhere. A no-npm install.sh one-liner bootstraps binary-only hosts.
  • Client-triggered updatesrpi upgrade brings the board's agent up to your CLI's version over the existing SSH + sudo path (download → SHA-256 verify → atomic swap → restart), no manual SSH session required.

Quick Start

1. On the Raspberry Pi (Docker and Node.js ≥ 18 are the prerequisites):

curl -fsSL https://get.docker.com | sh
sudo apt-get install -y nodejs npm

sudo npm install -g rpi-deploy   # prebuilt arm64 binary — seconds, not minutes
sudo rpi agent setup             # user, dirs, systemd unit; idempotent
rpi doctor                       # verify the installation

2. On the developer machine:

npm install -g rpi-deploy
rpi setup                        # wizard: SSH profile for your Pi

3. In the project you want to deploy:

rpi init                         # wizard: generates rpi.toml
rpi secrets send                 # only if the project needs secrets
rpi deploy

That's it — rpi ls shows the project, its host port, and its public hostname if one is configured.

Commands

| Command | Description | |---|---| | rpi deploy [--ref <git-ref>] [--no-gh-key] | Deploy the current project (reads ./rpi.toml) | | rpi deploy --cancel | Cancel active deploys of the current project | | rpi ls (alias: rpi ps) | List projects on the agent | | rpi logs <project> [-f] [--tail N] | Stream container logs | | rpi stats [project] [--json] [-w\|--watch] [--interval N] | CPU / memory / disk / temperature metrics; -w opens a full-screen live view with history sparklines | | rpi start\|stop\|restart <project> | Manage containers without a rebuild | | rpi rm <project> [--volumes] | Remove a project | | rpi status [--json] | Agent and host overview | | rpi doctor | Environment self-diagnosis | | rpi gc | Prune Docker images and build cache on the Pi | | rpi command [name] [-- <args>] | Run a [commands] entry in the service container; no name lists them | | rpi secrets send [--apply] | Send the env file and secret files (encrypted at rest) | | rpi secrets ls | List stored env keys and file paths (values are never transmitted) | | rpi setup | Wizard: server profile + SSH key + client config | | rpi init | Wizard: generate rpi.toml in the current project | | rpi agent setup | Bootstrap the agent on the Pi (run with sudo; idempotent) | | rpi agent status / rpi agent logs [-f] [--since 2h] | Agent health and logs (falls back to systemctl/journalctl over SSH) | | rpi agent migrate [--list] [--dry-run] [--run <id>] [--all --yes] | Host-level migrations | | rpi agent uninstall [--purge] | Remove the agent (keeps data unless --purge) |

Every client command accepts --server <profile> to pick a configured server, or --host/--user/--key to connect without a config file.

How It Works

developer machine / CI                      Raspberry Pi
┌──────────────────┐                 ┌────────────────────────────────┐
│    rpi  (CLI)    │    ssh tunnel   │      rpi agent  (systemd)      │
│  reads rpi.toml  ├────────────────▶│  /run/rpi/agent.sock           │
└──────────────────┘                 │  SQLite state · port allocator │
                                     │  git fetch → compose build     │
                                     │  → up -d → health → ingress    │
                                     └────────────────────────────────┘
  • rpi agent run is a daemon on the Pi, managed by systemd. It stores state in SQLite, selects a stable host port from port_min..port_max, writes a Compose override binding 127.0.0.1:<host-port>, runs docker compose build, then docker compose up -d --remove-orphans, and health-checks the result.
  • rpi deploy, rpi ls, rpi secrets …, and the other client commands run on a developer machine or CI runner and open an SSH tunnel to the agent's Unix socket.
  • Deployments queue latest-wins: pushing a newer deploy supersedes the one in progress.
  • Each deployable project carries an rpi.toml at its root. Run rpi deploy from the root of that project — not from this repository.

Installation

Client and agent ship in the same npm package; the role comes from what you run after installing. On install the package downloads a prebuilt binary from the matching GitHub Release (Windows x64, Linux x64, Linux aarch64) and verifies its SHA-256 checksum. On other platforms, or when the download fails, it builds the bundled Rust sources (cargo build --release --locked; rustup is installed automatically if needed). Set RPI_DEPLOY_BUILD_FROM_SOURCE=1 to force the source build. Installing with --ignore-scripts leaves the CLI unusable — as does npm's allow-scripts gate on recent versions, see Troubleshooting.

Update on both roles:

npm install -g rpi-deploy@latest  # with sudo on the Pi, unless npm is nvm-managed
sudo rpi agent setup              # Pi only: swaps the binary and restarts the agent

[!NOTE] If Node.js comes from nvm (or another per-user version manager), sudo npm/sudo rpi won't be found — sudo resets PATH. Install without sudo and forward PATH for the one command that needs root:

npm install -g rpi-deploy
sudo env "PATH=$PATH" rpi agent setup

agent setup self-installs the binary to /usr/local/bin/rpi, which is on root's default PATH, so every later sudo rpi … works without the wrapper.

Upgrading from v0.5? The command was renamed pirpi and the project config pi.tomlrpi.toml (hard cutover). See docs/migration-v0.5-to-v0.6.md.

Installing without npm

curl -fsSL https://raw.githubusercontent.com/khmilevoi/rpi-deploy/master/scripts/install.sh | sh

Downloads and verifies the prebuilt binary and installs it to /usr/local/bin (override with RPI_INSTALL_DIR); RPI_VERSION pins a version (default: latest). It does not run setup — follow with sudo rpi agent setup on a Pi, or rpi setup on a dev machine.

On the Pi (simple, but slow on smaller boards):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

git clone https://github.com/khmilevoi/rpi-deploy.git
cd rpi-deploy
cargo build --release
sudo install -m 755 target/release/rpi /usr/local/bin/rpi

Cross-build on the developer machine:

cargo install cross
cross build --release --target aarch64-unknown-linux-gnu
scp target/aarch64-unknown-linux-gnu/release/rpi [email protected]:/tmp/rpi
# then on the Pi:
sudo install -m 755 /tmp/rpi /usr/local/bin/rpi

CLI on the developer machine:

cargo install --path crates/bin --locked

If rpi is not found afterwards, add ~/.cargo/bin (%USERPROFILE%\.cargo\bin on Windows) to PATH.

The CLI needs passwordless SSH to the Pi. Create a key on the developer machine:

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_pi        # PowerShell: $env:USERPROFILE\.ssh\id_ed25519_pi

Append the public key to ~/.ssh/authorized_keys on the Pi (for the user the CLI will log in as), then test:

ssh -i ~/.ssh/id_ed25519_pi [email protected] true

Optionally add an SSH profile:

Host pi-home
    HostName pi-host.local
    User pi-user
    IdentityFile ~/.ssh/id_ed25519_pi
    IdentitiesOnly yes

Client Configuration

The CLI reads its config from the user config directory:

  • Windows: %APPDATA%\pi\config.toml
  • macOS/Linux: ~/.config/pi/config.toml

rpi setup creates this file for you; the format is:

default = "home"

[servers.home]
host = "pi-host.local"
user = "pi-user"
key = "~/.ssh/id_ed25519_pi"

Select a profile per invocation with --server home or PI_SERVER=home; skip the file entirely with --host/--user/--key (useful in CI). Check the connection with rpi ls — with no projects deployed yet it prints ▸ no projects deployed yet.

Console theme

Output uses the raspberry brand theme: a marker on every message line, the site's green/amber for success/warn, and a pink-to-raspberry triangle banner on rpi deploy, bare rpi, and rpi --version. The banner only appears on an interactive terminal — piped and CI output stays plain. On truecolor terminals (COLORTERM=truecolor) colours render as the exact brand #C51A4A. Set PI_THEME=classic for the pre-brand look; NO_COLOR and non-TTY output disable styling entirely.

Project Configuration: rpi.toml

Add rpi.toml to the root of the project you want to deploy (rpi init generates it):

schema = 1

[project]
name = "example-web"            # Compose project name and the agent's state key

[source]
repo = "[email protected]:you/example-web.git"
branch = "main"                 # default ref for `rpi deploy`

[build]
compose = "docker-compose.yml"  # Compose file inside the repository

[ingress]
service = "web"                 # Compose service that receives traffic
port = 3000                     # port inside the container
hostname = "app.example.com"    # optional: public ingress (Cloudflare Tunnel)
# expose = "lan"                # optional: bind 0.0.0.0 instead of 127.0.0.1

[healthcheck]
path = "/health"                # omit for a plain TCP probe
expect = "200"
timeout = "60s"

[secrets]
env = ".env"                    # optional, default ".env"
files = [                       # optional; recreated at the same paths on the Pi
  "certs/server.pem",
]

[timeouts]                      # optional per-project overrides
fetch = "3m"
build = "45m"
up = "2m"
# command = "30m"               # for `rpi command`, default 10m

For a worker, bot, or internal service that needs no public HTTP ingress, simply omit ingress.hostname.

Field notes:

  • healthcheck.path is probed through the allocated host port; without a path the agent uses a TCP probe.
  • secrets.files are sent encrypted, stored age-encrypted on the agent, and written 0600 into the checkout on every deploy. Paths are relative with forward slashes; .. is rejected.
  • expose = "lan" binds the host port on 0.0.0.0. On a host with a public IPv4 that means the public internet, and Docker bypasses host firewalls (UFW/iptables) for published ports — use it only on trusted networks or behind an external firewall.

[commands] — admin commands (optional)

One-off admin commands runnable inside the service container with rpi command:

[commands]
create-invite = "node scripts/create-invite.js"
migrate = ["npx", "prisma", "migrate", "deploy"]
backup = "sh -c 'pg_dump mydb | gzip > /data/backup.gz'"

[commands.seed]                       # table form: pin a different service
run     = "node dist/scripts/seed.cjs"
service = "server"                    # optional; omitted => ingress service
  • Values are a string (split with shell-word rules — quotes work, but no variables/pipes/redirects; need a shell? spell out sh -c '…') or an explicit argv array. Names must match [a-z0-9][a-z0-9_-]*.
  • Commands are registered on the agent at deploy time and run via docker compose exec -T in the ingress.service container by default. The agent only executes deployed commands — there is no generic remote exec.
  • rpi command (no name) lists the deployed commands; extra args after -- are appended to the declared argv. The remote exit code becomes the rpi exit code. Ctrl+C detaches and best-effort kills the run.
  • The live pane shows only the last 10 lines while streaming; on success only that tail stays on screen. Pass --full to also dump the complete captured output after it finishes. On failure the full output is always dumped.

Docker Compose Requirements

The agent publishes your service by writing an override file:

services:
  web:
    ports:
      - "127.0.0.1:8000:3000"

So the production Compose file should not pin a fixed host port itself — that can conflict with the agent's allocator or another project on the same Pi. Use expose:

services:
  web:
    build:
      context: .
    expose:
      - "3000"

For runtime files (logs, SQLite, uploads), mount directories instead of individual files that may not exist in a fresh clone, and git-ignore them:

services:
  app:
    environment:
      DATABASE_URL: file:///data/app.db
    volumes:
      - ./data:/data

Secrets

If rpi.toml has a [secrets] section, send the bundle from the project root before the first deploy:

rpi secrets send          # save on the agent; applied by the next deploy
rpi secrets send --apply  # save and restart the running stack with the new values
rpi secrets ls            # list stored env keys and file paths (never values)

The CLI reads the local env file and [secrets].files, sends them encrypted, and the agent stores an age-encrypted bundle in /var/lib/rpi/secrets. During rpi deploy the agent writes them into the project workdir before running Docker Compose.

Private Git Repositories

The Pi must be able to read source.repo. For private repos over SSH, rpi deploy runs a deploy-key preflight before starting the pipeline:

  • With GitHub CLI (gh) logged in locally, the CLI registers a read-only deploy key for the repository automatically — the token never leaves your machine, the private key never leaves the Pi.
  • Without gh, it prints the agent's public key with instructions and polls every 5 s (up to 10 min) until you add it — and if you run gh auth login mid-wait, it switches to automatic registration on the next poll.
  • --no-gh-key skips the GitHub API path and always prints the key for manual setup (GitHub: Repository → Settings → Deploy keys → Add deploy key; write access is not needed).

Cloudflare Tunnel

Only needed when the service must be reachable from the internet; outbound-only services can skip this section.

Automatic setup (recommended)

One command bootstraps everything through the Cloudflare API — no cloudflared tunnel login, no cert.pem:

sudo rpi agent setup --with-cloudflared --cf-token-file <path> --domain <zone>

It installs the cloudflared binary for the host's CPU, creates (or adopts) the tunnel, writes the credentials JSON and a validated config.yml, fills the [cloudflare]/[cloudflared] sections of /etc/rpi/agent.toml, and enables the cloudflared systemd --user service. After that every deploy manages tunnel routes and proxied DNS records by itself.

  • Pass the token via --cf-token-file <path>, --cf-token-file - (stdin), or the CLOUDFLARE_API_TOKEN environment variable. The inline --cf-token flag is deprecated (it leaks via ps/shell history).
  • Required token scopes: Zone:DNS:Edit, Zone:Zone:Read, Account:Cloudflare Tunnel:Edit. The account id is derived from the token.
  • --tunnel <name> overrides the tunnel name (default: derived from the hostname). --dry-run previews the plan.
  • The token is stored at /var/lib/rpi/cloudflare/token (root:rpi-secrets, mode 0640).
  • A fresh install is refused when a foreign tunnel is already running on the host.

Adopting an existing tunnel

If /var/lib/rpi/cloudflared/config.yml already exists (a hand-built tunnel), the same command adopts it instead of recreating anything: the existing config.yml is never rewritten and cloudflared is not restarted — hand-written routes and uptime are preserved. The tunnel id is taken from the tunnel: key, credentials are checked at the credentials-file: path, and the agent config is extended so future deploys manage routes and DNS. Zone:Zone:Read + Zone:DNS:Edit scopes suffice when tunnel: holds a UUID.

Manual ingress

Without a [cloudflared] section in /etc/rpi/agent.toml, deploys still succeed — the agent logs the address to route manually (hostname -> http://127.0.0.1:<host-port>; see the port in rpi ls). If a project declares [ingress] hostname while agent ingress is disabled, the deploy summary and rpi doctor warn loudly so a manually-managed route is never silently mismatched.

A typical locally managed cloudflared config:

tunnel: <tunnel-id-or-name>
credentials-file: /var/lib/rpi/cloudflared/<tunnel-id>.json

ingress:
  - hostname: app.example.com
    service: http://127.0.0.1:8000
  - service: http_status:404   # must remain the catch-all

To let the agent edit this config and manage DNS itself, add both sections to /etc/rpi/agent.toml:

[cloudflared]
config = "/var/lib/rpi/cloudflared/config.yml"
tunnel = "home"
restart = ["systemctl", "--user", "restart", "cloudflared"]

[cloudflare]
zone = "example.com"
token_file = "/var/lib/rpi/cloudflare/token"

rpi-agent must be able to read/write config.yml, run the restart command without a password prompt, and read token_file (a token scoped to DNS edit + tunnel read). DNS records (proxied CNAMEs to <tunnel-id>.cfargotunnel.com) go through the Cloudflare API. Both sections are required — with [cloudflared] alone, ingress falls back to manual with a warning.

Agent Management

sudo rpi agent setup is idempotent: it creates the rpi-agent system user, directories, the systemd unit, and /etc/rpi/agent.toml if missing, repairs permissions, and never touches secret.key or state.db. Re-running it is always safe; --dry-run previews.

Updating the agent

Update the rpi binary on a board to a chosen version from your laptop:

rpi upgrade                 # bring the board up to this CLI's version
rpi upgrade --version 0.22.0
rpi upgrade --version latest --yes

rpi upgrade opens ssh -t <user>@<host> sudo rpi agent update --version <X>, so a board whose sudo needs a password will prompt in your own terminal. It reuses your existing SSH profile (--server / PI_SERVER / default), shows current → target, and re-reads /v1/version afterwards to confirm. It needs real SSH access to the board, so it doesn't apply to the local-dev override and errors out if PI_AGENT_URL is set.

On the board, rpi agent update downloads the release archive (rpi-v<version>-<triple>.tar.gz) from GitHub Releases, verifies its SHA256 against the release SHA256SUMS, swaps /usr/local/bin/rpi, re-runs the idempotent rpi agent setup, and restarts rpi-agent. If the board was installed via npm, it refreshes the global rpi-deploy@<version> instead.

For unattended updates, add a narrow sudoers rule (not blanket NOPASSWD):

<login-user> ALL=(root) NOPASSWD: /usr/local/bin/rpi agent update *

Host-level upgrades run through a uniform migration framework:

rpi agent migrate --list        # every migration and whether it's applied
rpi agent migrate --dry-run     # plan only
rpi agent migrate --run <id>    # apply a specific (disruptive) migration
rpi agent migrate --all --yes   # apply everything pending

Applied migrations are recorded in state.db and never re-run. Non-disruptive ones run automatically during rpi agent setup; disruptive ones are only reported there and must be applied explicitly. (Currently registered: pi-to-rpi, renaming a legacy pi-agent install.)

Create the service user and directories:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin rpi-agent || true
sudo usermod -aG docker rpi-agent
sudo usermod -aG rpi-agent "$USER"   # tunnel access to the socket; re-login after

sudo mkdir -p /var/lib/rpi /etc/rpi
sudo chown -R rpi-agent:rpi-agent /var/lib/rpi

Create /etc/rpi/agent.toml:

data_dir = "/var/lib/rpi"
socket = "/run/rpi/agent.sock"
port_min = 8000
port_max = 8999
build_concurrency = 1
history_keep = 50

[timeouts]
fetch = "2m"
build = "30m"
up = "5m"

[gc]
disk_threshold_percent = 85

Create /etc/systemd/system/rpi-agent.service:

[Unit]
Description=pi deploy agent
After=network-online.target docker.service
Wants=network-online.target

[Service]
User=rpi-agent
Group=rpi-agent
ExecStart=/usr/local/bin/rpi agent run --config /etc/rpi/agent.toml
RuntimeDirectory=pi
RuntimeDirectoryMode=0750
Restart=on-failure
Environment=HOME=/var/lib/rpi
Environment=XDG_CONFIG_HOME=/var/lib/rpi/.config
Environment=XDG_CACHE_HOME=/var/lib/rpi/.cache
WorkingDirectory=/var/lib/rpi

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now rpi-agent

rpi-agent is intentionally created without a home directory; the HOME/XDG_* variables keep Docker/BuildKit from writing to a missing /home/rpi-agent.

Development

cargo test --workspace

Full Docker end-to-end test

The production-path e2e test builds the current rpi once, starts an isolated target with real SSH and /run/rpi/agent.sock, and deploys a local Git fixture into a dedicated Docker-in-Docker daemon:

npm run test:e2e

Requirements: Node.js 18+, Docker Desktop using Linux containers (or Docker Engine on Linux), Docker Compose 2.33.1+, and support for privileged Linux containers. The command starts a privileged docker:28-dind service, but it does not mount the host Docker socket or publish test ports to the host.

The scenario covers rpi deploy over SSH, the agent Unix socket, real Compose build/up, HTTP health, a stable second deploy, rpi ls, and rpi rm. It does not cover systemd installation, Cloudflare, secrets, private Git, or ARM.

On failure, inspect target/e2e-artifacts/<run-id>. The launcher records build, outer Compose, agent, nested Docker, scenario, and cleanup diagnostics before it removes the run's containers, networks, and volumes. Set RPI_E2E_KEEP=1 to keep a run's stack around for inspection; the launcher prints the cleanup command.

Manual dev stack

npm run e2e:dev:up starts the same topology plus a long-lived client-dev container (Compose profile dev, fixed project name rpi-e2e-dev):

docker exec -it rpi-e2e-dev-client-dev-1 bash   # rpi CLI + SSH key
docker exec -it rpi-e2e-dev-target-1 bash       # agent + sshd + nested Docker

Inside client-dev, run source /opt/e2e/lib.sh && e2e_client_init once, then use rpi <cmd> --host target --user deploy --key /run/e2e-keys/id_ed25519. npm run e2e:dev:down removes the dev stack and its image.

Run a local TCP agent and point the CLI at it:

cargo run -p pi -- agent run --config dev/agent.toml
export PI_AGENT_URL="http://127.0.0.1:7700"   # PowerShell: $env:PI_AGENT_URL = "http://127.0.0.1:7700"

Troubleshooting

npm warn allow-scripts … / rpi: binary not built

npm blocked the postinstall script that installs the binary. Reinstall with the script allowed:

npm install -g --allow-scripts=rpi-deploy rpi-deploy       # add sudo for a system-wide npm
npm config set allow-scripts=rpi-deploy --location=user    # persist for future updates

npm approve-scripts does not work for a global install (EGLOBAL — no project package.json).

sudo: npm: command not found / sudo: rpi: command not found

Node.js is nvm-managed and sudo resets PATH. Install without sudo, then sudo env "PATH=$PATH" rpi agent setup once — see the note in Installation.

rpi ls does not connect

Check SSH from the developer machine, then the agent on the Pi:

ssh -i ~/.ssh/id_ed25519_pi [email protected] true

systemctl status rpi-agent
journalctl -u rpi-agent -n 100 --no-pager
ls -l /run/rpi/agent.sock
groups "$USER"      # must include rpi-agent; open a new SSH session after adding

On the Pi itself the agent answers directly:

curl --unix-socket /run/rpi/agent.sock http://localhost/v1/version

Clone fails with Permission denied (publickey)

The Pi cannot authenticate to source.repo. Add the deploy key printed by the preflight (or the fetch stage), or configure another SSH key with read access, then rerun rpi deploy.

Docker build fails with /home/rpi-agent errors

The systemd unit must set HOME=/var/lib/rpi and the XDG_* variables (see the manual install section above), then:

sudo systemctl daemon-reload && sudo systemctl restart rpi-agent

Docker permission denied

sudo usermod -aG docker rpi-agent
sudo systemctl restart rpi-agent

Compose does not see secrets

Run rpi secrets send before rpi deploy (or rpi secrets send --apply for a running project).

Health check fails

The app must listen on 0.0.0.0 inside the container; [ingress].port must match the container port; [healthcheck].path must exist and answer with [healthcheck].expect. On the Pi: docker compose -p <project> ps and curl http://127.0.0.1:<host-port>/health.

Host port is in use

A fixed host ports: mapping in the Compose file conflicts with the agent's allocator. Remove it and use expose — see Docker Compose Requirements.

CLI and agent versions differ

The CLI warns when its version differs from the agent's. Update both sides to the same release: npm install -g rpi-deploy@latest on both, plus sudo rpi agent setup on the Pi.

Documentation

License

MIT © khmilevoi