Configure environment variables
Set the database, application URL, and optional integration credentials.
Grout requires PostgreSQL and a public application URL in production. Add optional credentials only for integrations you intend to activate.
Configure the application
Set these variables for the running application:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | Yes | Pooled PostgreSQL connection used by the application |
APP_BASE_URL | Yes in production | Public origin used in messages, embeds, Stripe redirects, and social images |
APP_DOMAIN | Serving several companies | Bare platform domain whose subdomains name a company by its slug |
DATABASE_URL_UNPOOLED | Vercel migrations | Direct PostgreSQL connection used by the Vercel build command |
Use a disposable local database while developing:
DATABASE_URL="your_local_postgres_url_here"
APP_BASE_URL="http://localhost:3000"Set APP_DOMAIN only when one deployment serves several cleaning companies. Give it the bare platform domain, such as grout.app rather than www.grout.app. Each company is then reached at its slug in front of it, so acme.grout.app serves the company whose slug is acme. A company that brings its own web address adds it under Settings, Domains, which takes precedence over its subdomain immediately. Give the deployment VERCEL_API_TOKEN and VERCEL_PROJECT_ID and the maintenance tick also attaches each address to the hosting project once its DNS answers, so the certificate is issued without an operator step; without them that attach stays a manual job.
Leave APP_DOMAIN unset for a single company. Every address the deployment answers on then serves that company, which is the behavior of an installation that has never been told about more than one.
Configure customer card payments
Set STRIPE_SECRET_KEY to use Stripe for customer card storage, charges, refunds, and tips. Without it, local development uses the mock payment gateway.
This is the platform's key, and it never charges the platform's own account. Every company takes its customers' payments on a Stripe account it owns, connected under Settings → Payments, with that company as merchant of record. A company that has not connected one cannot take card payments at all — bookings, cash, cheque and bank transfer still work.
Set STRIPE_WEBHOOK_SECRET to the signing secret of a Stripe Connect webhook endpoint pointed at /api/webhooks/stripe. It is how a company's account becomes live without anyone pressing anything; without it the endpoint refuses every delivery and the office has to press Check with Stripe on the payments page instead.
Grout does not run payroll or pay providers.
Configure message delivery
Set every variable for the channel you want to activate:
| Channel | Variables | Fallback when absent |
|---|---|---|
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM | Records the message in /admin/comms and logs it locally | |
| Text message | TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM | Records the message in /admin/comms and logs it locally |
| Push notification | NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT | Records the message in /admin/comms and logs it locally |
Partial Twilio or push configuration uses the local fallback and writes a warning. Configure each set of variables together.
Generate the push key pair with npx web-push generate-vapid-keys; VAPID_SUBJECT is a mailto: address. NEXT_PUBLIC_VAPID_PUBLIC_KEY is read by the browser, so it must be present when the application is built, not only when it runs.
Configure authentication and application services
Clerk is required — it is the only way anyone signs in:
| Service | Variables |
|---|---|
| Clerk | CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_WEBHOOK_SIGNING_SECRET |
CLERK_WEBHOOK_SIGNING_SECRET verifies the webhook at /api/webhooks/clerk,
which keeps the stored account in step when somebody changes their email address
or is deleted in Clerk. Without the webhook the application still works, but a
changed address is only noticed the next time that person signs in.
The rest are optional:
| Service | Variables |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| Vercel Blob | BLOB_READ_WRITE_TOKEN — without it the branding page refuses logo uploads, and job photos take a pasted URL instead of a file |
| Vercel domains | VERCEL_API_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID for a project a team owns — without them Settings, Domains refuses the availability search and attaches nothing, so an operator attaches each address by hand. VERCEL_DNS_A_VALUE and VERCEL_DNS_CNAME_VALUE override the record values the page shows when the hosting project uses its own |
| Sentry runtime reporting | NEXT_PUBLIC_SENTRY_DSN |
| Sentry source-map upload | SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT |
After adding Clerk credentials, create the company organization and owner membership:
npx tsx --env-file=.env.local scripts/clerk-bootstrap.tsClerk is required, not optional: it is the only way anyone signs in. The
Grout password and magic-link paths that used to serve the seeded
.test demo accounts were removed.
Set E2E_PERSONA_PASSWORD as well. The seed gives every demo persona that
password in Clerk, so somebody can sign in as one to look around. The end-to-end
tests do not use it — they sign in with a Clerk sign-in token — but they do need
CLERK_SECRET_KEY, so the suite cannot run offline.
Protect credentials
Keep .env, .env.local, and provider credentials out of Git. Copy variable names into deployment settings, but never copy secret values into documentation, commits, issues, or screenshots.