Run the Pi coding agent on a VPS
Install the Pi coding agent on a VPS with a pinned npm release, then contain it. Pi has no permission system, so your user account and sandbox set the limits.
Can you run the Pi coding agent on a VPS?
Yes. You can run the Pi coding agent on a VPS (virtual private server) if you install one pinned npm release on Node.js 22.19 or newer. Then you start it inside tmux as a user that owns nothing else. That last part matters most. Pi has no built-in permission system. The server, the user account and any sandbox you add are the only limits on what it can do.
This guide pins @earendil-works/pi-coding-agent 1.5.0. That was the latest release on npm on 3 November 2026. The guide uses Ubuntu 26.04 LTS (long-term support), because it is the Ubuntu LTS release whose own archive ships a Node.js new enough for Pi. Pi is a TUI (terminal user interface) program. You run every command below yourself in an SSH (Secure Shell) session and check the result by eye. If the word "harness" is new to you, what an agent harness is and what it does around the model explains the layer Pi lives in.
What does Pi not protect you from?
The Pi README is direct about this: "Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access." It continues: "By default, it runs with the permissions of the user and process that launched it."
The security page in Pi's docs gives the details. Pi "does not ask for approval before every tool call." Extensions, package installers, language servers and other child processes "run with those same permissions". It also says: "Watching the transcript, using project trust, and reviewing changes do not create a security boundary."
So look at the account that starts Pi. The model can read every file that account can read. A tool call can reach every key in its home directory, every cloud credential file, every environment variable and every SSH agent socket that account can see. The model also reads text you did not write, such as a README or an issue page. Text like that can steer it. Prompt injection against coding agents covers that problem in depth. The boundary has to live outside Pi. This guide builds it in four layers:
- a dedicated server with nothing valuable on it except the project
- an unprivileged user with no
sudorights and nodockergroup - no SSH agent forwarding, and no deploy keys left on the box
- a container or an OpenShell sandbox that sees only the project directory
Running a coding agent on a VPS explains why a separate box makes sense and how to size one. Treat that box as a disposable VM you can wipe and rebuild. It is not a server that also hosts your website or your database.
Which Node.js version does Ubuntu ship for Pi?
Pi requires Node.js 22.19 or newer. The engines field in the npm metadata for 1.5.0 says the same thing: >=22.19.0.
On 3 November 2026 the Ubuntu archive held these nodejs packages. Ubuntu 24.04 LTS ships 18.19.1. That is too old, so apt install nodejs on 24.04 gives you a Node that Pi will not accept. Ubuntu 26.04 LTS ships 22.22.1, which meets the minimum. Ubuntu 26.04 LTS was released on 23 April 2026 and is supported until April 2031. Ubuntu 26.10 ships 24.19.0. It is an interim release, though, supported for 9 months, until July 2027. For a server, 26.04 LTS is the better choice.
One date to note: in the Node.js project's release schedule, the Node 22 line reaches end of life on 30 April 2027. When you move to a newer Node line, install Pi again with the same pinned npm command shown below.
The archive route does not work if your VPS runs Ubuntu 24.04. The Node.js snap does. Its page lists a 24/stable channel, and Node 24 is above Pi's minimum. Run snap info node to see the current tracks, then install the one you want.
sudo snap install node --classic --channel=24/stable
node --versionnode --version should print a version that starts with v24.
Create a dedicated user for the agent
Do not run Pi as your admin user, and never run it as root. Create an account that exists only for the agent.
sudo useradd --create-home --shell /bin/bash agent
id agent
sudo -l -U agentid agent should list one group, agent. sudo -l -U agent should report that the user is not allowed to run sudo. If it lists allowed commands, the account is in a group with sudo rights. Remove it from that group before you go on.
Keep this user out of the docker group too. Docker's own post-install guide warns: "The docker group grants root-level privileges to the user." An agent in that group can do anything root can do on the host, and then the separate account protects nothing.
The agent account needs no password and no SSH key of its own. Log in as your normal admin user, then open a shell as the agent account:
sudo -iu agentKeep SSH keys and deploy keys off the box
SSH agent forwarding (ssh -A) lets the server use the keys on your laptop while you are connected. Any process that can reach the forwarded socket can then log in to other servers as you. You do not want that on an agent box. Turn forwarding off for this host on the client side, in ~/.ssh/config on your laptop:
Host agent-vps
HostName 203.0.113.10
User admin
ForwardAgent noThen refuse it on the server as well, so a forgotten -A cannot undo the client setting:
echo 'AllowAgentForwarding no' | sudo tee /etc/ssh/sshd_config.d/10-no-agent-forwarding.conf
sudo sshd -t
sudo systemctl reload ssh
sudo sshd -T | grep -i allowagentforwardingsudo sshd -t prints nothing when the config is valid. The last command should print allowagentforwarding no. Know what this setting does and what it does not do. The sshd_config manual notes that "disabling agent forwarding does not improve security unless users are also denied shell access". That warning is about users who build their own forwarders. The goal here is narrower: your laptop's keys never reach this server.
Check from inside the agent account:
echo "${SSH_AUTH_SOCK:-none}"It should print none. If it prints a path, this shell can see an agent socket, and so can Pi.
Deploy keys cause the same problem in another form. If a key with push rights sits on the VPS, the model can read it and use it. Let Pi commit in the working tree. Review the commits, then push from your own machine. If the agent must push, use a credential limited to one repository with an expiry date, and revoke it when the task ends. Do not copy cloud credentials or a password manager export onto the box either.
Install a pinned Pi release with npm
Run this part as your admin user, not as agent. When root installs Pi, the agent account can run pi but cannot change the installed files.
sudo apt update
sudo apt install -y nodejs npm git ripgrep tmux
node --version
npm view @earendil-works/pi-coding-agent dist-tagsnode --version should print v22.22 or later. On 3 November 2026, npm view showed latest at 1.5.0 and a second tag, legacy-node20, at 0.74.2. Ignore the legacy tag, because Node 20 has reached end of life. If latest has moved past 1.5.0 when you read this, read the release notes first. Then pin the version you chose. Pi's own Docker example also installs git and ripgrep, so they are useful on the host too.
sudo npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.5.0
command -v pi
pi --versioncommand -v pi should print a path. The CLI reference says pi --version "Shows the Pi version, then exits". It should report 1.5.0.
The --ignore-scripts flag comes from the README's own install line. It tells npm not to run package lifecycle scripts during the install. Version 1.5.0 declares no install, preinstall or postinstall script, so today the flag skips nothing. It costs nothing, though, and it stops a future release from running code at install time without you noticing. The README leaves out the version suffix, @1.5.0. Without it, the same command installs whatever latest means on the day you run it. To upgrade later, change the number and run the same line again.
Why not pipe the pi.dev installer into a shell?
The README lists the installer first: curl -fsSL https://pi.dev/install.sh | sh. The README does not warn against it. Still, read the script before you run it, because it does more than install one package. This is what the version served on 3 November 2026 does:
- when Node.js is missing or too old, it can run
sudo sh -c 'apt-get update && apt-get install -y nodejs npm' - it can add a PATH line to the end of your shell configuration file
- it builds a managed install under
~/.pi/agent/installwithnpm ci --ignore-scripts - it passes
--min-release-age=0, which sets npm's minimum release age to zero for that install
None of this is hidden, and the managed install updates itself with pi update. The issue is something else. A pipe into sh runs whatever the server sends at that moment, and you never see it. A box built to contain an agent needs an install step whose result you can name: one package at one version. If you prefer the installer, download it first, read it, then run it:
curl -fsSL https://pi.dev/install.sh -o pi-install.sh
less pi-install.sh
sh pi-install.shWhere does Pi keep its config on Linux?
Pi keeps user-level state in ~/.pi/agent. That is a dotfolder in your home directory, not ~/.config. The environment-variable reference lists PI_CODING_AGENT_DIR to "Override the config directory; default is ~/.pi/agent". You can move sessions on their own with PI_CODING_AGENT_SESSION_DIR.
People have complained about this location. Issue #534 in the earendil-works/pi repository on GitHub is titled "config folder is out of place on Linux". It was opened on 7 January 2026 and closed the same day. The maintainer replied: "I'm afraid we'll have to live with it." The Hacker News post about the issue had 56 points as of 3 November 2026. In the issue thread the maintainer pointed to PI_CONFIG_DIR. Commenters reported that it did not work, and that variable is not in the current environment-variable docs. Use PI_CODING_AGENT_DIR instead. In the docs read on 3 November 2026, the default is still ~/.pi/agent. The docs do not mention XDG (the freedesktop base-directory layout).
If you want the XDG layout, set the variable in the agent user's ~/.bashrc before the first run. If you do, change the ~/.pi/agent paths in the rest of this guide to match.
echo 'export PI_CODING_AGENT_DIR="$HOME/.config/pi/agent"' >> ~/.bashrcWherever it lives, this one directory holds your sessions and auth.json, and auth.json holds credentials. Treat the whole directory as secret, in backups too.
Log in to Pi from a headless SSH session
Start Pi inside tmux, as the agent user, in a project directory:
sudo -iu agent
mkdir -p ~/projects/demo
cd ~/projects/demo
git init
tmux new -s pi
piInside Pi, type /login and pick a provider. Pi guides you through that provider's OAuth (open authorization) flow or its API (application programming interface) key flow. It saves the result in auth.json.
OAuth has one catch on a server. The providers page says: "On a remote or headless machine, an OAuth callback may not reach the local process." Your browser runs on your laptop, but the process that waits for the callback runs on the VPS. The same docs give the fix: "When prompted, paste the final redirect URL or authorization code back into Pi." So if the last page in your browser fails to load or shows a code, copy the full address or the code. Then paste it into Pi. The exact screens differ by provider, and this guide did not test them. Run /login on your own box and follow its prompts. Whether you may use a given subscription through Pi depends on that provider's terms.
On a server, an API key is the simpler route. Choose the API-key option in /login, or set the provider's variable in the agent's environment. ANTHROPIC_API_KEY and OPENROUTER_API_KEY are two examples. Give that key a low spend limit at the provider, because Pi will use it without asking.
Then check the credential file:
ls -l ~/.pi/agent/auth.jsonThe docs say: "auth.json can contain API keys and OAuth tokens. Keep it private and do not commit it." If the listing shows read access for group or others, run chmod 600 ~/.pi/agent/auth.json. Note that /logout removes only the stored copy. It does not revoke the credential at the provider. When you retire the box, revoke the key on the provider's side.
Keep Pi running with tmux and --continue
tmux keeps Pi running while you are disconnected. Detach with Ctrl-b then d, and close your laptop. Later, run tmux attach -t pi to return. Running Claude Code on a VPS with tmux uses the same pattern for another agent.
Pi's tmux page warns that "tmux can report Shift+Enter, Ctrl+Enter, and plain Enter as the same key." When that happens, Shift+Enter can submit your prompt when you wanted a new line. The fix is the tmux extended-keys setting. Check your version with tmux -V. For tmux 3.5 or newer, add both lines below to ~/.tmux.conf. For tmux 3.2 to 3.4, add only the first line:
set -g extended-keys on
set -g extended-keys-format csi-uThe setting applies to the tmux server, so restart the server with tmux kill-server. That ends every session on that server, so do it before you start real work. Afterwards, Shift+Enter in Pi should insert a new line, and Enter should submit. Pi's docs report one problem on tmux 3.6: stray text such as c0c/0c0c/0c0c can appear in the editor at startup. Upgrading tmux fixes it.
Pi saves sessions without being asked. By default they go under ~/.pi/agent/sessions/, grouped by working directory. pi --continue (or -c) opens the most recent session for the current directory. pi --resume (or -r) opens a list of past sessions to pick from. If you want a session that is never saved, start Pi with --no-session. That session cannot be resumed later.
What happens to Pi when the VPS reboots?
Running programs end when the machine restarts, so the tmux server and the Pi inside it are gone. Unless you have set something up to start them again, expect tmux attach -t pi to fail with a message that no server is running. Check this on your own box after the first reboot. Pi's files on disk survive, because sessions, auth.json and trust.json all live under the agent directory. To recover, run these four commands:
sudo -iu agent
tmux new -s pi
cd ~/projects/demo
pi --continuePi should reopen the last conversation for that directory. If it opens an empty session, check that you are in the directory you used before. Sessions are grouped by working directory, so a different directory has a different history.
Do --approve and --no-approve make Pi safer?
One third-party guide lists --approve and --no-approve flags that the README does not mention. The flags do exist. Pi's CLI reference documents --approve (-a), which "Trusts project-local configuration and resources for this process." It also documents --no-approve (-na), which "Ignores trust-gated project-local configuration and resources for this process."
These flags control project trust. They do not approve tool calls. Project trust gates what a repository's .pi folder can load: project settings, MCP (Model Context Protocol) servers, extensions, skills, prompt templates and themes. Project SYSTEM.md and APPEND_SYSTEM.md files are gated too. By default Pi asks you, and /trust saves your answer in trust.json. The two flags give a one-time answer for automated runs.
The flags have two limits. First, the configuration docs say: "Context-file discovery does not require project trust." Pi reads an AGENTS.md in a cloned repository whatever you answer. Second, the security page says: "Project trust does not limit what tool calls can access or affect." --no-approve is a good default for a repository you have not read, but it is not a sandbox. These flags are in the docs on the main branch, so check that your pinned version has them:
pi --help | grep -n approveNo output means your version does not have them.
Vet Pi packages and extensions before you install them
Pi packages are code. The packages page says: "Packages can execute extension code and can include skills that instruct the model to run programs." It also says: "Review third-party package source before installing it." Extensions run inside the Pi process, with the agent user's permissions.
The pi.dev site suggests you "build your own confirmation flow with extensions", and it lists permission gates and path protection as example extensions. These can slow down a bad action. But a gate that runs inside the process it guards is not a boundary, and the security page says as much. Pin packages to exact npm versions or git tags. The docs note that an update does not move a pinned ref. Read a project's package declarations before you trust that project.
If you are choosing which command-line tools to give the agent in the first place, the CLI tools worth installing for a coding agent on a VPS lists them with the same caution in mind.
Contain Pi in Docker with only the project mounted
A container limits what Pi sees to one directory. Pi's containerization page gives a plain Docker pattern. Here it is with the version pinned. Save it as Dockerfile.pi:
FROM node:24-bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.5.0
WORKDIR /workspace
ENTRYPOINT ["pi"]Build the image and run it from the project directory. The docker group is equal to root, so run these commands as your admin user with sudo, not as agent:
sudo docker build -t pi-sandbox -f Dockerfile.pi .
sudo --preserve-env=ANTHROPIC_API_KEY docker run --rm -it \
-e ANTHROPIC_API_KEY \
-v "$PWD:/workspace" \
-v pi-agent-home:/root/.pi/agent \
pi-sandbox-v "$PWD:/workspace" mounts the current directory and nothing else. The named volume pi-agent-home keeps Pi's sessions and settings between runs, and it never touches the host's copy. The docs are explicit here: "Do not mount the host's ~/.pi/agent unless the container should have access to your host Pi configuration and credentials." Inside Pi, run !pwd. It should print /workspace.
Two notes go beyond Pi's docs. First, the example runs Pi as root inside the container. With a default Docker setup, files it creates in your project are owned by root on the host, so fix their owner afterwards with sudo chown -R. Second, every command Pi runs in the container can see the API key you pass with -e. That is acceptable for a key with a low spend limit. If you need more than that, use OpenShell.
Use OpenShell for a policy-controlled sandbox
The README lists OpenShell as one containment option: "run the whole pi process in a policy-controlled sandbox." (It also describes Gondolin, an extension that keeps Pi on the host and sends its tools into a local Linux micro-VM.) OpenShell is NVIDIA's sandbox runtime. On 3 November 2026 the snap's latest/stable channel was at 0.1.6:
sudo snap install openshellEvery sandbox needs an active gateway. Our guide to sandboxing agents with NVIDIA OpenShell on a VPS covers that setup. It pins an earlier OpenShell release than the snap now ships. Follow its concepts, and check each command against the current docs.
NVIDIA publishes a tutorial, Run Pi with OpenRouter. One line from it is the reason to use it: "The sandbox lets Pi reach OpenRouter but nothing else on the network, and Pi never sees your real API key." The tutorial needs an OpenShell install with an active gateway, Docker or Podman, and an OpenRouter API key. It builds a Node 24 image that installs Pi from a PI_VERSION build argument. That argument defaults to latest, so set it to the version you pinned. Then the tutorial creates the sandbox:
openshell sandbox create --name pi --from pi-agent:local --provider openrouter -- piPi's own docs point out one difference from Docker: "A remote gateway does not bind-mount your host working folder." With a remote gateway, clone the repository inside the sandbox, or move files with the sandbox upload and download commands.
FAQ
Does the Pi coding agent ask before it runs a command?
No. Pi's security docs say it "does not ask for approval before every tool call". It runs with the permissions of the account that started it. Run Pi as an unprivileged user on a dedicated server. If it will touch code you did not write, also put it in a Docker container or an OpenShell sandbox.
Which Node.js version do I need for Pi on Ubuntu?
Pi needs Node.js 22.19 or newer. On 3 November 2026 the Ubuntu 26.04 LTS archive shipped Node.js 22.22.1, so sudo apt install nodejs npm works there. Ubuntu 24.04 ships 18.19.1, which is too old. On 24.04, install the Node.js snap with sudo snap install node --classic --channel=24/stable.
How do I use Pi's /login over SSH when my browser is on my laptop?
Run /login inside Pi and pick a provider. Pi's docs say that on a remote or headless machine, the OAuth callback may not reach Pi. When Pi asks, paste the final redirect URL or the authorization code from your browser back into Pi. An API key needs no callback at all.
Where does Pi store its config and sessions on Linux?
By default Pi uses ~/.pi/agent, with sessions under ~/.pi/agent/sessions/, grouped by working directory. Set PI_CODING_AGENT_DIR to move the whole directory, for example to ~/.config/pi/agent. GitHub issue #534 mentions a PI_CONFIG_DIR variable, but it is not in the current docs, so do not rely on it.
How do I get my Pi session back after the VPS reboots?
The reboot ends the tmux session and the running Pi process, but Pi's saved sessions stay on disk. Log in and run sudo -iu agent. Start a new tmux session and change to the same project directory. Then run pi --continue to reopen the most recent session for that directory.