Portunus

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-server runs on a control-plane host — operators talk to it, it stores state, and it pushes rules to connected clients. portunus-client runs on each edge host, connects back to the server, binds forwarded ports, and moves traffic.
  • Standalone (no control plane). portunus-standalone is 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?

StandaloneServer + client
Rules come fromA local TOML fileThe server, pushed live
Best forOne host / edge boxMany hosts, central control
Control-plane server to run, secure, back upNoneRequired
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. protoc is vendored via prost-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 | sh

Server install

The fastest path is the one-line installer:

curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash -s -- server

This 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

  1. Create and enter the working directory:
mkdir -p ~/portunus-server && cd ~/portunus-server
  1. 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
  1. Start the server and verify it:
docker compose up -d && docker compose logs -f -t

That 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-data

The 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 --version

Client install

The fastest path is the one-line installer:

curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash -s -- client

For 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.json

See 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-client

Compose 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-stopped

Start 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 --version

Standalone 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:latest

The 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 -- standalone

This 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-standalone

Validate any config without starting the service (ok + exit 0 on success):

portunus-standalone --check --config /etc/portunus/standalone.toml

For 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-gnu
  • aarch64-unknown-linux-gnu
  • x86_64-apple-darwin
  • aarch64-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 --systemd

This 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 | bash

It 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

On this page