---
title: Published ports
description: A guest port at its own https hostname — private by default, and the credential shown once.
---

A published port answers at its own https hostname, which reaches exactly that one port on
exactly that one machine. There is no path, query or header a caller can send that reaches a
different port, a different machine, or the host underneath: the hostname **is** the whole
routing decision, and it is resolved server-side.

```
GET    /v1/machines/{id}/ports                published ports on a machine
POST   /v1/machines/{id}/ports                publish one
GET    /v1/machines/{id}/ports/{preview_id}
DELETE /v1/machines/{id}/ports/{preview_id}   stop publishing
GET    /v1/ports                              every published port in the organization
```

## Publishing

```ts
const preview = await machine.ports.expose(3000, { name: "dev-server" });
preview.url; // https://k3f9x2q7m1.preview.example.com
preview.access_url; // the one-shot credentialed link
preview.secret; // the raw secret
```

`port` is the port **inside the machine**, not a host port. Nothing has to be listening yet:
exposing before starting the server is the ordinary order, and the URL reports the machine as
unavailable until something answers.

Exposing a port that is already published returns the preview that exists rather than a second
hostname for one service, so a retry converges.

:::note
A port mapped by a container — `docker run -p` or a compose `ports:` entry — is **not**
listening on the machine, and nothing errors to say so: the guest kernel cannot install the
mapping, so containers run on the machine's own network instead. Expose the port the service
listens on inside the container, and run compose stacks with `network_mode: host`. See
[containers](/docs/reference/machines#containers).
:::

## Private by default

`access` is `private` unless you say otherwise, and `public` is a deliberate act with a
deliberate name. An agent that exposes a port so a person can look at it should have to say that
is what it meant, and someone reading a list should be able to see which of their services are on
the open internet without opening each one.

```ts
await machine.ports.expose(3000, { access: "public" }); // no credential at all
```

### The secret is shown once

`secret` and `access_url` are present **only in the response to the call that created the
preview**. There is no route that returns them afterwards — a value the product can hand back on
request is a value that lives in every log and cache the response passes through, exactly like an
API key.

`access_url` is a one-shot link that trades the secret for a host-scoped cookie. That is what
makes a private preview openable in a browser at all: a person cannot set an `Authorization`
header on an address bar, and putting the secret in the preview's own query string would leave it
in browser history, in referers, and in the guest application's own access log.

## Unpublishing

```ts
await machine.ports.unexpose(preview.id);
```

The URL stops answering immediately, and that hostname is **never issued again**. A hostname that
came back would resurrect every link and bookmark pointing at it, aimed at whatever that port
serves next.

## Fields

| Field             | Meaning                                    |
| ----------------- | ------------------------------------------ |
| `port`            | the port inside the machine                |
| `name`            | your label, or null                        |
| `access`          | `private` or `public`                      |
| `url`             | the https URL it answers on                |
| `secret`          | at creation only; null on every later read |
| `access_url`      | at creation only; null on every later read |
| `last_request_at` | when it was last reached, or null          |

## Deployment requirement

Publishing needs the deployment to have a preview domain configured, with a wildcard DNS record
and a wildcard certificate under it. Without one the API refuses with `unsupported_operation`
rather than handing back a URL that resolves nowhere — a far better failure than a customer
debugging a dev server that was never reachable.

Bytes served through a preview count as egress. See [billing](/docs/reference/billing).
