Skip to content

Expose a port publicly

Any HTTP service running on one of your hosts can get a public HTTPS address served by the DedNets edge. The hostname is derived automatically as {app}-{username}.{domain}; TLS certificates are issued on demand at the first request. You never open a port on your own network: traffic enters at the edge and rides the daemon’s existing outbound connection.

There are three ways to mark a service public. All of them end up in the same place; pick whichever fits how the service runs. Prefer your own domain? See Use your own domain.

Open Services. The left rail lists your hosts and everything each one publishes; click a host to open it. (The page became this explorer in v0.10.1 — it used to be one collapsible card per host.)

A host’s pane shows its containers with one chip per port. A chip you can click is a port nothing publishes yet; a green one names the service already using it. Click a free chip and the publish dialog opens with the container and port filled in — give the app a name, pick a protocol, and choose whether it gets a public DedNets hostname.

A container only advertises the ports its image EXPOSEs and the ones it published to the host, so a service listening on anything else has no chip. The dashed port… chip at the end of the row is for exactly that (since v0.11.2): type the port and its transport, and publish it like any other. The daemon dials the container directly, so any port it really listens on works — the chips are a convenience, not the limit.

The protocol choices are evidence-driven: a UDP port offers only UDP, and a port the daemon probed and found answering something other than HTTP will not offer http. A port you typed was probed by nobody, so the dialog takes your word for it — though the protocol still follows the number until you pick one (5432 suggests tcp, 8080 http). Below the containers, Host ports lists what is listening on the machine outside Docker, with the same Expose action — and the pane’s Expose a port button takes a port number directly, for anything discovery has not found.

That page also has a Use with AI button (since v0.4.3): if you would rather have an agent do it, it hands your assistant the exact steps.

Once published, the service appears in the rail with a health dot and its 24-hour uptime, and clicking it opens everything about it: every hostname that reaches it, the request path a request takes to get there, its traffic and latency, and Remove when you are done with it. (That button was called Unpublish before v0.11.2. It takes the whole service away; dropping only its DedNets hostname is the × on that hostname’s row, below.)

The DedNets hostname is not permanent, and dropping it is not the same as unpublishing the service. Open the service, find the row for {app}-{username}.{domain} under Hostnames, and click the × on it (since v0.11.2). The edge stops serving that name, and everything else stays: any domain of your own bound to the app keeps serving it, and your other hosts keep reaching it over the mesh at {username}/{daemon}/{app}. Click Add hostname → The DedNets name to put it back.

That is the usual last step of moving an app onto your own domain: bind your name, check it serves, then retire the DedNets one so the app answers on a single address.

The × is there only for a service the Console publishes (its badge reads console). For one published by a Docker label or the daemon’s config file, the switch lives where the app is declared — set dedmesh.public: "false" or public = false and the hostname goes away on the daemon’s next snapshot. If the host is offline when you turn the name off, it keeps answering until the host reconnects and applies the change; the Console says so when that happens.

A public HTTP app can be put behind a DedNets login instead of being open to anyone with the URL: tick Require a DedNets sign-in in the publish dialog, or set dedmesh.auth / auth / -auth dednets below. Only you and the accounts you allow get in, and everyone else is stopped at the edge. See Put an app behind a DedNets login (since v0.10.0).

If you prefer the service to declare itself, label its container:

# docker compose example
services:
blog:
image: ghost
labels:
dedmesh.enable: "true"
dedmesh.port: "2368"
dedmesh.name: "blog" # default: the container name
dedmesh.protocol: "http" # http (default) | tcp | udp
dedmesh.public: "true" # request the public hostname
dedmesh.auth: "dednets" # optional, since v0.10.0: require a DedNets
# sign-in - none (default) | dednets

The daemon watches Docker events, so starting the container publishes the app and stopping it unpublishes it. No Console clicks needed.

For a daemon run from a config file, add the app there:

[[apps]]
name = "blog"
protocol = "http"
local_host = "127.0.0.1"
local_port = 2368
public = true
auth = "dednets" # optional, since v0.10.0: none (default) | dednets

Raw TCP/UDP is published by a gateway: one of your own hosts (say, a VPS) publishes the port on its own address, any port you like, including 80, 443, or 53. See Publish on your own gateway.

DedNets never binds a raw port on its own infrastructure for you - the shared edge serves HTTP/HTTPS only (the admin-only raw edge ports were removed in v0.6.2).

Services that should not be public at all can stay mesh-only: other daemons of yours (or users you share the app with) reach them through local proxy ports, and nothing is exposed to the internet.