Skip to content

Operations and hosting

The reference deployment uses GitHub Actions, Docker, Caddy, external production and demo MySQL databases, and disposable per-PR MySQL databases.

Repository operators should follow the concise hosting runbook for DNS, runtime configuration, delivery, backups, and recovery.

GitHub environments match public hostnames: booking.title.dk, demo.booking.title.dk, and pr-<number>.booking.title.dk. A deployment status records a release attempt, not current route health; a failed blue/green release leaves the previous verified containers active.

Relational integrity

MySQL migrations enforce tenant-safe composite foreign keys for domain records and the group_members, event_teachers, product_events, and instance_groups join tables. The API record JSON remains for compatibility; the join tables are rebuilt transactionally from the corresponding domain arrays and are the indexed query boundary for mutable relationships.

Active or pending bookings are unique per attendee and event instance. Payment provider references and lifecycle reminder idempotency keys are unique within a tenant. Season-pass occurrence IDs remain JSON because they are immutable purchase snapshots, not mutable relationships.

Admin access decision

Admin authentication is an operational environment concern, not a feature-spec concern. The feature specs describe the product contract—email magic links, sessions, account selection, and authorization—while HOSTING.md is the authoritative runbook for deployment secrets, provider wiring, preview databases, and operator access. This guide summarizes that decision without duplicating the secret-bearing setup.

  • Production at /admin/ requires a delivered email magic link. It has DEMO_AUTH_ENABLED=false, so no email-free login button is expected.
  • Same-repository PR previews have isolated MySQL databases, seeded demo data, DEMO_AUTH_ENABLED=true, and mocked email/payment boundaries. Their supported shortcut is Open demo studio.
  • Local development uses the same demo configuration when the command in the repository README is used.
  • /demo/ is the attendee-facing demo and is separate from /admin/.

The reference VPS has two application containers: the production container serves https://booking.title.dk, while a separate booking-demo-<release> container serves the full demo environment at https://demo.booking.title.dk. The demo has its own database and seeded Harbour Yoga data. Production does not route or expose /demo; the attendee demo is available only at https://demo.booking.title.dk/demo/.

The demo hostname exposes the same route families as production: /admin, /api, /api/docs, /api/openapi.json, and /docs; its /demo/ path is the attendee-facing reference consumer. It uses mocked email and payment adapters, while production must use real providers.

The application selects the root index from the request hostname: the demo host shows a demo index linking to /demo/, while the production host shows the marketing placeholder. Production does not expose /demo; that route returns not found there.

The production application can be database-ready while email is still misconfigured. Confirm both public endpoints and the private environment file as described in HOSTING.md.

Release behavior

  • CI must succeed before deployment.
  • CI publishes a run summary showing whether the lightweight or full path was selected. The Verification result job is the single normalized deployment gate; the other path is intentionally skipped.
  • Pull-request CI runs from the pull_request event; pushes run CI only for main. This keeps each PR revision on one verification path and avoids duplicate downstream feature-spec report uploads.
  • CI also supports manual dispatch for rerunning the current branch when a pull-request event was not enqueued by GitHub.
  • In-progress PR verification is canceled when a newer revision is pushed. Feature-spec diff-report jobs are also serialized per PR, with obsolete jobs canceled before they can publish another report.
  • The report publication transfer itself is owned by the reusable feature-spec-md workflow. Repository-side workflow controls prevent duplicate and stale uploads; transfer-level parallelism or timeout changes belong in that upstream library.
  • One immutable image is built for each successful main verification, then production and demo back up, migrate, verify, and switch independently in parallel jobs.
  • Each previous application remains active until its own replacement passes database-backed readiness.
  • Replacement failures are reported by phase with bounded, redacted logs for the affected candidate; only that failed candidate is removed, and the other environment continues independently.
  • PRs from the repository receive pr-<number> environments and isolated schemas.
  • Closing a same-repository PR removes its route, container, user, credentials, and database, whether the PR was merged or abandoned.
  • Manual production and preview deployments still require a successful CI run for the selected commit.

Schema migrations must follow expand/contract rules. A container switch is non-disruptive, but destructive or locking SQL can still interrupt the old release.

Relational persistence migration

Drizzle migrations create the authoritative tenant-scoped tables and readiness checks connectivity and the migration-owned schema; it never creates tables. Back up the database before applying migrations and verify the restored schema in a disposable database during release preparation.

Booking SaaS implementation documentation