Horizon

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-image

To 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-image

Check the app on the host:

curl http://localhost:3000

Start a tunnel

Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.

hrzn tunnel http://localhost:3000 -s my-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Keep 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:

Output
  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 for Reconnected.
  • Run curl http://localhost:3000 on the host. If it fails, the problem is the container, not the tunnel. Run docker ps and check the PORTS column.
  • 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

On this page