> ## Documentation index
> Fetch the complete index at: https://maritime.sh/llms.txt
> Setup procedure for AI agents (install, sign in, deploy, verify): https://maritime.sh/agents.md
> This page as HTML: https://maritime.sh/docs/web-apps

# 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](https://maritime.sh/docs/builds).

## 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](https://maritime.sh/docs/frameworks/custom).

## 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](https://maritime.sh/docs/configuration); the full flag set for `create` and
`deploy` is in the [CLI reference](https://maritime.sh/docs/cli#maritime-create).
