Installation
Install Portunus as a server + client control plane or as a single-binary standalone forwarder, with the one-line script, Docker, or a source build.
Portunus ships in two deployment models. Pick one before you install:
- Server + client (control plane).
portunus-serverruns on a control-plane host — operators talk to it, it stores state, and it pushes rules to connected clients.portunus-clientruns on each edge host, connects back to the server, binds forwarded ports, and moves traffic. - Standalone (no control plane).
portunus-standaloneis a single binary that reads all of its rules from a local TOML file — no server, no enrollment, no gRPC. It reuses the exact same data-plane code as the client, and ships with a built-in terminal UI for live traffic stats (portunus-standalone stats).
Which one should I install?
| Standalone | Server + client | |
|---|---|---|
| Rules come from | A local TOML file | The server, pushed live |
| Best for | One host / edge box | Many hosts, central control |
| Control-plane server to run, secure, back up | None | Required |
| Live rule updates (no restart) | ✗ | ✓ |
| Browser Web UI, RBAC, audit log | ✗ | ✓ |
| Rate limiting / QoS, per-owner quotas | ✗ | ✓ |
| TLS-SNI routing | ✗ | ✓ |
| Prometheus metrics | ✗ | ✓ |
| Live traffic dashboard | ✓ terminal TUI (portunus-standalone stats) | ✓ Web UI + Prometheus |
| TCP/UDP, port ranges, multi-target failover, PROXY protocol, DNS targets | ✓ | ✓ |
A server+client deployment usually means one server host and one or more client hosts. A standalone deployment is just one host with one config file. For a local trial, one host with two terminals is enough.
Prerequisites
- curl for the one-line installer.
- Docker / Podman for the recommended container path.
- Rust 1.88+ stable only when building from source.
protocis vendored viaprost-build; no system install is required. - Two reachable hosts for a real server+client deployment (one host with two terminals is enough for a local trial).
- Linux or macOS. Linux is the supported production target.
Quick Docker install
On a fresh Linux host, use Docker's official convenience script:
sudo curl -fsSL https://get.docker.com | shServer install
The fastest path is the one-line installer:
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash -s -- serverThis detects your OS/arch, downloads the matching release binary, verifies its
SHA-256 against the published checksums, and installs portunus-server to
/usr/local/bin. See install.sh flags
— the flag table (--version, --bin-dir, --systemd) applies to both roles.
Docker Compose is the recommended path for production.
Copy-paste install on the server host
- Create and enter the working directory:
mkdir -p ~/portunus-server && cd ~/portunus-server- Write
docker-compose.yml:
cat > docker-compose.yml <<'EOF'
services:
server:
image: ghcr.io/zingerlittlebee/portunus-server:latest
container_name: portunus-server
ports:
- "7443:7443"
- "127.0.0.1:7080:7080"
volumes:
- portunus-data:/var/lib/portunus
command:
- --data-dir
- /var/lib/portunus
- serve
- --operator-http-listen
- 0.0.0.0:7080
restart: unless-stopped
volumes:
portunus-data:
name: portunus-data
EOF- Start the server and verify it:
docker compose up -d && docker compose logs -f -tThat is enough for a default server install. Generated TLS material and SQLite
state live in the portunus-data volume.
The Web UI is available on the server host at http://127.0.0.1:7080/. The
client control-plane listener remains on 0.0.0.0:7443.
Deploying behind a proxy / on a cloud host? The enrollment URI must
embed the public host:port an edge client can dial. If you do not
configure it, an enrollment created without a browser request (e.g. the
Docker operator enroll-client) falls back to 127.0.0.1:7443, which a
remote client cannot reach. Set the advertised endpoint via
PORTUNUS_ADVERTISED_ENDPOINT=host:port (or the Web UI Settings →
Client connect address), and ensure the server certificate SAN covers
that host — see Advertised Endpoint.
Compose file reference
services:
server:
image: ghcr.io/zingerlittlebee/portunus-server:latest
container_name: portunus-server
ports:
- "7443:7443"
- "127.0.0.1:7080:7080"
volumes:
- portunus-data:/var/lib/portunus
command:
- --data-dir
- /var/lib/portunus
- serve
- --operator-http-listen
- 0.0.0.0:7080
restart: unless-stopped
volumes:
portunus-data:
name: portunus-dataThe examples use :latest by default; pin a version tag such as :vX.Y.Z
when you need a fully repeatable production deploy.
If you need non-default listeners or other overrides, place an optional
server.toml at <data-dir>/server.toml, for example with
./server.toml:/var/lib/portunus/server.toml:ro.
See Deploy with Docker for operator commands, client Compose, backup, migration, and production notes.
For a source build:
git clone https://github.com/ZingerLittleBee/Portunus.git
cd Portunus
cargo build --release -p portunus-server
sudo install -m 0755 target/release/portunus-server /usr/local/bin/For manual binary archives, see Release binaries below.
Verify on the server host:
portunus-server --versionClient install
The fastest path is the one-line installer:
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash -s -- clientFor production, running the client as a container is recommended — see Docker below.
Enroll and start
Ask an operator for an enrollment command — from the Web UI Clients page; or portunus-server enroll-client <name> on a host-binary server; or, for a Docker-deployed server, docker compose run --rm operator enroll-client <name> (see Operator commands). Then on the edge host:
portunus-client enroll 'portunus://...' --out ./client.bundle.json
portunus-client --bundle ./client.bundle.jsonSee Client Configuration — Enroll for
bundle resolution order, 0600 permissions, and default locations.
Docker
Run both the one-shot enroll and the long-lived container as your host user so
the 0600 bundle is written and read by one identity:
# One-shot: redeem the enrollment command into a host file.
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" \
ghcr.io/zingerlittlebee/portunus-client \
enroll 'portunus://...' --out /work/client.bundle.json
# Long-lived forwarder.
docker run -d --name portunus-client --network host \
--user "$(id -u):$(id -g)" \
-v "$PWD/client.bundle.json:/etc/portunus/client.bundle.json:ro" \
ghcr.io/zingerlittlebee/portunus-clientCompose equivalent (compose.client.yaml), after the bundle exists:
services:
client:
image: ghcr.io/zingerlittlebee/portunus-client:latest
container_name: portunus-client
network_mode: host
user: "${HOST_UID}:${HOST_GID}"
volumes:
- ./client.bundle.json:/etc/portunus/client.bundle.json:ro
restart: unless-stoppedStart it with:
HOST_UID=$(id -u) HOST_GID=$(id -g) docker compose -f compose.client.yaml up -d--network host is recommended for the client because forwarded listen ports
are created later by operator rules. If you avoid host networking, publish each
forwarded port explicitly.
See Client Configuration for why both commands run as your host user, and Deploy with Docker for production notes.
For a source build:
git clone https://github.com/ZingerLittleBee/Portunus.git
cd Portunus
cargo build --release -p portunus-client
sudo install -m 0755 target/release/portunus-client /usr/local/bin/For manual binary archives, see Release binaries below.
Verify on the client host:
portunus-client --versionStandalone install
portunus-standalone reads all of its rules from a TOML file and needs no
server, no enrollment, and no gRPC. Two install paths — Docker for container
hosts, the one-click installer for plain Linux + systemd.
Docker
# 1. Grab a starter config and edit it:
curl -fsSL -o portunus.toml \
https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/crates/portunus-standalone/contrib/portunus.example.toml
chmod 0644 portunus.toml
${EDITOR:-vi} portunus.toml
# 2. Run it (host networking so port-range rules bind directly):
docker run -d --network host \
--name portunus-standalone \
--restart unless-stopped \
-v "$PWD/portunus.toml:/etc/portunus/standalone.toml:ro" \
ghcr.io/zingerlittlebee/portunus-standalone:latestThe container runs as UID 65532 (nonroot), so the host config must be readable
by that UID — chmod 0644 portunus.toml is the simplest fix. Pin a version tag
(:vX.Y.Z) in production instead of :latest.
One-click installer (binary + systemd)
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash -s -- standaloneThis installs the binary, creates the portunus system user, writes a starter
/etc/portunus/standalone.toml, and installs a hardened systemd unit. The
service is not auto-started — the shipped config is a template. Edit it
first, then enable:
sudo ${EDITOR:-vi} /etc/portunus/standalone.toml
sudo systemctl enable --now portunus-standaloneValidate any config without starting the service (ok + exit 0 on success):
portunus-standalone --check --config /etc/portunus/standalone.tomlFor a source build:
git clone https://github.com/ZingerLittleBee/Portunus.git
cd Portunus
cargo build --release -p portunus-standalone
sudo install -m 0755 target/release/portunus-standalone /usr/local/bin/→ Full config schema, rule examples, CLI flags, signals, the live stats TUI, and operational notes live in Standalone Forwarder.
Release binaries
The recommended install is install.sh — it resolves the latest release,
downloads the right archive, and verifies the SHA-256 checksum automatically.
For a manual download, visit the GitHub
releases page and pick
the archive that matches your platform. Asset names follow the pattern
portunus-<version>-<target>.tar.gz for these four targets:
x86_64-unknown-linux-gnuaarch64-unknown-linux-gnux86_64-apple-darwinaarch64-apple-darwin
A portunus-<version>-checksums.txt is published alongside each release.
Extract and install:
# pick the asset for your platform from the releases page, then:
tar -xzf portunus-<version>-<target>.tar.gz
sudo install -m 0755 portunus-<version>-<target>/portunus-server /usr/local/bin/
sudo install -m 0755 portunus-<version>-<target>/portunus-client /usr/local/bin/systemd
Install the binary and the hardened systemd unit in one step:
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | sudo bash -s -- server --systemd
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | sudo bash -s -- client --systemdThis creates the service user and directories and installs the hardened unit
(Linux only). For a source-checkout install, use
cd deploy/systemd && sudo ./install.sh server client instead (the binaries must already be
in /usr/local/bin).
See Deploying with systemd for the full unit breakdown and hardening notes.
Interactive manager
Run the installer with no arguments for a guided menu (works piped too):
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bashIt offers Install / Uninstall / Upgrade / Status / Service / Config /
Env for both binary+systemd and Docker Compose deployments, and the
server install prompts for the advertised endpoint (persisted to a
systemd drop-in or compose .env). Non-interactive flags are unchanged
for CI/automation; --dry-run still performs no network or writes.
Requires bash 4+.
For the full verb/flag surface, deploy-form details, scoped config keys, lifecycle examples, and safety semantics, see the install.sh manager reference.
Next steps
- Want the raw CLI flow? → CLI Walkthrough takes you from a fresh checkout to "100 MB streamed through a rule".
- Going to production? → Deploy with Docker or Deploy with systemd.
- Want the big picture first? → Architecture.