Skip to content
Login

Docker

Verified 2026-11-13

Run HLE tunnels on any Docker host — Synology NAS, Unraid, bare metal servers, VMs, or cloud instances. Choose the full image with a web UI for managing tunnels visually, or the headless image for a lighter, API-and-CLI-only deployment.

View on GitHub

Agent (managed from the dashboard) — One container serves every endpoint you declare at hle.world/dashboard. No local config, no published ports. Best if you want one place to manage everything, including other machines. See the agent guide.

ghcr.io/hle-world/hle-docker:headless # with HLE_AGENT_TOKEN set

Full (With Web UI) — Includes the React dashboard for managing tunnels, access rules, PIN protection, and share links. Best for most users.

ghcr.io/hle-world/hle-docker:latest

Headless (API + CLI Only) — No web UI — manage tunnels via the hle CLI or REST API. Faster build, lighter footprint.

ghcr.io/hle-world/hle-docker:headless
Terminal window
docker run -d \
--name hle-agent \
--restart unless-stopped \
-e HLE_AGENT_TOKEN=hle_your_token_here \
--add-host host.docker.internal:host-gateway \
-v hle-agent-data:/data \
ghcr.io/hle-world/hle-docker:headless

Then add endpoints in the dashboard. They go live within seconds — no restart.

Point endpoints at whatever the container can reach:

TargetLocal URL to use
A service on the Docker hosthttp://host.docker.internal:8123
Another container on the same networkhttp://container-name:8080
Something else on the LANhttp://192.168.1.50:8096

Setting HLE_AGENT_TOKEN switches the container into agent mode; HLE_API_KEY and the web UI are ignored.

Terminal window
docker run -d \
--name hle \
-p 8099:8099 \
-v hle-data:/data \
ghcr.io/hle-world/hle-docker:latest

Open http://your-host:8099 in a browser and log in with the container’s login token (see Login token). Enter your API key in Settings, then add a tunnel.

HLE Docker web UI with the Settings panel open and one connected tunnel

The web UI and every /api route on port 8099 require a login token. On first start the container generates one, prints it once in the log, and stores it in /data/ui_token:

Terminal window
docker logs hle 2>&1 | grep -A1 "login token"
docker exec hle cat /data/ui_token # if the log line is gone

Delete /data/ui_token and restart the container to rotate it, or set HLE_UI_TOKEN to use a fixed token of your own.

Terminal window
docker run -d \
--name hle \
-p 8099:8099 \
-v hle-data:/data \
ghcr.io/hle-world/hle-docker:headless

The API is available at http://your-host:8099/api/ and takes the same login token. Manage tunnels via CLI or API (see below).

services:
hle:
image: ghcr.io/hle-world/hle-docker:latest # or :headless
container_name: hle
restart: unless-stopped
ports:
- "8099:8099"
volumes:
- hle-data:/data
environment:
- HLE_API_KEY= # optional: set here or via web UI/CLI
volumes:
hle-data:

For agent mode:

services:
hle-agent:
image: ghcr.io/hle-world/hle-docker:headless
container_name: hle-agent
restart: unless-stopped
volumes:
- hle-agent-data:/data
environment:
- HLE_AGENT_TOKEN=hle_your_token_here
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
hle-agent-data:
Terminal window
# Start
docker compose up -d
# View logs
docker compose logs -f hle
VariableDefaultDescription
HLE_AGENT_TOKEN(empty)Agent token (hle_…). When set, the container runs in agent mode and takes its endpoints from the dashboard.
HLE_API_KEY(empty)Your HLE API key. Can also be set via the web UI or hle auth login.
HLE_PORT8099Port the server listens on inside the container.
HLE_UI_TOKEN(generated)Fixed login token for the web UI and API (at least 20 characters). When unset, a random token is generated on first start and stored in /data/ui_token.

HLE_AGENT_TOKEN takes precedence over HLE_API_KEY. Both can also be stored in /data/hle_config.json as agent_token / api_key.

All configuration and tunnel state is stored in /data inside the container. Mount a Docker volume or host directory to persist data across container restarts and upgrades.

Contents: hle_config.json (API key, settings), tunnel definitions, and logs (/data/logs/).

The hle CLI is available inside both images. Use docker exec to interact with it:

Terminal window
# Set your API key
docker exec hle hle auth login --api-key YOUR_API_KEY
# Expose a local service (e.g. Home Assistant on the Docker host)
docker exec hle hle tunnel create ha http://host.docker.internal:8123
# List active tunnels
docker exec hle hle status
# See all CLI commands
docker exec hle hle --help

To stop a tunnel, stop the process serving it — from the web UI on port 8099, or by stopping the container. There is no CLI verb for it: asking the relay to drop the connection does not stop anything, because the client reconnects.

Once a tunnel is stopped, docker exec hle hle tunnel delete ha removes its record and the access rules attached to it.

The REST API is available on both images at port 8099. Send the login token as a Bearer token:

Terminal window
TOKEN=$(docker exec hle cat /data/ui_token)
# List tunnels
curl -H "Authorization: Bearer $TOKEN" http://localhost:8099/api/tunnels
# Get current configuration
curl -H "Authorization: Bearer $TOKEN" http://localhost:8099/api/config
# Update API key
curl -X POST http://localhost:8099/api/config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"api_key": "hle_your_key_here"}'

Pull the latest image and recreate the container. Your data is preserved in the volume.

With Compose:

Terminal window
docker compose pull
docker compose up -d

Without Compose:

Terminal window
docker pull ghcr.io/hle-world/hle-docker:latest
docker stop hle && docker rm hle
docker run -d --name hle -p 8099:8099 -v hle-data:/data ghcr.io/hle-world/hle-docker:latest

For automatic updates, use Watchtower to monitor and auto-update the container.

Home Assistant rejects X-Forwarded-* headers from a proxy it does not trust and answers 400 Bad Request. The HA add-on writes the proxy settings for itself, but a plain Docker agent cannot, so on a Container or Core install you have to add them to configuration.yaml yourself:

http:
use_x_forwarded_for: true
trusted_proxies:
- 172.19.0.0/16 # the agent's Docker network
  • Trust the agent’s Docker subnet, not a single container IP — IPs change when a container is recreated. Find the network’s subnet with docker network inspect <network> | grep Subnet.
  • Restart Home Assistant afterwards. A config reload does not apply http: changes.
  • Trust only that subnet. Never use 0.0.0.0/0 — it lets anyone spoof client IPs and dodge HA’s own IP bans.
  • Keep Home Assistant’s own login enabled; HLE’s SSO sits in front of it, not in place of it.

The Home Assistant page covers the add-on, which configures this for you.

Terminal window
git clone https://github.com/hle-world/hle-docker.git
cd hle-docker
# Full image (with web UI)
docker build -t hle-docker:local .
# Headless (no UI)
docker build -f Dockerfile.headless -t hle-docker:headless .

Container starts but tunnels don’t connect

Section titled “Container starts but tunnels don’t connect”

Check that your API key is set correctly. View logs with docker logs hle or check /data/logs/ inside the container.

Use host.docker.internal instead of localhost in service URLs. On Linux, you may need to add --add-host=host.docker.internal:host-gateway to your docker run command or add extra_hosts: ["host.docker.internal:host-gateway"] in Docker Compose.

If port 8099 is already in use, map to a different host port: -p 9099:8099.