Run multiple NetBird networks on one machine, at the same time — each in its own isolated config dir, daemon socket, and WireGuard interface.
Useful when a single host needs to be a peer on two separate NetBird networks at once (e.g. a personal net and a work net) so you can SSH and reach web GUIs on both — something a single NetBird daemon can't do, since it runs one profile at a time.
Wingman is a continuation of the abandoned
twinbirdproject, with the multi-instance state-isolation bug fixed, rootless systemd persistence, and apeerscommand.
NetBird must be installed and on PATH first (it's a hard dependency, from the AUR).
paru -S wingman-git # or yay -S wingman-gitwingman-git builds from the tip of master and installs /usr/bin/wingman (depends: python, python-typer, netbird). The AUR package is kept in sync with this repo automatically on every push (see .github/workflows/aur.yml).
To build straight from a checkout instead:
git clone https://github.com/dsandall/wingman.git
cd wingman/packaging/arch && makepkg -siInstall NetBird from its official APT repository, then install the wingman
Debian package from a Wingman release that includes Debian assets.
# 1. NetBird (hard dependency)
sudo apt update
sudo apt install -y curl ca-certificates gnupg
curl -fsSL https://pkgs.netbird.io/debian/public.key \
| sudo gpg --dearmor -o /usr/share/keyrings/netbird-archive-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' \
| sudo tee /etc/apt/sources.list.d/netbird.list
sudo apt update
sudo apt install -y netbird
# 2. Download wingman_<version>_all.deb from GitHub Releases, then:
sudo apt install ./wingman_<version>_all.debThe package installs the rootless prerequisites: the NetBird file capabilities,
the Debian/Ubuntu sudo-group polkit rule for per-interface DNS, and a dpkg
path trigger that reapplies the capabilities whenever a NetBird upgrade replaces
/usr/bin/netbird. No manual setcap rerun is required.
To keep instances running across reboot without an active login session:
sudo loginctl enable-linger "$USER"For development snapshots without a .deb, install with pipx; then follow the
package's Debian packaging notes to provision the
same privileges.
wingman runs rootless — daemons run as your user, no sudo for everyday commands. The Arch and Debian packages install the required privileges and keep them across NetBird upgrades. For a manual install, do it once yourself:
sudo setcap cap_net_admin,cap_net_raw,cap_net_bind_service+eip $(command -v netbird)wingman up preflights this and aborts with the exact command if it's missing, so you won't be left guessing.
Peer name resolution (<peer>.<account DNS domain>, netbird.cloud by default) has one host-level prerequisite the package can't set up: a rootless daemon installs DNS through systemd-resolved, so it must be running and own /etc/resolv.conf. On a NetworkManager-only host the tunnel comes up but names never resolve. Fix once:
sudo systemctl enable --now systemd-resolved
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.confwingman up warns (without blocking) when this is missing.
# Start named instances on two different networks
wingman up personal --management-url https://api.netbird.io:443 --setup-key KEY1
wingman up work --management-url https://api.netbird.io:443 --setup-key KEY2
# Peers + connection status (replaces `netbird status --detail | awk`)
wingman peers # all instances
wingman peers work # one instance
# Status / list / stop
wingman status
wingman list
wingman down workOmit --setup-key to log in interactively via SSO/OAuth instead.
To keep instances running on boot without an active login session, enable linger once (the AUR package prints this reminder on install):
sudo loginctl enable-linger "$USER"export WINGMAN_MANAGEMENT_URL=https://api.netbird.io:443
export WINGMAN_SETUP_KEY=YOUR_KEY
wingman up personal| Variable | Purpose |
|---|---|
WINGMAN_MANAGEMENT_URL |
Default management URL |
WINGMAN_SETUP_KEY |
Default setup key |
WINGMAN_NETBIRD_BIN |
Path to netbird binary (default: netbird on PATH) |
WINGMAN_CONFIG_DIR |
Override the config root (default: ~/.config/wingman) |
Each named instance gets:
- Its own config directory (
~/.config/wingman/<name>/, user-owned) - An isolated
NB_STATE_DIR, so a daemon never shares/var/lib/netbirdwith the system install (or another instance) and fight over the same WireGuard interface - A unique daemon socket (Unix socket on Linux/macOS, TCP port on Windows)
- A unique WireGuard interface (
wt<N>on Linux,utun<N>on macOS)
On Linux the daemon lifecycle is owned by systemd: up registers a systemctl --user unit and starts it (so instances survive reboot once linger is enabled), and down stops it — status/list query systemd directly. Where no service manager is available (containers, macOS, Windows), wingman falls back to supervising a netbird service run daemon directly, tracked via PID files. Either way it connects with netbird up once the daemon is reachable.
uv sync # install dependencies
uv run pytest -v # run tests (mocked, no real netbird needed)
uv run ruff check . # lint
uv run wingman --help # run the CLIThere are also Docker test harnesses under docker/ for exercising instances in an isolated network namespace without touching the host's NetBird.
- NetBird installed and on PATH
- Python 3.10+
MIT