Skip to main content
tickward runs as a Next.js app with PostgreSQL for durable data and a Redis-compatible REST endpoint for rate limiting.

Docker Compose

git clone https://github.com/CorgiCorner/tickward.git tickward
cd tickward
cp .env.example .env
docker compose up --build
Open http://localhost:3000. The Compose stack starts the app, PostgreSQL, Redis, and an Upstash-compatible HTTP proxy for local rate limiting.

Local development

Use Node 22.
cp .env.example .env
npm ci
npm run dev
For development outside Docker, run PostgreSQL, apply migrations, and run a Redis-compatible REST endpoint:
npm run db:migrate:deploy
Use host-reachable URLs in .env when you run services outside Compose.

Configuration

VariableRequiredDescription
DATABASE_URLYesRuntime PostgreSQL connection used by the app.
DIRECT_URLYesDirect PostgreSQL connection used by Prisma migrations.
UPSTASH_REDIS_REST_URLYesUpstash-compatible Redis REST endpoint for rate limiting.
UPSTASH_REDIS_REST_TOKENYesToken accepted by the Redis REST endpoint.
SITE_URLYesPublic app URL used for metadata, sitemap, robots.txt, and generated social images.
NEXT_PUBLIC_TICKWARD_MAX_TIMERSNoPublic timer limit embedded into the browser bundle at build time.
NEXT_PUBLIC_TICKWARD_MAX_TIMERS_PER_SPACENoPublic per-space active timer limit embedded into the browser bundle at build time.
NEXT_PUBLIC_TICKWARD_MAX_SPACESNoPublic space limit embedded into the browser bundle at build time.
UNSPLASH_ACCESS_KEYNoEnables Unsplash image search.
NEXT_PUBLIC_PLAUSIBLE_URLNoEnables the optional Plausible Analytics script.
NEXT_PUBLIC_PLAUSIBLE_DOMAINNoPlausible domain override. Defaults to the SITE_URL hostname.
BETTER_AUTH_URLNoEnables account sign-in when set with BETTER_AUTH_SECRET.
BETTER_AUTH_SECRETNoSecret used by Better Auth.
RESEND_API_KEYNoEnables email OTP delivery through Resend.
RESEND_FROMNoSender address used for Resend emails.
RESEND_REPLY_TONoOptional reply-to address for Resend emails.
WEB_PUSH_VAPID_PUBLIC_KEYNoEnables the Web Push public-key endpoint when set with the private key.
WEB_PUSH_VAPID_PRIVATE_KEYNoPrivate VAPID key for push-capable deployments.
WEB_PUSH_VAPID_SUBJECTNoOptional VAPID subject.
TICKWARD_MCP_REMOTE_URLNoRemote MCP endpoint shown in account settings when configured.
TICKWARD_ENVIRONMENTNoEnvironment label included in webhook payloads.
TICKWARD_SCHEDULER_SECRETNoBearer secret for /api/internal/scheduler/tick when webhooks are enabled.
TICKWARD_WEBHOOK_ALLOW_PRIVATE_NETWORKSNoSet to true only when a self-hosted deployment must call trusted internal URLs.
SEED_ADMIN_EMAILNoEmail used by seed scripts to create or promote an admin account.
SEED_ADMIN_NAMENoOptional display name for the seeded admin account.
SEED_ADMIN_IDNoOptional custom user id for the seeded admin account.
SEED_DEMO_PROJECT_IDNoOptional project id for the PostgreSQL demo seed.
SEED_DEMO_RESTORE_KEYNoRestore key for the PostgreSQL demo project seed.
SEED_DEMO_BASE_DATENoOptional base date for deterministic screenshot data.
SITE_URL and NEXT_PUBLIC_* values affect build-time metadata or browser bundles. Rebuild the app after changing them. Webhook delivery needs a scheduler caller in production. See Webhooks and scheduler for the Cloudflare Cron, EventBridge, and VPS cron options.

Accounts and email

Accounts are optional. Without account configuration, tickward still works with anonymous projects and restore keys. Restore keys are project access tokens for anonymous projects. Treat them like passwords. When a signed-in user claims a project, tickward saves it to the account and revokes the old restore-key access. Timer share links are separate read-only public links. They expose the linked timer view, not the full project or its restore key. Email-code sign-in is enabled when Better Auth and Resend are configured:
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=change-me
RESEND_API_KEY=placeholder
RESEND_FROM="tickward <no-reply@example.com>"
RESEND_REPLY_TO=contact@example.com
OTP send requests are rate limited with Redis: one request per minute per normalized email and three requests per minute per IP. To smoke-test the request path against a running app:
SMOKE_BASE_URL=http://localhost:3000 SMOKE_EMAIL_TO=you@example.com npm run smoke:email-otp
This smoke test verifies that the app accepts and dispatches an OTP request. It does not read an inbox or prove mailbox delivery.

Database migrations

Managed deployments must apply Prisma migrations before building code that depends on new tables or columns:
npm run db:migrate:deploy

Demo data

Run Storybook locally with:
npm run storybook
To load screenshot-friendly demo timers and spaces, start the app and run:
npm run demo:seed
For a PostgreSQL-backed bootstrap seed, configure DATABASE_URL and SEED_ADMIN_EMAIL, apply migrations, then run:
npm run db:migrate:deploy
npm run db:seed
The database seed creates or promotes the admin account and idempotently upserts the demo project in PostgreSQL.

Advanced database roles

Most installs can skip this. If migrations and app runtime use different PostgreSQL roles, run this after migrations:
npm run db:grant-runtime
Set TICKWARD_DATABASE_RUNTIME_ROLE or TICKWARD_DATABASE_SCHEMA only when the script cannot infer them from DATABASE_URL.

Docs

The Mintlify entrypoint is the root docs.json file on the public repo’s main branch. The docs/site directory mirrors the same content for app-local checks and packaged docs assets. If you host docs outside the app, set TICKWARD_DOCS_ORIGIN to the external docs origin and the app will redirect /docs there. Useful local docs checks:
npx mintlify@latest validate
npx mintlify@latest broken-links