Getting Started for Owners
This guide is for the person installing and operating a Zone 5 instance. You need access to the source repository and permission to configure hosting, a database, email and your domain. If someone has already set up your studio, use Getting Started for Users.
The current deployment configuration targets Vercel, with Supabase for PostgreSQL and sign-in, Vercel Blob for files and photographs, and Resend for email. This guide follows that configuration; it does not describe a general-purpose Docker deployment.
1. Install and preview locally
Install Node.js 22.9 or later and npm. Clone the repository you were given, open its root directory and run:
npm install
cp .env.example .env.local
npm run dev
Open http://localhost:3000. Without configured services, the public site can show sample photographs; the studio workspace is not ready for real work. A working demo homepage does not mean the database, sign-in or email is configured.
Use .env.example as the reference for application environment variables. Keep real credentials in local environment files and your hosting provider's environment settings.
2. Plan production and staging
Create a production environment for the real studio and a staging environment for testing. Use separate Supabase projects, private and public Blob stores, and webhook secrets. Use staging credentials for local development.
Choose the studio's application origin, such as https://studio.example.com. Set both SITE_URL and APP_URL to that origin, without a trailing slash. The first controls public website addresses; the second controls links sent by email and authentication redirects.
The documentation site is a separate deployment on its own subdomain, such as https://docs.example.com. Its address does not belong in the application's SITE_URL or APP_URL.
3. Create the database and sign-in service
In each Supabase project:
- Disable public sign-ups and enable TOTP multi-factor authentication.
- Configure the application's origin and its
/auth/callbackaddress in the auth URL settings. Allow your staging and local callback addresses in the staging project too. - Keep the application's
appdatabase schema out of the Data API's exposed schemas. - Copy the project URL, publishable key and server-only secret key into the matching environment variables.
Configure these application variables:
| Variable | Purpose |
|---|---|
SUPABASE_URL |
The Supabase project URL. |
SUPABASE_PUBLISHABLE_KEY |
The project's publishable key. |
SUPABASE_SECRET_KEY |
Server-only access for creating invited sign-in identities. |
MIGRATION_DATABASE_URL |
Schema-owner connection using the session pooler, normally port 5432. |
DATABASE_URL |
Restricted runtime connection using the transaction pooler, normally port 6543. |
First set the migration connection and apply the schema:
npm run db:migrate
The migrations create the app_runtime role. In Supabase's SQL editor, create a runtime login with a unique password:
create role app_runtime_login login password '<unique-random-password>' in role app_runtime;
Use that login in DATABASE_URL, following your project's pooler connection format. The application should use the runtime login; migrations use the schema-owner connection.
Run the migration and owner commands against the intended environment. These commands load .env.local and, if present, .env.development.local; the latter can override the former.
4. Create the first owner
With the database migrated and the Supabase secret key configured, run:
npm run db:bootstrap-owner -- \
--email owner@example.com \
--studio "Your Studio" \
--time-zone America/New_York \
--currency USD
Replace the example values with your studio's details. Run this against a fresh studio database: the bootstrap command uses the first existing studio if one is already present. It creates the owner's sign-in identity and membership; it does not send an invitation email.
The owner signs in at the application's /auth/sign-in page using an emailed link, then enrolls an authenticator app for two-step verification. Production always requires that second step. Until Resend is configured, staff sign-in falls back to Supabase's email service, so its email delivery and redirect configuration must work for that first sign-in.
5. Connect email, files and background work
Email: verify a sending domain in Resend and set RESEND_API_KEY and EMAIL_FROM. For marketing and inbound replies, the key needs full access. Register the application's /api/webhooks/resend endpoint and set its signing secret as RESEND_WEBHOOK_SECRET.
Enable delivery events for sent, delivered, delayed, bounced, complained and failed email. For client email replies, also enable email.received, configure a dedicated receiving subdomain in Resend and its DNS records, and set EMAIL_REPLY_DOMAIN. Marketing unsubscribe synchronization also uses contact.created, contact.updated and contact.deleted events.
Files and photographs: create a private Blob store for originals and private documents, and a public Blob store for published images. Connect production stores only to Production. Use separate stores for Preview and Development.
| Store | Production prefix | Preview / Development prefix |
|---|---|---|
| Private | BLOB |
BLOB_STAGING |
| Public | PUBLIC_BLOB |
PUBLIC_BLOB_STAGING |
Browser photo uploads need the private store's read-write token, including in production. Outside production, the app prefers the staging tokens when present. Configure PUBLIC_BLOB_READ_WRITE_TOKEN for production and PUBLIC_BLOB_STAGING_READ_WRITE_TOKEN for staging's public images.
Background work: generate a random CRON_SECRET, such as with openssl rand -hex 32. The repository's vercel.json schedules /api/cron/jobs every five minutes. Choose a hosting plan that supports this schedule. This job processes retries, email, reminders and other queued work.
Connection encryption: generate INTEGRATION_ENCRYPTION_KEY with openssl rand -base64 32 before connecting services that store credentials. Keep it stable and backed up; changing it requires those services to be reconnected.
6. Deploy the application
- Import the application repository into a Vercel project with the repository root as its root directory. This is the studio deployment, separate from the documentation project.
- Configure Production and Preview variables separately, including the database, auth, email, storage and origin values above.
- Deploy using the committed application
vercel.json. Its build command applies migrations before building Next.js. Production requiresMIGRATION_DATABASE_URL; previews skip migrations unlessRUN_MIGRATIONS=1is set. Migrate staging explicitly when previews skip them. - Attach the studio's domain, configure the DNS records supplied by your host and check HTTPS. Match
SITE_URL,APP_URL, authentication callbacks and webhook addresses to this origin. - For a test site, set
SEARCH_INDEXING=off. Remove that override when the production site is ready to be indexed, then redeploy.
By default, production shows an empty portfolio until you select library albums. Set PORTFOLIO_SOURCE=mock only if you deliberately want demo imagery.
7. Enable the services your studio needs
| Capability | Additional setup |
|---|---|
| Invoice card payments | Configure Stripe and its signed /api/webhooks/stripe endpoint. For Connect, configure the platform's STRIPE_SECRET_KEY, STRIPE_CONNECT_CLIENT_ID and STRIPE_CONNECT_WEBHOOK_SECRET, then connect the studio's account in Settings → Connections. A single-studio deployment can use its own key and STRIPE_WEBHOOK_SECRET without Connect. |
| Google Calendar | Configure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET and the application's /api/integrations/google/callback redirect in Google Cloud. Connect the account and select calendars in Settings → Calendar. |
| Print sales | Connect Stripe and Prodigi in Settings → Connections, create print sizes and publish pieces. Paid print orders currently require manual placement with Prodigi. |
| Original photo backups | Configure R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY and R2_BACKUP_BUCKET, then enable backup in Photos. |
Without Stripe, you can still issue invoices and record payments received elsewhere. Keep the shop closed until payment and print-order handling have been verified.
8. Verify before inviting clients
- Visit
/adminwhile signed out; it should require sign-in. Sign in as the owner and complete two-step verification. - Check Settings → Connections and send yourself a test email from Settings.
- Confirm the cron job runs and queued work completes. Check failed tasks in Connections.
- Upload a photograph, wait for processing, assign a portfolio album and verify the public image.
- Use a contact you control to create a project, send an estimate, accept it from the client page, sign a contract, issue an invoice and create a protected gallery.
- Verify Stripe payment and refund behavior in the intended test environment before using live payments. Test email replies if you configured them.
- Confirm client pages and galleries require the expected access and cannot open another client's project.
- Establish database backups, photo-original backups and a restore procedure. Test a restore into an isolated environment before relying on it.
Now invite photographers through Settings → Team and give them Getting Started for Users. Continue with Team & settings, Website & portfolio and Shop.