Published ports

A published port is claimed across every node of the cluster it is published on. Two applications on one cluster publishing the same number would break each other, so the platform tracks every one and guarantees that two applications are never given the same wire on the same cluster.

A wire is a (protocol, port) pair on one cluster. 1194/udp and 1194/tcp are different wires and can be held by different applications at once, and so are 1194/udp on two different clusters.

Ask the platform to pick

Put 0 on the published side and the platform assigns a free port:

services:
  openvpn:
    image: someones/openvpn:latest
    ports:
      # `0` on the published side means "platform, pick a free one and tell me
      # which". Read it back from GET /cli/applications/{slug}/endpoints once the
      # revision is running; it does not change on redeploy.
      - "0:1194/udp"

You do not have to discover what is free, and you cannot lose a race — the assignment is made under a database constraint, so if two deploys pick the same instant, one is redelivered and lands on the next port instead.

Read back what you were given from /cli/applications/{slug}/endpoints, where the port arrives with "assigned": true.

Naming one yourself does nothing

You can write a number on the published side, but you will not get it. It is dropped at ingest and the entry is read as 0: — published, on a port the platform picked — with a warning on the revision saying so:

services:
  openvpn:
    image: someones/openvpn:latest
    ports:
      # A number you name is dropped with a warning: this is read as "0:1194/udp",
      # published on a port the platform picked. Read it back from /endpoints.
      - "1194:1194/udp"

service "openvpn": dropped the published port 1194 you chose for 1194/udp — the platform assigns every published port. It is published on one the platform picked; read that back from /cli/applications/{slug}/endpoints.

The upload is not refused, because the part the platform can honour is still there: this service wants 1194/udp reachable from outside. Only the number goes.

A published port is claimed across a whole cluster, so a number you pick is one you cannot know is free and cannot be promised. Assigning all of them removes that class of failure entirely: no collision with another tenant, and no well-known port somebody else has already squatted.

Warnings are listed on the revision's page in the control panel, beside the spec the platform actually deployed.

How stable the address is

A reservation is keyed on the cluster, the application, the compose service, the container port and the protocol. Redeploying onto the same cluster resolves to the same reservation, so the port you were given on the first deploy is the port you have on the hundredth. That is the case you are in almost always, and it is what makes it reasonable to write the address into something you hand to a client.

It is not a promise for all time, and a consumer should not treat it as one. Two things can move it:

  • The application is re-placed onto a different cluster — because its own is draining, unreachable, or gone. The platform offers it the number it already holds, so the move is usually invisible; but if that number is taken on the destination it is given a different one and the deploy still succeeds.
  • The stack is torn down. Stopping and deleting are one teardown, and a teardown hands every wire back straight away. Starting again allocates afresh, so a stopped application does not keep its address — it may be given the same number back, because assignment takes the lowest free port and the one just released usually is, but that is a coincidence rather than a promise.

So: re-read /endpoints after a deploy, rather than once at the first one, and treat the address you get as current rather than permanent. If you have issued something durable with an address baked into it — a VPN profile on somebody's phone names a literal port — you need a way to re-issue it, and stopping the stack is enough to require it.

You are told when it moves

You do not have to poll to find out. When an address changes or goes away, the platform raises a notification against the organization:

Kind Raised when
published_port_released The stack came down, so its ports went back into the pool. Starting again allocates new ones
published_port_changed The application was re-placed onto another cluster that already had its number taken

A machine consumer reads them the same way a person does:

GET /cli/notifications

An organization token sees the organization's notifications, because the account behind it is a member (Authentication).

The notification is the signal, not the payload. It tells you an address moved; /endpoints remains the single source of truth for what it moved to. Re-read that on seeing one rather than parsing the title, which is written for a person and will change wording.

A port you named yourself in compose never raises one — you changed it, so there is nothing to tell you.

The rules, in one table

compose ports: entry Result
"0:1194/udp" Platform assigns a free UDP port on the target cluster, stable across redeploys there
"1194:1194/udp" Read as "0:1194/udp" — the number is dropped with a warning, and a free one assigned
"1194/udp" Not published. Reachable from sibling services on the stack network only
Two services asking to publish the same container port Each is assigned its own wire; a wire belongs to one service
Assigned range exhausted The revision fails, naming the range for the operator to widen

Assigned ports come from 30000–32767 by default — Swarm's own auto-assign range, so an assigned port is one an operator already expects to be open. The range is therefore the whole of what may be published; a revision that exhausts it fails, naming the range for the operator to widen.

HTTP does not need any of this

A service exposing 80, 8080, 3000, 8000 or 5000 is detected as HTTP and given a hostname at the platform's edge, with TLS terminated for it. It needs no published port at all, and publishing one does not replace the hostname — you get both.

When your port is not one of those, say so

Those five are a guess, and the guess is all the platform had: whether a port speaks HTTP is not a property of the number. A service listening on 8473 was given no hostname, no certificate and no router, and there was no way to correct it.

x-someones.http is that way. An x--prefixed key is the compose spec's own extension field, so the file still runs under docker compose up unchanged and every other tool ignores it:

services:
  lab:
    image: ghcr.io/acme/lab
    x-someones:
      http: 8473
Declares which container port speaks HTTP
Publishes nothing — the edge reaches it over the shared ingress network
Needs a ports: entry no. Add one only if you also want a raw wire; both is fine
Beats the guess yes. A service declaring 8473 while also exposing 80 is routed to 8473
Bad value a 400 at ingest, not a warning — a mistyped port that was ignored would leave you exactly as unrouted as before

Read the result back from /endpoints, or off the application's page in the control panel, which answers from the same resolver.

← All developer pages