TideMeter
Skip to Content
Configuration

Configuration

TideMeter is configured through environment variables. This page documents all available options.

Database Migrations

Migrations run automatically when a new image starts — there is no separate migrate command. This applies to Docker, docker compose, and Kubernetes alike, so upgrading is just docker pull (or rolling a new image tag) and restarting the container.

What happens on boot:

  1. PayloadCMS schema is applied:
    • Development (NODE_ENV != "production"): Drizzle “push” mode diffs the collections against the database and applies DDL automatically. Fast iteration, no migration files required.
    • Production (NODE_ENV == "production"): versioned migrations under apps/web/src/migrations/ are applied via payload.db.migrate() and tracked in the payload_migrations table. Already-applied migrations are skipped on subsequent boots.
  2. The analytics package applies any pending SQL migrations from packages/analytics/drizzle/ against ANALYTICS_DATABASE_URL (or ClickHouse migrations from packages/analytics/clickhouse/ when ANALYTICS_DB_TYPE=clickhouse). A Postgres advisory lock serializes concurrent runners, so rolling deploys with multiple replicas are safe.
  3. If DEMO_MODE=true, demo data is seeded (idempotent — see Demo Mode below).

If any step fails, the pod fails its readiness probe and Kubernetes halts the rollout — the previous image keeps serving traffic. There is no silent partial-upgrade state.

Init is triggered by any route that imports the Payload config — including /api/health, which is hit by the readiness/startup probes. The database user only needs CREATE privileges; no manual SQL is required.

Overrides

VariableEffect
PAYLOAD_DB_PUSHForce push mode (true) or migration mode (false), regardless of NODE_ENV. Useful when upgrading an old deployment whose schema was originally created with push and has no payload_migrations rows: set true for a single boot to let push reconcile, then unset it.

Adding a schema change (for contributors)

In dev (NODE_ENV=development), changes to Payload collections are picked up instantly via push. To ship the change in a production image:

cd apps/web && pnpm exec payload migrate:create

Commit the generated file under apps/web/src/migrations/ together with the updated index.ts. For analytics tables, add a numbered .sql file under packages/analytics/drizzle/ and update the Drizzle schema in packages/analytics/src/schema/tables.ts.

Environment Variables

Required

VariableDescriptionExample
DATABASE_URLPostgreSQL connection string for PayloadCMSpostgresql://user:pass@localhost:5480/tidemeter
PAYLOAD_SECRETSecret for PayloadCMS auth (min 32 chars). Required in production — app will refuse to start without it.Any strong random string
SESSION_SALT_SECRETSecret for hashing visitor IDs (rotated daily). Required in production.Any strong random string

Application

VariableDefaultDescription
NEXT_PUBLIC_APP_URLhttp://localhost:3700Public URL of your TideMeter instance
NODE_ENVdevelopmentdevelopment or production

Analytics Database

VariableDefaultDescription
ANALYTICS_DB_TYPEpostgresqlAnalytics storage backend: postgresql, clickhouse, or sqlite
ANALYTICS_DATABASE_URLSame as DATABASE_URLPostgreSQL connection string for analytics
CLICKHOUSE_URLhttp://localhost:8123ClickHouse HTTP endpoint
CLICKHOUSE_DATABASEtidemeter_analyticsClickHouse database name
CLICKHOUSE_USERdefaultClickHouse username
CLICKHOUSE_PASSWORDClickHouse password (optional)
ANALYTICS_SQLITE_PATH./data/analytics.dbSQLite file path (when using SQLite)

Optional

VariableDefaultDescription
GEOIP_DB_PATHPath to MaxMind GeoLite2-City.mmdb for geolocation
DEMO_MODEfalseWhen true, seeds a demo user, website, ~1500 events, and example funnels on boot

Email (optional)

TideMeter uses email only for password reset and account verification. If you don’t need those flows (single-user or private deployment), skip this entirely — Payload will print a one-time WARN: No email adapter provided on startup and write any outgoing email to stdout. Login, first-user creation (/admin/create-first-user) and password change from /admin/account all work without email.

Two backends are supported. They are picked in this order, so setting both is unnecessary:

  1. Resend (RESEND_API_KEY set) — Resend’s HTTP API via @payloadcms/email-resend. No SMTP port required, lightweight, recommended if you don’t already run a mail server.
  2. SMTP / Nodemailer (SMTP_HOST set) — works with any SMTP provider: your own Postfix, Gmail, SendGrid, Mailgun, Postmark, AWS SES, etc.

Option 1 — Resend

VariableDefaultDescription
RESEND_API_KEYResend API key (re_…). Setting this enables Resend.
SMTP_FROM_ADDRESSno-reply@localhostFrom address (must be a verified Resend sender / domain)
SMTP_FROM_NAMETideMeterFrom name

Option 2 — SMTP (Nodemailer)

VariableDefaultDescription
SMTP_HOSTSMTP server hostname. Setting this enables SMTP delivery.
SMTP_PORT587SMTP server port
SMTP_USERSMTP username (see provider notes below)
SMTP_PASSWORDSMTP password / API key
SMTP_SECUREauto (true on 465)true for implicit TLS (port 465). Otherwise STARTTLS is auto-negotiated.
SMTP_FROM_ADDRESSno-reply@localhostFrom address for outgoing email
SMTP_FROM_NAMETideMeterFrom name for outgoing email

Provider-specific SMTP_USER conventions:

ProviderSMTP_USERSMTP_PASSWORD
SendGridliteral string apikeyyour SendGrid API key
Mailgunpostmaster@<your-domain>the Mailgun SMTP password
AWS SESSES SMTP username (generated in the SES console)SES SMTP password (not your AWS key)
Gmailfull Gmail addressa Google app password
Postmarkyour Postmark server tokenthe same server token
Postfixlocal SASL user (or omit both for unauth relays)local SASL password

Buffer Settings

The event buffer can be tuned via code constants in apps/web/src/lib/ingestion/buffer.ts:

SettingDefaultDescription
FLUSH_SIZE100Max events before flush
FLUSH_INTERVAL5000Max milliseconds between flushes
MAX_BUFFER_SIZE10000Max buffered events (prevents OOM under DB outage)

Example .env File

# Database (PostgreSQL for PayloadCMS) DATABASE_URL=postgresql://postgres:postgres@localhost:5480/tidemeter # PayloadCMS PAYLOAD_SECRET=a-very-long-and-random-secret-key-32-chars-minimum # Privacy SESSION_SALT_SECRET=another-random-secret-for-visitor-hashing # Application NEXT_PUBLIC_APP_URL=http://localhost:3700 NODE_ENV=development # Analytics database (defaults to PostgreSQL) ANALYTICS_DB_TYPE=postgresql # ANALYTICS_DATABASE_URL=postgresql://postgres:postgres@localhost:5480/tidemeter # For ClickHouse analytics # ANALYTICS_DB_TYPE=clickhouse # CLICKHOUSE_URL=http://localhost:8124 # CLICKHOUSE_DATABASE=tidemeter_analytics # CLICKHOUSE_USER=default # CLICKHOUSE_PASSWORD= # For SQLite analytics # ANALYTICS_DB_TYPE=sqlite # ANALYTICS_SQLITE_PATH=./data/analytics.db # Optional: GeoIP # GEOIP_DB_PATH=/path/to/GeoLite2-City.mmdb # Optional: Demo mode (seeds [email protected] / demodemo + sample data) # DEMO_MODE=true

Demo Mode

Set DEMO_MODE=true to start TideMeter pre-populated with a demo user and sample analytics data. Useful for evaluation, screenshots, and public sandboxes.

On first startup the container will:

  • Create the demo user [email protected] with password demodemo
  • Create a sample website (demo.example.com)
  • Generate ~1500 analytics events spanning the last 90 days
  • Create three example funnels

Seeding is idempotent — it only runs when the demo data is missing, so restarts and upgrades are safe. To re-seed, wipe the database volume.

# Compose overlay (PostgreSQL + DEMO_MODE=true) docker compose -f docker/docker-compose.yml -f docker/docker-compose.demo.yml up -d # Or with `docker run` docker run -d -p 3700:3000 \ -e DATABASE_URL="postgresql://..." \ -e PAYLOAD_SECRET="..." \ -e SESSION_SALT_SECRET="..." \ -e DEMO_MODE=true \ tidemeter/tidemeter:latest

The public instance at demo.tidemeter.com  runs with this flag enabled.

Docker Configuration

PostgreSQL Only

docker compose -f docker/docker-compose.yml up -d

Default ports:

  • Application: 3700
  • PostgreSQL: 5480

PostgreSQL + ClickHouse

docker compose -f docker/docker-compose.yml -f docker/docker-compose.ch.yml up -d

Default ports:

  • Application: 3700
  • PostgreSQL: 5480
  • ClickHouse HTTP: 8124
  • ClickHouse Native: 9001

PayloadCMS Configuration

The PayloadCMS configuration is in apps/web/src/payload.config.ts. Key settings:

  • Collections: Users, Teams, TeamMembers, Websites, ApiKeys, Funnels
  • Auth: Cookie-based authentication with payload-token HTTP-only cookie
  • Admin: Available at /admin
  • REST API: Available at /api/{collection-slug}
  • Editor: Lexical rich-text editor

Tailwind CSS 4

TideMeter uses Tailwind CSS 4 with the CSS-first configuration approach:

  • Theme is defined in apps/web/src/app/globals.css using @theme
  • No tailwind.config.js file
  • PostCSS plugin: @tailwindcss/postcss

TypeScript

TypeScript strict mode is enabled across all packages via @tidemeter/tsconfig. Key conventions:

  • Target: ES2022, module: ESNext, moduleResolution: bundler
  • No any types (unless absolutely necessary)
  • Named exports for all modules
  • No .js extensions in imports (Turbopack doesn’t resolve them)
  • noUncheckedIndexedAccess enabled