# Expose a preview URL

URL: https://briq.cloud/docs/guides/expose-a-preview-url

> Give a running service an HTTPS URL, public or private.

Briqs are private. Publishing gives one port of a briq its own HTTPS URL, either as you run it
(`expose` on `briq_run`) or later with `briq_expose`. A URL is public or private:

| Mode      | Who can open it                         | Use                                                                                                                                                  |
| --------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `private` | Only whoever has the URL you were given | Anything only you, or the people you send it to, should see: an app in progress, a preview for a reviewer, an admin UI, webhooks from a known sender |
| `public`  | Anyone on the internet                  | A demo or a site meant for everyone, webhooks from anyone                                                                                            |

A private URL carries a secret, `?briq_token=`. briq checks it before a request reaches your
service and turns away every request without it. On the first visit, the browser keeps it as a
cookie and lands on the clean address, so links inside your app work as usual.

```text
briq_run({ image: "ghcr.io/acme/web:pr-412", name: "web", stack: "preview", ports: [3000],
           env: { PORT: "3000" }, expose: "private" })
→ bq_9a2fk3m8q1zt web (ghcr.io/acme/web:pr-412) running · …internal · url: https://web-k7x3.briq.run/?briq_token=… · cost so far … · sleeps at 19:10 UTC unless used
  outputs:
    URL=https://web-k7x3.briq.run/?briq_token=…
```

The URL is known as soon as the run answers, `wait: false` included, and serves once the briq
runs. Recipes with a web UI publish their front door the same way:
`briq_run({ recipe: "grafana", expose: "private" })`.

`briq_expose({ briq, port, mode })` publishes a briq that is already running, or switches its
mode. Its front door (the recipe's, or a briq's only port) is published at once. Another port
moves the URL there and restarts the machine to deliver it: the processes start over, and files
on its disk are kept. Without `mode`, the URL keeps the mode it has: moving a private URL to
another port never opens it.

## Good to know

* **Listen on `::`**, not `localhost` or `0.0.0.0` alone: the private network is IPv6-only.
* **HTTP only.** Keep databases private and reach them from another briq.
* **Stable address**: `https://<briq>-<suffix>.briq.run`, the suffix four random characters
  drawn on the first expose. It lasts as long as the briq, and exposing again keeps it, token
  included. A new briq gets a new suffix, even under the same name.
* **An app that needs its own address at boot** (links in emails, an OAuth callback, a `BASE_URL`):
  publish it as you run it and write `{{self:host}}` in a plain `env` value, for example
  `env: { BASE_URL: "https://{{self:host}}" }`. briq puts the URL's hostname there before the app
  starts. A briq published later gets `localhost`.
* **One URL per briq.** Exposing another port moves the URL to that port.
* **A private link that leaked**: `briq_expose({ briq, port, rotate: true })`, or "New token" on
  the briq's page in the dashboard. The URL stays; links and cookies with the old token stop
  working.
* **Visits keep the stack awake**, and wake it when it sleeps. A URL can stay up for months this
  way, billed only while it is used (`sleep: "30m"` sleeps it after half an hour without
  visits). A briq stopped by `briq_stop`, its own exit or a limit answers 503 until started or
  until the limit clears; a destroyed briq loses its URL.
* **Limits**: a public URL has no token to rotate, and a briq declares at most 32 ports, so
  exposing a new one past that is refused.
