# Installation (https://portunus.bybee.dev/en/docs/getting-started/installation)



`Portunus` ships in two deployment models. Pick one before you install:

* **Server + client (control plane).** [`portunus-server`](#server-install) runs
  on a control-plane host — operators talk to it, it stores state, and it pushes
  rules to connected clients. [`portunus-client`](#client-install) runs on each
  edge host, connects back to the server, binds forwarded ports, and moves traffic.
* **Standalone (no control plane).** [`portunus-standalone`](#standalone-install)
  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? [#which-one-should-i-install]

|                                                                          |     [**Standalone**](#standalone-install)    | [**Server + client**](#server-install) |
| ------------------------------------------------------------------------ | :------------------------------------------: | :------------------------------------: |
| 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 [#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 [#quick-docker-install]

On a fresh Linux host, use Docker's official convenience script:

```sh
sudo curl -fsSL https://get.docker.com | sh
```

## Server install [#server-install]

The fastest path is the one-line installer:

```sh
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](/en/docs/configuration/client#install-the-binary)
— 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 [#copy-paste-install-on-the-server-host]

1. Create and enter the working directory:

```sh
mkdir -p ~/portunus-server && cd ~/portunus-server
```

2. Write `docker-compose.yml`:

```sh
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
```

3. Start the server and verify it:

```sh
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](/en/docs/features/advertised-endpoint).

### Compose file reference [#compose-file-reference]

```yaml
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](/en/docs/deployment/docker) for operator commands,
client Compose, backup, migration, and production notes.

For a source build:

```sh
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](#release-binaries) below.

Verify on the server host:

```sh
portunus-server --version
```

## Client install [#client-install]

The fastest path is the one-line installer:

```sh
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 [#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](/en/docs/deployment/docker#operator-commands)). Then on the edge host:

```sh
portunus-client enroll 'portunus://...' --out ./client.bundle.json
portunus-client --bundle ./client.bundle.json
```

See [Client Configuration — Enroll](/en/docs/configuration/client#enroll) for
bundle resolution order, `0600` permissions, and default locations.

### Docker [#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:

```sh
# 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:

```yaml
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:

```sh
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](/en/docs/configuration/client#docker) for why both commands run as your host user, and [Deploy with Docker](/en/docs/deployment/docker) for production notes.

For a source build:

```sh
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](#release-binaries) below.

Verify on the client host:

```sh
portunus-client --version
```

## Standalone install [#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 [#docker-1]

```sh
# 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) [#one-click-installer-binary--systemd]

```sh
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:

```sh
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):

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

For a source build:

```sh
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](/en/docs/configuration/standalone).

## Release binaries [#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](https://github.com/ZingerLittleBee/Portunus/releases) 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:

```sh
# 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 [#systemd]

Install the binary and the hardened systemd unit in one step:

```sh
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](/en/docs/deployment/systemd) for the full unit
breakdown and hardening notes.

## Interactive manager [#interactive-manager]

Run the installer with no arguments for a guided menu (works piped too):

```sh
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](/en/docs/cli/installer).

## Next steps [#next-steps]

* **Want the raw CLI flow?** → [CLI Walkthrough](/en/docs/cli/walkthrough)
  takes you from a fresh checkout to "100 MB streamed through a rule".
* **Going to production?** → [Deploy with Docker](/en/docs/deployment/docker)
  or [Deploy with systemd](/en/docs/deployment/systemd).
* **Want the big picture first?** → [Architecture](/en/docs/getting-started/architecture).
