Expose a Docker container
Expose a Docker container running a web app to the internet with a Horizon tunnel, using a published port such as -p 3000:3000.
Share a web app that runs in a Docker container, with an HTTPS URL. You run hrzn on your host, not in the container.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI on your host (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - Docker, and an image whose app listens on port 3000
Publish the port
Publish the container's port to your host with -p HOST_PORT:CONTAINER_PORT. Without a host IP, Docker publishes the port on all host interfaces.
docker run -d -p 3000:3000 my-imageTo keep the port off your network, bind it to loopback. The Horizon CLI still reaches it, because it runs on your host.
docker run -d -p 127.0.0.1:3000:3000 my-imageCheck the app on the host:
curl http://localhost:3000Start a tunnel
Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appKeep this terminal open.
Open the URL
Open https://my-app.hrzn.run. A first-time browser visitor sees the Before you continue page. Select Continue to site.
Check it works
The Horizon terminal prints a line per request:
GET 200 /Horizon sets Host to localhost:3000, so your app sees the host the container's port is published on. The public host arrives in X-Forwarded-Host.
Troubleshooting
Bind for 0.0.0.0:3000 failed: port is already allocated
Another container or process holds host port 3000. Publish a different host port, for example -p 3001:3000, and tunnel to http://localhost:3001.
Nothing reaches your app
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Run
curl http://localhost:3000on the host. If it fails, the problem is the container, not the tunnel. Rundocker psand check thePORTScolumn. - The tunnel target is the host port, the left side of
-p.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app.
Next steps
- Use the same flow with Docker Compose.