Several Fortinet SSL-VPN / IPsec tunnels at once, in one Docker container, driven from your browser.
148
๐ฌ๐ง English ยท ๐ต๐ฑ Polskiโ
Tunnelbox connects you to several Fortinet (FortiGate) SSL-VPN and IPsec VPNs at once, straight from your browser โ the tunnels run inside a Docker container, so your own computer never joins the client's network.

curl -fsSLO https://raw.githubusercontent.com/Mysttic/tunnelbox/main/docker-compose.yml && docker compose up -d
Then open http://tunnelbox.localhost:25263โ . On a Mac, add the loopback aliasesโ first.
Formerly FortiVPN 2. Not affiliated with or endorsed by Fortinet. Built on openfortivpnโ and strongSwanโ .
127.0.0.3:13392.You need Docker Desktopโ (Windows, Mac) or Docker Engine with the Compose plugin (Linux). In the folder where Tunnelbox should keep its data, run:
curl -fsSLO https://raw.githubusercontent.com/Mysttic/tunnelbox/main/docker-compose.yml && docker compose up -d
and open http://tunnelbox.localhost:25263โ . Windows
PowerShell 5.1 (the one built into Windows) needs curl.exe instead of curl, which is an
alias there, and the two commands run separately, because && needs PowerShell 7:
curl.exe -fsSLO https://raw.githubusercontent.com/Mysttic/tunnelbox/main/docker-compose.yml
docker compose up -d
./data next to docker-compose.yml.docker compose pull, then docker compose up -d, in the same folder. When
a new version is out, the panel's update bar shows these two commands โ a release appears
only once its image is in GHCR (ghcr.io/mysttic/tunnelbox, the one docker-compose.yml
uses); the Docker Hub copy follows it and can lag behind if copying fails.ghcr.io/mysttic/tunnelbox (GHCR) and mysttic/tunnelbox (Docker Hub) for
linux/amd64, linux/arm64 and linux/arm/v7, tagged latest, X.Y.Z and X.Y. To pin
a version, change :latest in docker-compose.yml.start.bat, so fast RDP connect and the one-click update
are not set up โ the panel offers "Download .rdp file" instead (turn off โ Settings โ
Fast RDP connect to get it straight away). Want both? Use the ZIP package below.Download tunnelbox-X.Y.Z.zip from the
project pageโ and unpack it into any
folder. The package carries the image (tunnelbox-image-X.Y.Z.tar.gz): the start script
loads it locally (docker load), so nothing is built and nothing is pulled from a registry.
start.bat. It also sets up fast RDP connect and the one-click
"โฌ๏ธ Update" button in the panel. Data: %LOCALAPPDATA%\Tunnelbox\data../start.sh. Data: ~/.local/share/tunnelbox/data.The panel opens at http://tunnelbox.localhost:25263โ and starts with Docker from then on โ no terminal window to keep open. The package carries its own short README with installation and troubleshooting notes.
git clone https://github.com/Mysttic/tunnelbox.git
cd tunnelbox
./start.sh # Windows: double-click start.bat
start.bat / ./start.sh build the image locally (tunnelbox:dev, with the
docker-compose.build.yml override on top of docker-compose.yml); the first run takes a few
minutes. Data lives in ./data in the repository.
The forwarded ports listen on 127.0.0.2โ127.0.0.60, and macOS routes only 127.0.0.1 to
the loopback by default: Docker Desktop for Mac needs the aliases 127.0.0.2โ127.0.0.60 on
lo0, otherwise the container does not start. ./start.sh (package and source) adds the
missing ones with sudo. For the registry install, add them yourself before
docker compose up -d:
for i in $(seq 2 60); do sudo ifconfig lo0 alias 127.0.0.$i up; done
The aliases disappear after a reboot, and Docker then cannot start the container on its own:
run the loop again, then docker compose up -d in the folder with docker-compose.yml
(package and source installs: just run ./start.sh, it does both). Linux and Windows need
nothing of the kind.
Podman and Rancher Desktop are not tested yet โ the container needs a rootful engine, because
it runs privileged with network namespaces and /dev/ppp (Podman: podman machine set --rootful; Rancher Desktop with the dockerd (moby) engine behaves like Docker).
privileged: true is the first thing anyone reading docker-compose.yml asks about. It stays
because at runtime the panel:
ip netns add needs mount propagation on
/run/netns, that is CAP_SYS_ADMIN and no mount denial from Docker's AppArmor profile
docker-default;CAP_NET_ADMIN;rp_filter, forwarding) for interfaces it has just created,
and Docker mounts /proc/sys read-only unless the container is privileged;/dev/ppp for pppd โ the device does not exist in Docker Desktop's virtual machine,
so the entrypoint creates it with mknod, which the device cgroup has to allow.A variant with cap_add / devices / sysctls instead is possible, but it has not been
verified with a real PPP tunnel, so 3.0.0 keeps privileged. It still listens only on
127.0.0.0/8: the panel and the forwarded ports are reachable from this computer only.
๐ Full user manual with screenshots: docs/MANUAL.mdโ (Polish: docs/MANUAL.pl.mdโ ) โ step by step, from installing to the first RDP session. Written for people who do not work with networks.
127.0.0.3:13392 and a โฎ
menu with "Connect over RDP", "Copy address" and more. You can also paste the address
into your RDP/SSH client and connect.vault.enc in the data folder (see Installโ ),
encrypted with the master password. The panel and the forwarded ports are reachable from
this computer only (they listen on the 127.0.0.0/8 loopback).13391โ13449, and every port gets its own loopback
address: 13391 โ 127.0.0.2, 13392 โ 127.0.0.3, โฆ 13449 โ 127.0.0.60.
A separate address per machine is necessary because Windows stores RDP credentials
under TERMSRV/<address> without the port โ on a shared localhost the saved
passwords would overwrite each other. To change the pool, edit both compose files
(docker-compose.yml, packaging/docker-compose.yml) and app/connections.py.Mysttic/Mysttic repository, next to other programs'
downloads) and shows a bar above the connection list: with the ZIP package on Windows the
"โฌ๏ธ Update" button downloads and installs it without leaving the browser, with the
registry install the bar shows the docker compose pull command. The check sends none of
your data and can be turned off in โ Settings. The details and the contract (tag
tunnelbox-vX.Y.Z, file tunnelbox-X.Y.Z.zip) are in app/updates.pyโ ../start.sh / start.bat again; it
rebuilds the image with
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build.docker compose down in the folder with docker-compose.yml
(the program folder for the ZIP package).docker rm -f fortivpn2-panel, it holds port 25263 and the port pool), install Tunnelbox
and load the file with "โฌ๏ธ Import" on its first screen, using the same master password.
FortiVPN 2 already removed? Import its vault file instead:
%LOCALAPPDATA%\FortiVPN2\data\vault.enc on Windows,
~/.local/share/fortivpn2/data/vault.enc on macOS and Linux (with Docker Engine that file
belongs to root, so make a copy you can read first:
sudo install -o "$USER" -m 600 ~/.local/share/fortivpn2/data/vault.enc ~/fortivpn2-vault.enc).
A source checkout keeps its ./data, so there only the container has to go. Once
everything works, delete %LOCALAPPDATA%\FortiVPN2 (~/.local/share/fortivpn2) and the
fortivpn2-panel images.browser โโ tunnelbox.localhost:25263 โโ FastAPI (panel + API)
โ
container tunnelbox
โ
per connection: netns "vpnN" โโ openfortivpn (ppp0)
โ or charon (IPsec policies)
โ
127.0.0.X:1339X โโ socat โโ veth โโ socat โโ machine in the client's network
app/main.py โ the panel's API (profiles, connect/disconnect, certificate trust)app/vault.py โ the encrypted vault of profiles (scrypt + AES-256-GCM)app/connections.py โ network namespaces, tunnel lifecycle, port forwardingapp/ipsec.py โ strongSwan configuration and driving the daemon through swanctlapp/updates.py โ the daily check for a newer releaseapp/i18n.py, app/static/i18n.js โ English and Polish message catalogsapp/static/ โ the browser interfaceSSL-VPN and IPsec differ only in how the tunnel is established. openfortivpn creates a
ppp0 interface and reports progress on stdout; IPsec creates no interface โ traffic
is steered by an XFRM policy and a separate routing table (220), and the state is
established and checked through swanctl. Each tunnel gets its own charon-systemd
instance, because IPsec policies belong to a network namespace.
The panel and the forwarded ports listen on the 127.0.0.0/8 loopback only, saved profiles
are encrypted with your master password (scrypt + AES-256-GCM), and apart from your VPN
tunnels the only connection the panel makes by itself is the daily update check, which you
can turn off. The threat model, and how to report a vulnerability without opening a public
issue: SECURITY.mdโ .
Tunnelbox's own code is released under the MIT Licenseโ . The image also carries openfortivpn, strongSwan, pppd, socat and other components under their own licenses; they run as separate programs, and THIRD_PARTY.mdโ lists them together with where to find their exact versions and sources.
Bug reports, ideas and pull requests are welcome โ CONTRIBUTING.mdโ covers
the branches (develop โ main), the commit convention, running the tests and refreshing the
manual's screenshots. Publishing a release (for the maintainer):
docs/RELEASING.mdโ โ a release is built automatically when develop is
merged into main.
Content type
Image
Digest
sha256:9b499c134โฆ
Size
71.1 MB
Last updated
1 day ago
docker pull mysttic/tunnelbox