Installation¶
You have four ways to install tunlx. Pick the one that matches how you already deploy software.
One-line installer Fastest path on a fresh Linux host. Installs Docker, deploys tunlx, writes a starter config, optionally locks down to Tailscale.
Docker Compose For operators who want to own the stack. Start from the repo's compose file and adjust.
Native binary Build from source with Go 1.27+. Drop the binary anywhere and point it at a config file.
Nixpacks
Build an OCI image straight from the repo with nixpacks.toml — for Railway, Coolify, Dokploy, and similar PaaS builders.
Prerequisites¶
- Linux host (Debian / Ubuntu recommended for the installer; any modern distro works for Docker / native)
- Root or sudo access
- Outbound internet access for image / package downloads (once installed, the sign-in page, dashboard and web player load every script, style and font from tunlx itself, so they work on a LAN with no internet; proxied streams and external EPG or iptv-org sources still need to reach their upstreams)
Choose your path¶
The installer brings up everything you need on a clean host.
What it does:
- Installs missing dependencies (
docker,docker compose,htpasswd, iptables persistence helpers). - Pulls
ghcr.io/eyupio/tunlx:latest. - Prompts for admin credentials and dashboard bind settings.
- Optionally enables Tailscale-only access.
- Optionally configures WireGuard helper scripts.
It asks before it installs two optional helpers for a nicer interface:
toilet (the banner) and gum (the menus). gum comes from the Charm
apt repository, which is added to your apt sources, so the installer says
so and waits for a yes. Say no and the questions are asked with simpler menus.
Piped from curl, it asks on your terminal; with no terminal (a CI job, or
ssh host 'curl ... | bash') it stops and tells you to download the script
first or to use --silent.
After it finishes, open http://<host-ip>:6060 and sign in with the
credentials it printed.
Silent install for CI / automation¶
TUNLX_ADMIN_PASSWORD='change-me-now' \
TUNLX_TAILSCALE_ONLY=1 \
TUNLX_TAILSCALE_AUTHKEY='tskey-auth-XXXXXXXX' \
TUNLX_TAILSCALE_HOSTNAME='tunlx' \
bash install.sh --silent
Run with -n (dry-run) to see what it would do without changing
anything. See CLI & Environment
for every flag.
Use this when you manage your own stack and want to keep tunlx as one service among others.
Minimal example:
services:
tunlx:
container_name: tunlx
image: ghcr.io/eyupio/tunlx:latest
restart: unless-stopped
environment:
- commandLine=-configFile=/app/data/config.json
volumes:
- ./data:/app/data
network_mode: host
Start it:
Notes:
network_mode: hostkeeps proxy listeners reachable without per-port mapping.- Keep
config.jsonunder./data/config.jsonfor the default path. - Add
NET_ADMIN,NET_RAW, and/dev/net/tunonly if you need in-container WireGuard routing.
A gluetun-sidecar compose variant lives under deployments/docker/ in
the repo.
Build it from source.
git clone https://github.com/eyupio/tunlx.git
cd tunlx
go build -o tunlx .
./tunlx -configFile=/etc/tunlx/config.json
First start auto-creates config.json if it's missing. From there,
sign in to http://<host>:6060 and start configuring.
The binary carries its own dashboard pages and static files, so you can
copy it anywhere and start it from any directory; there is nothing else to
ship next to it. To edit the pages or stylesheets while developing, point
TUNLX_ASSETS_DIR at a checkout and tunlx reads templates/ and
static/ from there instead (restart to pick up template edits).
The repo ships a nixpacks.toml, so any Nixpacks
builder can turn the source tree into a runnable image without a
Dockerfile. Build it locally:
Run what it produced:
What the build definition does:
- Builds
tunlx-linuxwithCGO_ENABLED=0and-buildvcs=false, letting the Go toolchain ingo.moddownload itself (the provider's pinned nixpkgs Go is older than this project needs). - Runs from a slim
ubuntu:nobleimage carrying only the binary and CA certificates; the dashboard pages and static files are built into it. - Starts with
-configFile=/app/data/config.json, so mount a volume at/app/datato keep config and state across rebuilds. - Maps an injected
$PORTontoTUNLX_DASHBOARD_PORTwhen the platform sets one, so hosts that assign a random port still reach the dashboard.
On a PaaS. Point Railway, Coolify, Dokploy, or Zeabur at the repository and select the Nixpacks builder — these platforms prefer a Dockerfile when they find one, and this repo ships both, so the choice has to be explicit.
Then attach a persistent volume at /app/data. Without one, every deploy
starts from a fresh config.json and a fresh session secret; if the
platform can't give you persistent storage, at least pin
TUNLX_SESSION_SECRET so sessions survive a restart. See
CLI & Environment for the rest
of the environment overrides.
The Nixpacks image is a lean runtime
It ships no ffmpeg, so HLS→TS and MP4→TS remuxing is unavailable —
leave that off, or use the Docker image, which does include it.
VPN egress is out too: route: vpn and in-container WireGuard need
NET_ADMIN, NET_RAW, /dev/net/tun, and wireguard-tools. Most
managed hosts grant none of them, and proxies asking for VPN egress
fall back to public egress — or return 503 if
fail-closed is on. Take the Docker Compose path when
you need either.
What the baseline compose file grants¶
The docker-compose.yaml in the repository is set up so that every feature
works the moment you start it. That means it asks for more than a plain proxy
needs. This is what each setting is for and what you lose without it.
Setting in docker-compose.yaml |
What uses it | Without it |
|---|---|---|
network_mode: host |
Proxy listeners on any port, with no ports: line per proxy. |
Publish the dashboard port and one port per proxy with ports:. |
NET_ADMIN, NET_RAW and /dev/net/tun |
Bringing up WireGuard for route: vpn. With host networking the interface is created on the host. |
VPN routing stays down; nothing else changes. |
/etc/wireguard |
Keeps your wg0.conf, and the wg0.conf.bak copy of the one the last save replaced, on the host. |
They are lost when the container is recreated. |
/var/run/docker.sock |
The Docker Logs dialog and the start, stop and restart buttons for a tunlx_gluetun sidecar. Whoever holds the socket controls every container on the host, so treat it as root access. |
Those two features do not work. |
pid: "host" |
Nothing: tunlx does not look at other processes. | No tunlx feature is affected, so remove it unless you rely on it for something of your own. |
/var/run/mullvad-vpn and /usr/bin/mullvad (commented out) |
The Mullvad provider (vpn.mode: provider): it lists servers by running the host's Mullvad CLI, which talks to the Mullvad app's daemon over that socket. The image does not contain the CLI. They are off by default because Docker creates a host path that is missing as an empty directory, which would leave an empty /usr/bin/mullvad on a host without the app. |
Uncomment both lines when the Mullvad app is installed on the host. Left off, the provider's server lookup fails with "mullvad CLI binary not found"; a manual WireGuard config does not use it. |
The mediaflow service, port 8800 |
The optional MediaFlow companion. It is published on the host's loopback only (127.0.0.1:8800), which is all tunlx needs on host networking, so other machines cannot reach it. Set MEDIAFLOW_API_PASSWORD in a .env file (see examples/.env.example) anyway: both containers read it. |
Remove the service if you do not use MediaFlow. To reach it from other machines, change the port line to "8800:8888" and set that password first. |
To start from nothing and add only what you use, take the minimal example in the Docker Compose tab above. Without host networking it publishes ports instead:
services:
tunlx:
image: ghcr.io/eyupio/tunlx:latest
restart: unless-stopped
environment:
- commandLine=-configFile=/app/data/config.json
ports:
- "6060:6060" # the dashboard
- "8100:8100" # one line for each proxy you create
volumes:
- ./data:/app/data
Post-install validation¶
- Sign in. Open the dashboard and confirm your credentials work.
- Check metrics.
curl http://localhost:6060/metricsshould return JSON. - Create your first proxy. Follow the Quick Start.
- If using WireGuard, verify
wg showreports an interface before relying onroute: vpn.
Restrict to Tailscale (any time)¶
You can switch any installation to Tailnet-only access. Either follow Tailscale Access for the embedded-node path, or use the sidecar pattern documented there for older deployments.
Upgrade¶
Rebuild from main (or check out a tag), drop the new binary in place,
and restart your unit / process. config.json is forward-compatible
between minor versions — unknown fields are ignored.