Horizon

Expose a Docker Compose service

Expose a Docker Compose service to the internet with a Horizon tunnel, using the service's published port.

Share a web service from a Compose project, with an HTTPS URL. You run hrzn on your host, not in a 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 Compose, and a service whose app listens on port 3000

Publish the port

Add a ports entry to the service. Write it as a quoted string, as Docker recommends, to avoid a YAML parsing conflict.

compose.yaml
services:
  web:
    image: my-image
    ports:
      - "3000:3000"

The short syntax is [HOST:]CONTAINER. To keep the port off your network, use "127.0.0.1:3000:3000". The Horizon CLI still reaches it, because it runs on your host.

Start the service.

docker compose up -d

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  /

Troubleshooting

Bind for 0.0.0.0:3000 failed: port is already allocated

Another container or process holds host port 3000. Change the left side of the entry, for example "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 service, not the tunnel. Run docker compose ps and check the ports column.
  • A service without a ports entry isn't reachable from your host. Tunnel only to a service that publishes a port.

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