Deployment Guide

Tours is one Next.js application, so deploying it is the same everywhere: build it, set your environment variables, point it at PostgreSQL, connect object storage, and serve it. There is no separate API process and no background daemon to manage. This guide covers the production build, environment configuration, the database, file storage, scheduled jobs, and license activation.

Choosing where to host? See the Hosting Guide for a side-by-side of Vercel, a Node VPS, and other options.

Building for Production

From the project root:

Terminal
npm install        # install dependencies
npm run build      # produce the optimized production build (next build)
npm run start      # serve it (next start — defaults to port 3000)

On a managed platform such as Vercel, the build and start commands are configured for you by the bundled vercel.json.

The database client is lazy-initialisedDATABASE_URL is only read at request time, not at build time. This means next build succeeds even if the database is unreachable during the build step. Just make sure DATABASE_URL is present at runtime.

Environment Variables in Production

Set the same variables documented in the Installation Guide in your host's environment configuration. The essentials for a live deployment:

ENV
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"
JWT_SECRET="a-long-random-string"
SECRET_KEY="a-long-random-string"      # encrypts stored integration secrets
NEXT_PUBLIC_SITE_URL="https://your-domain.com"
CRON_SECRET="a-random-string"          # protects /api/v1/cron

Optional, depending on what you use: JWT_EXPIRES_IN, LICENSE_SERVER_URL / LICENSE_PUBLIC_KEY, ADMIN_EMAIL / ADMIN_PASSWORD, NOTIFY_EMAIL, API_DOCS_USER / API_DOCS_PASS, the R2_* storage fallbacks, PEXELS_API_KEY, and WORLDVECTORLOGO_API_KEY.

Generate strong secrets. Use openssl rand -base64 48 for JWT_SECRET, SECRET_KEY, and CRON_SECRET. Keep them stable across deploys — rotating JWT_SECRET invalidates active sessions, and changing SECRET_KEY after integration credentials are saved makes those encrypted values undecryptable. Set SECRET_KEY once, before connecting integrations.

Database (Neon / PostgreSQL)

Tours talks to PostgreSQL through the @neondatabase/serverless driver using raw, parameterized SQL. The recommended database is Neon because the serverless driver is built for it and works over HTTP, which suits serverless hosts.

  • Create a Neon project and copy its connection string into DATABASE_URL. In production, use the Neon pooler URL (...-pooler...neon.tech?sslmode=require) — pooled connections are right for serverless.
  • The schema is created idempotently — by the /install wizard, by npm run db:init, or by the ensure*() helpers at runtime. There is no migration framework to run on each deploy.
  • Any Neon-compatible PostgreSQL endpoint that the serverless driver accepts will work.

Migrating local → Neon. For a pg_dump / pg_restore migration, use the direct (unpooled) endpoint (the pooler host minus -pooler) — the pooler stalls large single-transaction restores. Same database, same credentials.

File Storage (Cloudflare R2)

Uploads — tour media, images, and documents — go to object storage, never to local disk in production.

Critical for serverless hosts: on Vercel the application filesystem is read-only at runtime. The app detects this automatically (the VERCEL environment variable is set). Uploads must be sent to R2 (or another S3-compatible bucket); they cannot be written to the local filesystem. Configure storage before going live, or uploads will fail.

Tours's storage layer supports any S3-compatible provider — Cloudflare R2, DigitalOcean Spaces, Amazon S3, Google Cloud Storage — through a single S3 client. Configure it one of two ways:

  1. Recommended — Admin → Settings → Integrations → Storage. Add your provider connection (endpoint, access key, secret, bucket, public URL) and mark it Active + Default. Credentials are encrypted in the integration_connections table with SECRET_KEY — they are not read from env and they come across in a DB dump.
  2. Fallback — environment variables. Set R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_DEFAULT_BUCKET, and R2_PUBLIC_URL. These are used only when no active storage connection exists in the database.

To set up Cloudflare R2:

  1. Create an R2 bucket and a public access URL (custom domain or r2.dev).
  2. Generate an R2 API token with read/write access.
  3. Paste the account-specific endpoint, access key, secret, bucket name, and public URL into the admin (or the R2_* env vars).

Troubleshooting uploads. An upload failing with SignatureDoesNotMatch means the stored storage secret key is wrong (or the token was rotated) — it's config, not code. Re-enter the credentials in Settings → Storage. The upload route surfaces the underlying storage error rather than a generic 500, so this is diagnosable.

Scheduled Jobs (Cron)

Background tasks — payouts, reminders, cleanup — run via a single HTTP endpoint, /api/v1/cron, protected by CRON_SECRET.

  • On Vercel: the bundled vercel.json declares the cron for you, so Vercel calls it hourly automatically — no extra action needed:

    JSON
    { "crons": [{ "path": "/api/v1/cron", "schedule": "0 * * * *" }] }
    
  • Self-hosted: add a crontab entry that curls the endpoint hourly. The install wizard's finish step prints the exact line, for example:

    Terminal
    0 * * * * curl -fsS "https://your-domain.com/api/v1/cron?key=YOUR_CRON_SECRET" >/dev/null 2>&1
    

License Activation on the Live Domain

Tours's license is tied to your production domain.

  • On a local or LAN host (localhost, 127.0.0.1, *.localhost, *.test, and private 10.x / 192.168.x / 172.16–31.x ranges) the app runs in dev mode — the license check is bypassed and all entitlements are unlocked, so you can build and test freely.
  • On your live domain, activate your purchase code. Do this during the install wizard's license step, or later from the admin. Activation verifies against LICENSE_SERVER_URL (default https://creative-cape.com) using an RS256 public key — a default is baked in; override with LICENSE_PUBLIC_KEY only if instructed.

Because dev hosts bypass licensing, always verify entitlement-gated add-ons on your real domain before launch. See the License Guide for details.

Deployment Checklist

  • DATABASE_URL set and reachable at runtime
  • JWT_SECRET and SECRET_KEY set to strong, stable values
  • NEXT_PUBLIC_SITE_URL set to your live URL
  • Storage (R2 / S3) configured — required on read-only hosts
  • CRON_SECRET set and the hourly cron wired up
  • License activated on the live domain
  • Admin password changed from any seed value

© CreativeCape Solutions · creative-cape.com · support@creative-cape.com