Ports and public URLs

Last updated

Binding a port is how your app becomes reachable. If your app makes only outbound connections — a bot, a worker, a poller — you don't need one.

Adding a binding

On the app's Ports panel, choose a protocol and the port your app listens on inside the container. Saving restarts the app.

HTTPS

For anything that speaks HTTP. You get a public hostname with a valid certificate, TLS terminated at the edge, and plain HTTP forwarded to your container port.

  • Leave the subdomain empty to use the generated hostname, a<appId>-<hash>.<platform-domain>.
  • Or set your own label: it must start with a letter, contain only letters, digits, - and _, and be globally unique. Names matching the generated pattern are reserved.
  • Your app should serve plain HTTP on that port. Don't terminate TLS yourself — if you're using something Caddy-based, disable its automatic HTTPS or it will try to obtain certificates it doesn't need.

Certificates are automatic. There is nothing to upload, and nothing to renew.

TCP and UDP

For non-HTTP services — databases, game protocols, MQTT, anything raw.

The important detail: the public port is generated, not chosen. You give the container port, and the binding is published on a platform host with a high port number. The Ports panel shows the effective public endpoint — use that pair in connection strings.

That means services expecting a standard port need explicit configuration on the client side, and some tools (git over SSH, for example) are awkward as a result.

Bind 0.0.0.0

Your app must listen on all interfaces. A server bound to 127.0.0.1 is reachable only from inside its own container.

This is the most common reason a deploy "succeeds" and the URL doesn't work. Because nothing probes your app, nothing catches it for you — the container is running, so everything looks fine.

EXPOSE in a Dockerfile does not create a binding. It's image metadata; the panel is what publishes a port.

Everything you bind is public

There is no internal-only option, no IP allowlist, and no firewall. A bound port is on the internet from the moment the app starts.

  • Authenticate anything that isn't meant to be public.
  • Remove bindings you don't need — a database only your own app talks to gains nothing from being reachable.
  • Don't rely on the hostname being unguessable.

See Networking for the full picture.

Port count

Bindings count against a per-account limit alongside CPU, RAM and disk. See Limits.

Troubleshooting

Symptom Likely cause
URL returns nothing, app is running Listening on 127.0.0.1, or on a different port than the one bound
Certificate warning You're terminating TLS inside the container — serve plain HTTP instead
TCP client can't connect on the port you chose The public port is generated; check the Ports panel
Subdomain rejected Must start with a letter; may already be taken