Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The mantis needs a public-reachable URL. Pick the option that matches where you
| **B. [Docker + Tailscale](./tailscale.md)** | Your machine | Tailscale Funnel (`*.ts.net`) | Personal mantis on a laptop / home server — works behind CGNAT |
| **C. [Docker + Cloudflare Tunnel](./cloudflare.md)** | Your machine | Your own domain on Cloudflare | You already have a Cloudflare-hosted domain; want SSO via Cloudflare Access |
| **E1. [Railway](./railway.md)** | Railway (long-running) | `*.up.railway.app` or custom | Set-and-forget; the worker runs natively (no cron config) |
| **E2. [Fly.io](./fly.md)** | Fly (long-running) | `*.fly.dev` or custom | Same shape as Railway; broader regional choice |
| **E2. [Fly.io](./fly.md)** | Fly app + managed or external Postgres | `*.fly.dev` or custom | One-command, idempotent launch; broader regional choice |
| **E3. [Render](./render.md)** | Render (long-running) | `*.onrender.com` or custom | Same shape; free tier exists but cold-starts after 15 min idle |

For any of the public options, also see **[edge-limits.md](./edge-limits.md)** for rate-limit and DDoS guidance, and **[backups.md](./backups.md)** for Postgres backup strategies.
Expand Down
112 changes: 100 additions & 12 deletions deployment/fly.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,107 @@
---
title: "Fly.io"
description: "Deploy Mantis to Fly.io with Postgres and public edge guidance."
description: "Deploy Mantis to Fly.io with one command, Postgres, and safe proxy defaults."
---

The repository includes an idempotent launcher that creates the Fly app,
connects Postgres, sets the required secrets, deploys Mantis, and prints the
first admin API key.

## Quick launch

Prerequisites:

- A Fly.io account and [`flyctl`](https://fly.io/docs/flyctl/install/).
- A local clone of the Mantis repository.
- `fly auth login` completed once.

```bash
git clone https://github.com/privacykey/mantis
cd mantis
fly auth login

bash deploy/fly-launch.sh --app my-mantis --region iad
```

The launcher shows the app-machine and database choices before creating
anything. Add `--dry-run` to print the planned commands, or `--yes` to skip the
confirmation prompt:

```bash
bash deploy/fly-launch.sh \
--app my-mantis \
--region iad \
--dry-run
```

On a first launch, save the `mantis_live_...` admin key printed at the end. It
is shown once and works for both the dashboard login and `mantis login`.

## Database choices

The default is Fly Managed Postgres. It is the supported Fly database product,
but it is billed separately and can cost considerably more than the small
Mantis app machine. The launcher surfaces that before provisioning.

| Option | Behaviour | When to use it |
|---|---|---|
| `--db mpg` | Creates and attaches Fly Managed Postgres. This is the default. | You want the supported, managed Fly option. |
| `--db external` | Imports the `DATABASE_URL` already present in your environment. | You use Neon, Supabase, or another external Postgres provider. |
| `--db unmanaged` | Creates a legacy `fly postgres` machine. | You accept operating and recovering Postgres yourself. |
| `--db none` | Skips database setup. | You will attach or configure Postgres separately before the app can start. |

For an external database:

```bash
cp deploy/fly.toml.example fly.toml
# edit `app =` to a unique name; edit PUBLIC_BASE_URL after first deploy
fly launch --no-deploy --copy-config
fly postgres create --name mantis-db
fly postgres attach mantis-db
fly secrets set \
PUBLIC_BASE_URL=https://<app>.fly.dev \
MANTIS_API_KEY_PEPPER="$(openssl rand -base64 32)" \
BOOTSTRAP_API_KEY=mantis_live_...
fly deploy
DATABASE_URL='postgres://...' \
bash deploy/fly-launch.sh --app my-mantis --db external
```

Fly concurrency protects the VM, not the public URL. For app-layer URL/rate limits, use a Cloudflare-proxied custom domain; see **[edge limits](/deployment/edge-limits#flyio)**.
## Safe re-runs and configuration checks

Re-running the launcher is safe: it reuses an existing app and database and
refuses to rotate `MANTIS_API_KEY_PEPPER`, because rotating that value would
invalidate every API key.

If `fly.toml` already exists, the launcher leaves it untouched and checks its
public origin before deploying:

- `PUBLIC_BASE_URL` must be a quoted absolute `https://` URL.
- A `*.fly.dev` URL must match the app passed with `--app`.
- An intentional custom HTTPS domain is accepted.
- Missing `TRUST_PROXY_HEADERS` remains a warning so existing deployments are
not blocked, but it should be fixed before relying on hit IP attribution.

The generated configuration enables trusted proxy headers and pins attribution
to Fly's authoritative `X-Forwarded-For` value. Without those settings,
production deployments record a null client IP. If a custom domain is proxied
through Cloudflare, follow the comments in `deploy/fly.toml.example` and use
`cf-connecting-ip` instead.

## Later deployments

To redeploy from your machine, either re-run the launcher with the same options
or deploy the existing configuration directly:

```bash
fly deploy --app my-mantis --config fly.toml
```

The repository also includes an opt-in
[`fly-deploy.yml`](https://github.com/privacykey/mantis/blob/main/.github/workflows/fly-deploy.yml)
workflow for deploys from `main`. It stays inert until you configure the
app-scoped `FLY_API_TOKEN`, `FLY_APP`, and `FLY_DEPLOY_ENABLED=true` repository
settings described at the top of that file.

## Verify

```bash
curl https://my-mantis.fly.dev/api/health
mantis login --url https://my-mantis.fly.dev
mantis doctor
```

The health response should report `"status":"ok"` and `"db":"ok"`.

Fly concurrency protects the VM, not the public canary URL. For app-layer
rate limits and DDoS guidance, see **[edge limits](/deployment/edge-limits#flyio)**.
2 changes: 1 addition & 1 deletion getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The CLI talks to either of two backends, with the same command surface:

| Backend | What it gives you | Where it runs |
|---|---|---|
| **Stateful server** (`mantis`) | Dashboard, hit history, audit log, multiple destinations per key, key revocation, monitor/status, optional Apple Wallet passes | Your own server. Docker Compose on a VPS, or one-click via [Railway / Fly.io / Render](/deployment). **Don't run this on your laptop as your primary backend** — your laptop's not on the public internet at 3am when the canary fires. |
| **Stateful server** (`mantis`) | Dashboard, hit history, audit log, multiple destinations per key, key revocation, monitor/status, optional Apple Wallet passes | Your own server. Docker Compose on a VPS, the [one-command Fly.io launcher](/deployment/fly), or the [Railway / Render guides](/deployment). **Don't run this on your laptop as your primary backend** — your laptop's not on the public internet at 3am when the canary fires. |
| **Cloudflare Worker** (`mantis-edge`) | Sub-50ms response from the CF edge, no DB to host, "anyone with the AES key can mint", URLs are self-contained encrypted blobs | Cloudflare's edge — you deploy a single Worker via `pnpm --filter @mantis/edge run deploy` from the source checkout (see [`mantis-edge/`](https://github.com/privacykey/mantis/tree/main/mantis-edge)) |

You can use **both**. Most people deploy the stateful server for primary tripwires (the ones you want a timeline + multi-destination management for), and reach for the edge variant for hand-off URLs that just need to fire a webhook without a central log.
Expand Down
2 changes: 1 addition & 1 deletion updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Pick the row that matches how each component was installed. After bumping the se
| **Server** | Docker (`docker compose up -d`) | `git pull && docker compose up -d --build` — `AUTO_MIGRATE=1` (set in `docker-compose.yml`) runs new migrations on container start. |
| **Server** | Local dev (`pnpm run dev`) | `git pull && corepack enable && pnpm install && pnpm run db:migrate` — then restart `pnpm run dev`. |
| **Server** | Railway / Render (Git-tracked) | `git push` to the tracked branch — both auto-redeploy. Migrations run on boot via `AUTO_MIGRATE=1`. |
| **Server** | Fly.io | `git pull && fly deploy`. |
| **Server** | Fly.io launcher | `git pull && bash deploy/fly-launch.sh --app <name> --region <region>` — safely reuses the app/database and does not rotate the API-key pepper. If `fly.toml` is already configured, `git pull && fly deploy --app <name> --config fly.toml` is the shorter equivalent. |
| **CLI** | Homebrew (`brew install privacykey/tap/mantis`) | `brew update && brew upgrade mantis`. |
| **CLI** | Direct tarball from [releases](https://github.com/privacykey/mantis/releases?q=cli-v) | Download the new `mantis-<platform>.tar.gz`, extract, replace the binary on your `$PATH`. |
| **Edge worker** | `mantis-edge/` via wrangler | `git pull && corepack enable && pnpm install && pnpm --filter @mantis/edge run deploy` — secrets (`MANTIS_EDGE_KEY`, `MANTIS_EDGE_WEBHOOK_ALLOWLIST`) persist across deploys, no re-keying needed. |
Expand Down
Loading