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.
Choose Your Mode
Section titled “Choose Your Mode”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 setFull (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:latestHeadless (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:headlessQuick Start
Section titled “Quick Start”Agent mode
Section titled “Agent mode”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:headlessThen add endpoints in the dashboard. They go live within seconds — no restart.
Point endpoints at whatever the container can reach:
| Target | Local URL to use |
|---|---|
| A service on the Docker host | http://host.docker.internal:8123 |
| Another container on the same network | http://container-name:8080 |
| Something else on the LAN | http://192.168.1.50:8096 |
Setting HLE_AGENT_TOKEN switches the container into agent mode; HLE_API_KEY
and the web UI are ignored.
Full image (with web UI)
Section titled “Full image (with web UI)”docker run -d \ --name hle \ -p 8099:8099 \ -v hle-data:/data \ ghcr.io/hle-world/hle-docker:latestOpen 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.

Login token
Section titled “Login token”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:
docker logs hle 2>&1 | grep -A1 "login token"docker exec hle cat /data/ui_token # if the log line is goneDelete /data/ui_token and restart the container to rotate it, or set
HLE_UI_TOKEN to use a fixed token of your own.
Headless image (no UI)
Section titled “Headless image (no UI)”docker run -d \ --name hle \ -p 8099:8099 \ -v hle-data:/data \ ghcr.io/hle-world/hle-docker:headlessThe API is available at http://your-host:8099/api/ and takes the same
login token. Manage tunnels via CLI or API (see below).
Docker Compose
Section titled “Docker Compose”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:# Startdocker compose up -d
# View logsdocker compose logs -f hleEnvironment Variables
Section titled “Environment Variables”| Variable | Default | Description |
|---|---|---|
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_PORT | 8099 | Port 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.
Data Persistence
Section titled “Data Persistence”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/).
Managing Tunnels via CLI
Section titled “Managing Tunnels via CLI”The hle CLI is available inside both images. Use docker exec to interact with it:
# Set your API keydocker 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 tunnelsdocker exec hle hle status
# See all CLI commandsdocker exec hle hle --helpTo 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.
Managing Tunnels via API
Section titled “Managing Tunnels via API”The REST API is available on both images at port 8099. Send the login token as a Bearer token:
TOKEN=$(docker exec hle cat /data/ui_token)
# List tunnelscurl -H "Authorization: Bearer $TOKEN" http://localhost:8099/api/tunnels
# Get current configurationcurl -H "Authorization: Bearer $TOKEN" http://localhost:8099/api/config
# Update API keycurl -X POST http://localhost:8099/api/config \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"api_key": "hle_your_key_here"}'Updating
Section titled “Updating”Pull the latest image and recreate the container. Your data is preserved in the volume.
With Compose:
docker compose pulldocker compose up -dWithout Compose:
docker pull ghcr.io/hle-world/hle-docker:latestdocker stop hle && docker rm hledocker run -d --name hle -p 8099:8099 -v hle-data:/data ghcr.io/hle-world/hle-docker:latestFor automatic updates, use Watchtower to monitor and auto-update the container.
Home Assistant Container / Core
Section titled “Home Assistant Container / Core”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.
Building from Source
Section titled “Building from Source”git clone https://github.com/hle-world/hle-docker.gitcd 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 .Troubleshooting
Section titled “Troubleshooting”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.
Can’t reach services on the Docker host
Section titled “Can’t reach services on the Docker host”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.
Port conflict
Section titled “Port conflict”If port 8099 is already in use, map to a different host port: -p 9099:8099.