> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Going live

> Step-by-step from a fresh clone to a live commercial site — every credential, every dashboard URL, every env var name.

This is the "I cloned Vibestrap, now what do I actually have to do to put real money in
my bank account" guide. It covers every external service you'll touch, every dashboard
URL, and every env var name — in the order that makes sense.

## Mental model

Vibestrap follows a **fill-what-you-use** model. The app boots with **two** real values
(`DATABASE_URL` + `BETTER_AUTH_SECRET`); everything else is optional and gracefully
no-ops when blank. Don't have a Stripe account yet? The pricing page just hides the
checkout button. No Resend key? Password reset still renders, just doesn't send.

But here's the trap most people fall into:

<Warning>
  **The app booting is not the same as the app working.** Zod validates env-var
  *structure* (`min(1)`, `.url()`, etc.) — not *semantics*. A pod can be `1/1 Running`
  while the website returns 500 because the placeholder `DATABASE_URL` doesn't point at a
  real database. So when you set things up, **test the actual user flows** (sign up, log
  in, checkout) — don't trust just `kubectl get pods`.
</Warning>

### The 3 config layers

Vibestrap's configuration lives in three places, each for a reason:

| Layer             | Lives in                             | What it controls                          |
| ----------------- | ------------------------------------ | ----------------------------------------- |
| Brand & product   | `src/config/site.ts`                 | Name, copy, pricing display, social links |
| Translations      | `messages/{en,zh}.json`              | All user-facing strings                   |
| Secrets & toggles | `k8s/.env` (or `.env.local` for dev) | API keys, DB URLs, provider switches      |

This guide is about the third layer. For the other two see
[Configuration](/configuration) and [Customization](/customization).

### Where you'll edit values

The flow on a live cluster:

```bash theme={null}
# 1. edit k8s/.env on your laptop (gitignored — never committed)
vim k8s/.env

# 2. push the env file into the cluster as a Secret
./k8s/create-secrets.sh

# 3. pods don't auto-reload secrets — restart the deployment
kubectl rollout restart deployment/vibestrap -n vibestrap
```

For local dev you put the same names in `.env.local` and `pnpm dev` picks them up
automatically — no restart dance.

## Required: the 4 values without which the site shows 500

Skip any of these and the site won't render. Do these first.

<Steps>
  <Step title="DATABASE_URL — your Postgres">
    **Why**: every page that reads or writes data goes through this. Without a real DB,
    every non-static route returns 500. The marketing homepage might load; `/login`,
    `/register`, `/pricing`, `/admin` will not.

    **Where to sign up**: [neon.tech](https://neon.tech) — serverless Postgres, generous
    free tier, indie-friendly. (Alternatives: Supabase, Railway, RDS — anything that
    speaks Postgres 15+.)

    <Steps>
      <Step title="Create the project">
        1. Sign up at [https://neon.tech](https://neon.tech)
        2. Create a new project — pick a region close to your K8s cluster (e.g. `us-east-2`
           if your nodes are there)
        3. Wait \~10 seconds for provisioning to finish
      </Step>

      <Step title="Grab the connection string">
        1. In the left sidebar, click **Connection Details**
        2. **Important**: select **Pooled connection** (handles serverless and burst traffic
           way better than direct)
        3. Copy the URL it shows
        4. Make sure it ends with `?sslmode=require` — Neon enforces TLS, your app will fail
           to connect without it
      </Step>

      <Step title="Paste into k8s/.env">
        ```bash theme={null}
        DATABASE_URL=postgres://neondb_owner:abc...@ep-xxx-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
        ```
      </Step>
    </Steps>

    | Variable       | Example                                                                                    |
    | -------------- | ------------------------------------------------------------------------------------------ |
    | `DATABASE_URL` | `postgres://neondb_owner:...@ep-xxx-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require` |

    <Warning>
      **First-time setup requires a manual database step.** The runtime image
      doesn't auto-apply migrations — same model as ShipAny / mksaas. Right
      after you set `DATABASE_URL`, run **one** of these from your laptop
      against the production URL:

      ```bash theme={null}
      # Recommended for production — applies the SQL files in
      # src/db/migrations/ in order, tracks history in __drizzle_migrations.
      DATABASE_URL='postgres://...prod...' DATABASE_PROVIDER=postgres pnpm db:migrate

      # Or for the absolute fastest path (no migration history kept).
      DATABASE_URL='postgres://...prod...' DATABASE_PROVIDER=postgres pnpm db:push
      ```

      Skip this and the app will boot but every page that hits the DB returns
      500 with `relation "user" does not exist`. Each schema change in the
      future is the same flow: `pnpm db:generate` to write a new migration
      file, commit it, then `pnpm db:migrate` against the prod URL before
      applying the new image.
    </Warning>
  </Step>

  <Step title="BETTER_AUTH_SECRET — auth signing key">
    **Why**: signs session cookies and JWT tokens. If it's left as the placeholder, anyone
    who reads the public template repo can forge sessions for any user on your site. This
    is a security hole that would end your business on day one.

    **Where to sign up**: nowhere — generate it on your laptop.

    <Steps>
      <Step title="Generate">
        ```bash theme={null}
        openssl rand -base64 32
        ```

        Output is 44 chars and ends with `=`.
      </Step>

      <Step title="Paste into k8s/.env">
        ```bash theme={null}
        BETTER_AUTH_SECRET=S8sFO3lhBaqfDD0p308BqjBNQ6TssHA9i2p4JoMYSb4=
        ```
      </Step>
    </Steps>

    | Variable             | Example                                        |
    | -------------------- | ---------------------------------------------- |
    | `BETTER_AUTH_SECRET` | `S8sFO3lhBaqfDD0p308BqjBNQ6TssHA9i2p4JoMYSb4=` |

    <Warning>
      Never commit this. Never reuse the same secret across staging and prod. Never paste it
      in chat or screenshots. If it leaks, regenerate it — every user gets logged out, but
      that's the only safe move.
    </Warning>
  </Step>

  <Step title="NEXT_PUBLIC_APP_URL — your domain">
    **Why**: this value gets baked into the **client bundle** at build time. It's used for
    OG images, sitemap URLs, OAuth redirect URIs, and links inside emails. If it points at
    `localhost`, every shared link will be broken.

    | Variable              | Example                 |
    | --------------------- | ----------------------- |
    | `NEXT_PUBLIC_APP_URL` | `https://vibestrap.dev` |

    <Note>
      Must be the production https URL. Don't include a trailing slash. Don't use `http://`.
    </Note>
  </Step>

  <Step title="BETTER_AUTH_URL — auth callback root">
    **Why**: Better Auth's OAuth callback signature relies on this. If wrong, sign-in
    callbacks reject as "invalid origin" and users see a confused error page.

    In 99% of cases this should be the same as `NEXT_PUBLIC_APP_URL`. The only reason to
    split them is if your auth lives on a different subdomain.

    | Variable          | Example                 |
    | ----------------- | ----------------------- |
    | `BETTER_AUTH_URL` | `https://vibestrap.dev` |
  </Step>
</Steps>

After these four, your pod boots and the homepage renders. Now make it actually useful.

## Strongly recommended: the things buyers expect to work

Without these, the site loads but key features are broken: users can't recover
passwords, can't sign in via Google or GitHub, can't pay you. Set them up before
launch — not after the first user complaint.

### Resend (transactional email)

**Why**: password reset, email verification, welcome emails. Without it, the "forgot
password" button is purely decorative.

**Where to sign up**: [resend.com](https://resend.com) — free tier covers 100 emails
per day, 3,000 per month. Plenty for early launch.

<Steps>
  <Step title="Sign up + verify your own email">
    Standard signup flow.
  </Step>

  <Step title="Add and verify your domain">
    1. Left sidebar → **Domains** → **Add Domain**
    2. Enter your domain (e.g. `vibestrap.dev`)
    3. Resend shows 3 DNS records (SPF, DKIM, DMARC) — **don't close the page**
    4. Add those DNS records at your registrar (Cloudflare, Namecheap, GoDaddy, …)
    5. Click **Verify DNS Records**; wait 5–30 min until status shows **Verified**
  </Step>

  <Step title="Create an API key">
    1. Left sidebar → **API Keys** → **Create API Key**
    2. Permission: "Sending access" is enough
    3. Copy the `re_xxx` key — it's only shown once
  </Step>
</Steps>

| Variable                | Where it comes from               | Example                                      |
| ----------------------- | --------------------------------- | -------------------------------------------- |
| `RESEND_API_KEY`        | Resend → API Keys                 | `re_a1b2c3d4...`                             |
| `RESEND_FROM_EMAIL`     | Any email at your verified domain | `Larry from Vibestrap <hello@vibestrap.dev>` |
| `RESEND_REPLY_TO_EMAIL` | Inbox you actually read           | `larry@vibestrap.dev`                        |

<Note>
  The human-readable name in `RESEND_FROM_EMAIL` (e.g. `Larry from Vibestrap`) is
  optional, but it noticeably improves deliverability — bare `hello@…` looks like a bot.
</Note>

### Admin access

You'll want to reach `/admin` yourself.

| Variable       | Example                                         |
| -------------- | ----------------------------------------------- |
| `ADMIN_EMAILS` | `wisehackerlarry@gmail.com,partner@example.com` |

Comma-separated. Matched against the user's email at sign-in time. The first matching
user gets the `admin` role on first login and from then on can access `/admin`.

### Google OAuth

**Why**: roughly 60% of indie devs prefer "sign in with Google" over typing a password.
The conversion lift on a signup form with this button alone is huge.

**Where to set up**: [console.cloud.google.com](https://console.cloud.google.com)

<Steps>
  <Step title="Pick or create a Google Cloud project">
    Top-left dropdown → **New Project**, or pick an existing one.
  </Step>

  <Step title="Configure the OAuth consent screen">
    1. Left menu → **APIs & Services** → **OAuth consent screen**
    2. User type: **External** → Create
    3. Fill **App name**, **User support email**, **Developer contact email**
    4. Save and continue through the scopes / test users screens (defaults are fine)
  </Step>

  <Step title="Create the OAuth client">
    1. Left menu → **APIs & Services** → **Credentials**
    2. **+ Create Credentials** → **OAuth 2.0 Client ID**
    3. Application type: **Web application**
    4. Name: anything (e.g. "Vibestrap")
    5. **Authorized JavaScript origins**: `https://vibestrap.dev`
    6. **Authorized redirect URIs**: `https://vibestrap.dev/api/auth/callback/google`
       (this exact path — Better Auth uses it)
    7. Click **Create** → modal shows Client ID + Client Secret → copy both immediately
  </Step>
</Steps>

| Variable                       | Example                                                           |
| ------------------------------ | ----------------------------------------------------------------- |
| `GOOGLE_CLIENT_ID`             | `1234567890-abc...apps.googleusercontent.com`                     |
| `GOOGLE_CLIENT_SECRET`         | `GOCSPX-abcdef...`                                                |
| `NEXT_PUBLIC_GOOGLE_CLIENT_ID` | same as `GOOGLE_CLIENT_ID` (mirror, used for client-side One-Tap) |

<Warning>
  The redirect URI must be byte-for-byte exact: `https` vs `http`, trailing slash,
  capitalization, all matter. Most "OAuth doesn't work" reports trace back to a typo
  here. If you ever change your domain, update this URI on the same day.
</Warning>

### GitHub OAuth

**Why**: Vibestrap targets developers, and "sign in with GitHub" converts \~80% of devs
who land on your signup page. Worth 5 minutes of setup.

**Where to set up**: [github.com/settings/developers](https://github.com/settings/developers)
→ **OAuth Apps** → **New OAuth App**

<Steps>
  <Step title="Fill the form">
    * **Application name**: Vibestrap (or whatever you want users to see)
    * **Homepage URL**: `https://vibestrap.dev`
    * **Authorization callback URL**: `https://vibestrap.dev/api/auth/callback/github`
    * Click **Register application**
  </Step>

  <Step title="Grab credentials">
    1. Copy the **Client ID** shown on the app page
    2. Click **Generate a new client secret** → copy it immediately (only shown once)
  </Step>
</Steps>

| Variable               | Example         |
| ---------------------- | --------------- |
| `GITHUB_CLIENT_ID`     | `Iv1.abc123...` |
| `GITHUB_CLIENT_SECRET` | `ghs_abcdef...` |

### Stripe

**Why**: take money. Without this, the entire pricing page is decorative.

**Where to set up**: [dashboard.stripe.com](https://dashboard.stripe.com)

<Steps>
  <Step title="Activate your Stripe account">
    Provide tax + banking info. Required before Stripe gives you live keys. Test mode
    works without activation, but you can't actually receive payouts.
  </Step>

  <Step title="Grab the API keys">
    1. **Developers → API keys**
    2. Copy **Secret key** (`sk_live_...`)
    3. Copy **Publishable key** (`pk_live_...`)
  </Step>

  <Step title="Create the product and prices">
    1. **Products → + Add product**
    2. Name it "Vibestrap" (or whatever your product is called)
    3. Add prices under that product. For Vibestrap typically two:
       * **Standard one-time**: e.g. \$99
       * **Promo one-time**: e.g. \$49 (optional sale price)
    4. Click into each price; copy the `price_xxx` ID from the URL or the price detail
       panel
  </Step>

  <Step title="Register the webhook">
    1. **Developers → Webhooks → + Add endpoint**
    2. **Endpoint URL**: `https://vibestrap.dev/api/webhooks/stripe`
    3. Events to send (toggle each):
       * `checkout.session.completed`
       * `customer.subscription.created`
       * `customer.subscription.updated`
       * `customer.subscription.deleted`
       * `invoice.payment_succeeded`
       * `invoice.payment_failed`
    4. Click **Add endpoint**
    5. On the endpoint detail page, click **Reveal** next to **Signing secret** → copy
       `whsec_...`
  </Step>
</Steps>

| Variable                             | Example                                   |
| ------------------------------------ | ----------------------------------------- |
| `STRIPE_SECRET_KEY`                  | `sk_live_abc...`                          |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | `pk_live_abc...`                          |
| `STRIPE_WEBHOOK_SECRET`              | `whsec_abc...`                            |
| `STRIPE_PRICE_VIBESTRAP_STANDARD`    | `price_abc...` (your standard SKU)        |
| `STRIPE_PRICE_VIBESTRAP_PROMO`       | `price_abc...` (optional, for sale price) |

<Warning>
  **The webhook secret is the most-forgotten step.** Without it, Stripe accepts your
  customer's payment, but your DB never records it — buyer paid you, got nothing. Always
  test the happy path with Stripe's "Send test webhook" button on the endpoint page
  **before** going live. If the signature check fails, your `STRIPE_WEBHOOK_SECRET` is
  wrong.
</Warning>

## SEO + analytics — strongly recommended

These don't affect whether your site works, but they directly affect whether anyone
finds it.

### Google Search Console — site verification

**Why**: Google won't reliably index your site without verification. No SEO traffic =
no organic growth = paid ads are your only acquisition channel.

**Where**: [search.google.com/search-console](https://search.google.com/search-console)

<Steps>
  <Step title="Add the property">
    1. **Add property** → **URL prefix** → enter `https://vibestrap.dev`
    2. Choose **HTML tag** verification method
  </Step>

  <Step title="Copy just the value">
    The page shows something like `<meta name="google-site-verification" content="ABC123..." />`.
    You only want the bit between the quotes — `ABC123...`.
  </Step>
</Steps>

| Variable                   | Example                                                  |
| -------------------------- | -------------------------------------------------------- |
| `GOOGLE_SITE_VERIFICATION` | `Rj7vQ...` (the `content="..."` value, not the full tag) |

### Microsoft Clarity — heatmaps + session recordings

**Why**: free heatmaps, session recordings, and dead-click detection. The best \$0
you'll ever not-spend on user research.

**Where**: [clarity.microsoft.com](https://clarity.microsoft.com)

<Steps>
  <Step title="Create a project">
    1. Sign in (Microsoft / Google / Facebook all work)
    2. **New project** → name it, paste your URL
    3. Skip the install instructions; Vibestrap injects the script automatically
    4. Copy the project ID — 10-char alphanumeric, shown on the install instructions page
  </Step>
</Steps>

| Variable                         | Example      |
| -------------------------------- | ------------ |
| `NEXT_PUBLIC_CLARITY_PROJECT_ID` | `abc123xyz0` |

### PostHog — product analytics (optional)

**Why**: funnels, retention, feature flags. Free tier covers 1M events/month, more
than enough for early-stage products.

**Where**: [posthog.com](https://posthog.com)

<Steps>
  <Step title="Create a project">
    1. Sign up; create a new project
    2. **Project Settings → Project API Key** → copy `phc_xxx`
    3. Note the host: US cloud is `https://us.i.posthog.com`, EU is `https://eu.i.posthog.com`
  </Step>
</Steps>

| Variable                   | Example                              |
| -------------------------- | ------------------------------------ |
| `NEXT_PUBLIC_POSTHOG_KEY`  | `phc_abc...`                         |
| `NEXT_PUBLIC_POSTHOG_HOST` | `https://us.i.posthog.com` (default) |

## Optional — only when you actually need them

A single table for everything else. Don't pre-emptively wire these — they're each
"feature off until you fill in the var".

| Feature                    | Set up when                                 | Variables                                                                                                    | Where to sign up                                                                    |
| -------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| Bing site verification     | You want Bing/DuckDuckGo traffic            | `BING_SITE_VERIFICATION`                                                                                     | [bing.com/webmasters](https://www.bing.com/webmasters)                              |
| Yandex verification        | Targeting Russian-speaking users            | `YANDEX_VERIFICATION`                                                                                        | [webmaster.yandex.com](https://webmaster.yandex.com)                                |
| Resend Audience newsletter | Collecting email subs via Resend            | `RESEND_AUDIENCE_ID`                                                                                         | [resend.com](https://resend.com) → Audiences                                        |
| Beehiiv newsletter         | You already use Beehiiv                     | `BEEHIIV_API_KEY`, `BEEHIIV_PUBLICATION_ID`                                                                  | [beehiiv.com](https://beehiiv.com)                                                  |
| Crisp chat                 | Want live chat widget                       | `NEXT_PUBLIC_CRISP_WEBSITE_ID`                                                                               | [crisp.chat](https://crisp.chat)                                                    |
| Tawk.to chat               | Free Crisp alternative                      | `NEXT_PUBLIC_TAWK_PROPERTY_ID`, `NEXT_PUBLIC_TAWK_WIDGET_ID`                                                 | [tawk.to](https://tawk.to)                                                          |
| Intercom                   | Bigger budget, better CRM                   | `NEXT_PUBLIC_INTERCOM_APP_ID`                                                                                | [intercom.com](https://intercom.com)                                                |
| Chatwoot                   | Self-hosted support                         | `NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN`, `NEXT_PUBLIC_CHATWOOT_BASE_URL`                                        | [chatwoot.com](https://chatwoot.com)                                                |
| Cloudflare Turnstile       | Anti-bot on signup/checkout                 | `NEXT_PUBLIC_TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY`                                                     | [cloudflare.com](https://www.cloudflare.com/products/turnstile/)                    |
| Affonso affiliates         | Affiliate program                           | `NEXT_PUBLIC_AFFONSO_ID`                                                                                     | [affonso.io](https://affonso.io)                                                    |
| Rewardful affiliates       | Stripe-native affiliates                    | `NEXT_PUBLIC_REWARDFUL_API_KEY`                                                                              | [rewardful.com](https://rewardful.com)                                              |
| Creem (alt to Stripe)      | Stripe unavailable in your region           | `CREEM_API_KEY`, `CREEM_WEBHOOK_SECRET`, `CREEM_PRODUCT_ID`                                                  | [creem.io](https://creem.io)                                                        |
| AI providers               | Your product uses AI                        | `AI_PROVIDER`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `REPLICATE_API_TOKEN`, `FAL_KEY` | provider websites                                                                   |
| S3 / R2 storage            | User uploads, generated images              | `S3_ENDPOINT`, `S3_REGION`, `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`                          | AWS S3 or [Cloudflare R2](https://developers.cloudflare.com/r2/)                    |
| GitHub invite              | Inviting buyers to your private source repo | `GITHUB_INVITE_TOKEN`                                                                                        | fine-grained PAT — see [GitHub invite delivery](/operations/github-invite-delivery) |

For long-form setup of any of these, the per-feature pages have full walkthroughs:
[Customer service](/growth/customer-service), [Affiliate](/growth/affiliate),
[Newsletter](/growth/newsletter), [Turnstile](/growth/turnstile),
[GitHub invite delivery](/operations/github-invite-delivery), [AI providers](/ai/providers).

## Apply + verify

You've filled in `k8s/.env`. Now push it into the cluster and prove it works.

### Apply your changes

```bash theme={null}
# 1. Edit your env file (gitignored — only on your laptop / cluster operator's machine)
vim k8s/.env

# 2. Refresh the K8s Secret
./k8s/create-secrets.sh

# 3. Pods don't auto-reload secrets — restart them
kubectl rollout restart deployment/vibestrap -n vibestrap

# 4. Watch the rollout finish
kubectl rollout status deployment/vibestrap -n vibestrap
```

### Verify the user flows (don't trust pod status alone)

`kubectl get pods` showing `1/1 Running` only proves the process boots. It does not
prove the database is reachable, OAuth redirects work, or Stripe webhooks fire. Walk
through this list manually:

<Steps>
  <Step title="Homepage renders">
    Open `https://vibestrap.dev`. Should render. If it 500s, the most common causes are
    typoed `DATABASE_URL`, or you forgot to run `pnpm db:migrate` after
    configuring the database (see the warning at the top of the
    "Required" section).
  </Step>

  <Step title="Email signup works">
    Go to `/register`, sign up with email. Check your inbox for the welcome email.
    Validates: Resend API key + verified domain.
  </Step>

  <Step title="Google sign-in works">
    Go to `/login`, click **Continue with Google**. Should redirect, prompt, return to
    your app logged in. Validates: Google OAuth client + redirect URI.
  </Step>

  <Step title="GitHub sign-in works">
    Same drill, **Continue with GitHub**. Validates: GitHub OAuth app.
  </Step>

  <Step title="Forgot-password email arrives">
    On `/login`, click "Forgot password". Submit your email. Email should arrive within
    30 seconds. Validates: Resend further + auth wiring.
  </Step>

  <Step title="Admin dashboard reachable">
    Sign in with an email matching `ADMIN_EMAILS`. Visit `/admin`. Should load — not 403.
    Validates: `ADMIN_EMAILS` parsing.
  </Step>

  <Step title="Pricing page → Stripe Checkout">
    Visit `/pricing`. Click any checkout button. Should land on a Stripe Checkout page
    under `checkout.stripe.com`. Validates: Stripe live keys + price IDs are configured.
  </Step>

  <Step title="A real test purchase end-to-end">
    On the Stripe Checkout page, use Stripe's test card `4242 4242 4242 4242` (only works
    in test mode — for live mode use a real card and refund yourself, or run this whole
    test against a sandbox first). Confirm:

    * payment shows up in Stripe dashboard
    * webhook fires (Stripe → Webhooks → your endpoint shows a `200`)
    * a row appears in `payment` table
    * credits land in your account (visible in `/dashboard`)
    * visiting `/settings/purchases` shows the GitHub invite form for the buyer-only repo

    Validates: webhook secret + price-ID-to-credit wiring + buyer-flow gate.
  </Step>
</Steps>

If any step fails, look at the pod logs:

```bash theme={null}
kubectl logs -n vibestrap -l app=vibestrap --tail=50
```

Zod and Better Auth print clear error messages — most of the time the answer is right
there in the log.

<CardGroup cols={2}>
  <Card title="Configuration reference" icon="sliders" href="/configuration">
    Every config layer in detail.
  </Card>

  <Card title="Env reference" icon="list" href="/env-reference">
    The full list of every env var Vibestrap reads.
  </Card>

  <Card title="Stripe deep dive" icon="credit-card" href="/payments/stripe">
    More Stripe specifics: subscriptions, refunds, dispute handling.
  </Card>

  <Card title="Kubernetes deployment" icon="cloud" href="/deployment/kubernetes">
    The cluster setup, Harbor registry, secret rotation.
  </Card>
</CardGroup>

That's it. You're live. Now go tell people.
