Skip to main content
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:
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.

The 3 config layers

vibestrap’s configuration lives in three places, each for a reason:
LayerLives inWhat it controls
Brand & productsrc/config/site.tsName, copy, pricing display, social links
Translationsmessages/{en,zh}.jsonAll user-facing strings
Secrets & togglesk8s/.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 and Customization.

Where you’ll edit values

The flow on a live cluster:
# 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.
1

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 — serverless Postgres, generous free tier, indie-friendly. (Alternatives: Supabase, Railway, RDS — anything that speaks Postgres 15+.)
1

Create the project

  1. Sign up at 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
2

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
3

Paste into k8s/.env

DATABASE_URL=postgres://neondb_owner:[email protected]/neondb?sslmode=require
VariableExample
DATABASE_URLpostgres://neondb_owner:[email protected]/neondb?sslmode=require
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:
# 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.
2

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.
1

Generate

openssl rand -base64 32
Output is 44 chars and ends with =.
2

Paste into k8s/.env

BETTER_AUTH_SECRET=S8sFO3lhBaqfDD0p308BqjBNQ6TssHA9i2p4JoMYSb4=
VariableExample
BETTER_AUTH_SECRETS8sFO3lhBaqfDD0p308BqjBNQ6TssHA9i2p4JoMYSb4=
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.
3

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.
VariableExample
NEXT_PUBLIC_APP_URLhttps://vibestrap.dev
Must be the production https URL. Don’t include a trailing slash. Don’t use http://.
4

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.
VariableExample
BETTER_AUTH_URLhttps://vibestrap.dev
After these four, your pod boots and the homepage renders. Now make it actually useful. 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 — free tier covers 100 emails per day, 3,000 per month. Plenty for early launch.
1

Sign up + verify your own email

Standard signup flow.
2

Add and verify your domain

  1. Left sidebar → DomainsAdd 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
3

Create an API key

  1. Left sidebar → API KeysCreate API Key
  2. Permission: “Sending access” is enough
  3. Copy the re_xxx key — it’s only shown once
VariableWhere it comes fromExample
RESEND_API_KEYResend → API Keysre_a1b2c3d4...
RESEND_FROM_EMAILAny email at your verified domainLarry from Vibestrap <[email protected]>
RESEND_REPLY_TO_EMAILInbox you actually read[email protected]
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.

Admin access

You’ll want to reach /admin yourself.
VariableExample
ADMIN_EMAILS[email protected],[email protected]
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
1

Pick or create a Google Cloud project

Top-left dropdown → New Project, or pick an existing one.
2

Configure the OAuth consent screen

  1. Left menu → APIs & ServicesOAuth 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)
3

Create the OAuth client

  1. Left menu → APIs & ServicesCredentials
  2. + Create CredentialsOAuth 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
VariableExample
GOOGLE_CLIENT_ID1234567890-abc...apps.googleusercontent.com
GOOGLE_CLIENT_SECRETGOCSPX-abcdef...
NEXT_PUBLIC_GOOGLE_CLIENT_IDsame as GOOGLE_CLIENT_ID (mirror, used for client-side One-Tap)
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.

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/developersOAuth AppsNew OAuth App
1

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
2

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)
VariableExample
GITHUB_CLIENT_IDIv1.abc123...
GITHUB_CLIENT_SECRETghs_abcdef...

Stripe

Why: take money. Without this, the entire pricing page is decorative. Where to set up: dashboard.stripe.com
1

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.
2

Grab the API keys

  1. Developers → API keys
  2. Copy Secret key (sk_live_...)
  3. Copy Publishable key (pk_live_...)
3

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
4

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_...
VariableExample
STRIPE_SECRET_KEYsk_live_abc...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYpk_live_abc...
STRIPE_WEBHOOK_SECRETwhsec_abc...
STRIPE_PRICE_VIBESTRAP_STANDARDprice_abc... (your standard SKU)
STRIPE_PRICE_VIBESTRAP_PROMOprice_abc... (optional, for sale price)
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.
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
1

Add the property

  1. Add propertyURL prefix → enter https://vibestrap.dev
  2. Choose HTML tag verification method
2

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....
VariableExample
GOOGLE_SITE_VERIFICATIONRj7vQ... (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
1

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
VariableExample
NEXT_PUBLIC_CLARITY_PROJECT_IDabc123xyz0

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
1

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
VariableExample
NEXT_PUBLIC_POSTHOG_KEYphc_abc...
NEXT_PUBLIC_POSTHOG_HOSThttps://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”.
FeatureSet up whenVariablesWhere to sign up
Bing site verificationYou want Bing/DuckDuckGo trafficBING_SITE_VERIFICATIONbing.com/webmasters
Yandex verificationTargeting Russian-speaking usersYANDEX_VERIFICATIONwebmaster.yandex.com
Resend Audience newsletterCollecting email subs via ResendRESEND_AUDIENCE_IDresend.com → Audiences
Beehiiv newsletterYou already use BeehiivBEEHIIV_API_KEY, BEEHIIV_PUBLICATION_IDbeehiiv.com
Crisp chatWant live chat widgetNEXT_PUBLIC_CRISP_WEBSITE_IDcrisp.chat
Tawk.to chatFree Crisp alternativeNEXT_PUBLIC_TAWK_PROPERTY_ID, NEXT_PUBLIC_TAWK_WIDGET_IDtawk.to
IntercomBigger budget, better CRMNEXT_PUBLIC_INTERCOM_APP_IDintercom.com
ChatwootSelf-hosted supportNEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN, NEXT_PUBLIC_CHATWOOT_BASE_URLchatwoot.com
Cloudflare TurnstileAnti-bot on signup/checkoutNEXT_PUBLIC_TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEYcloudflare.com
Affonso affiliatesAffiliate programNEXT_PUBLIC_AFFONSO_IDaffonso.io
Rewardful affiliatesStripe-native affiliatesNEXT_PUBLIC_REWARDFUL_API_KEYrewardful.com
Tolt affiliatesCheaper alternativeNEXT_PUBLIC_TOLT_REFERRAL_KEYtolt.io
Creem (alt to Stripe)Stripe unavailable in your regionCREEM_API_KEY, CREEM_WEBHOOK_SECRET, CREEM_PRODUCT_IDcreem.io
AI providers (Phase 2)Your product uses AIAI_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY, REPLICATE_API_TOKEN, FAL_KEYprovider websites
S3 / R2 storageUser uploads, generated imagesS3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYAWS S3 or Cloudflare R2
License download URLWhere buyers fetch the zipLICENSE_DOWNLOAD_URLyour private GitHub release / S3 link
For long-form setup of any of these, the per-feature pages have full walkthroughs: Customer service, Affiliate, Newsletter, Turnstile, License delivery, AI providers.

Apply + verify

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

Apply your changes

# 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:
1

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).
2

Email signup works

Go to /register, sign up with email. Check your inbox for the welcome email. Validates: Resend API key + verified domain.
3

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.
4

GitHub sign-in works

Same drill, Continue with GitHub. Validates: GitHub OAuth app.
5

Forgot-password email arrives

On /login, click “Forgot password”. Submit your email. Email should arrive within 30 seconds. Validates: Resend further + auth wiring.
6

Admin dashboard reachable

Sign in with an email matching ADMIN_EMAILS. Visit /admin. Should load — not 403. Validates: ADMIN_EMAILS parsing.
7

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.
8

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
  • a license appears in /admin/licenses (or /settings/licenses for your account)
Validates: webhook secret + price-ID-to-credit wiring + license issuance.
If any step fails, look at the pod logs:
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.

Configuration reference

Every config layer in detail.

Env reference

The full list of every env var vibestrap reads.

Stripe deep dive

More Stripe specifics: subscriptions, refunds, dispute handling.

Kubernetes deployment

The cluster setup, Harbor registry, secret rotation.
That’s it. You’re live. Now go tell people.