# Public web agents

Any repo with a Dockerfile can be served on a public, no-login HTTPS URL. Maritime builds it, runs it serverlessly, puts it to sleep when idle, and wakes it on the next visit.

## Deploy one

```
maritime create my-app --repo https://github.com/you/app --public
```

| Flag | Description |
| --- | --- |
| `-r, --repo` | GitHub repo to build (Dockerfile at the root). Combine with `-b, --branch` |
| `--public` | Serve a public, no-login web URL for this agent |
| `--port` | Port the public URL sends traffic to. Keep the default, 8080: Maritime forwards it to the PORT your app binds |

The dashboard's **New agent** flow takes the same sources: a GitHub repo, a Docker image, or a ZIP upload. Repo and ZIP sources build on a dedicated build machine; see [How builds work](/content/docs/builds/index.html).

## Visibility

Every agent that serves a port has its own address, `https://<agent-id>.mtm.sh/`, shown on its Overview as **URL**. It is private until you say otherwise.

- **Private** (the default). A visitor needs a share link. Your backend mints one for a signed-in user with `maritime.agents.shareLinks.create(agentId, { endUserExternalId })` and sends them to its `url`. The first visit swaps the secret for a cookie on that hostname, so pages, assets, and WebSockets all work. A link lasts one hour unless you choose up to 30 days, or no expiry. A link bound to an end user dies when you suspend that end user.
- **Public.** Anyone with the URL may open it, and a visit wakes a sleeping agent. Pass `--public` at create, press **Public** on the Overview, or run `maritime agents url visibility <agent-id> public`. Needs a verified email.

The app sees its own `Host` and its own cookies. Agent URLs tell search engines not to index them. The older `https://api.maritime.sh/a/<agent-id>` path keeps working for public agents.

## What your app must do

- **Ship a Dockerfile at the repo root.** Whatever it builds is what runs.
- **Listen on `0.0.0.0:$PORT`.** Maritime sets `PORT` and forwards public traffic on port 8080 to it. Don't hardcode a port, and don't bind 8080 yourself.
- **Persist to `/data` only.** It survives restarts, redeploys, and sleep/wake; the rest of the filesystem is disposable.

A web app does not need the chat contract; that is only for agents you want to talk to. If you want both (a web UI *and* a chattable agent), implement the two-endpoint contract from [Deploy any container](/content/docs/frameworks/custom/index.html).

## Sleep and wake

The app auto-sleeps when idle and any request to its public URL wakes it, in about a second, with state intact. Sleeping is not downtime: visitors just wake it. The first request after a sleep waits for the wake; every request after that is answered by a running machine.

## Configure and observe

```
maritime env set my-app DATABASE_URL=postgres://... --reload   # encrypted secrets
maritime logs my-app -f                                        # stream logs
maritime history my-app                                        # every build/deploy attempt
```

Env vars, secrets, and sizing are covered in [Env vars & sizing](/content/docs/configuration/index.html); the full flag set for `create` and `deploy` is in the [CLI reference](/content/docs/cli#maritime-create/index.html).
