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.

The public landing page keeps the deployment timestamp in ISO form for machine readers and formats the visible date and time in each visitor's local language and time zone.

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.

Email log

Every environment includes an Email log destination in administration. It shows transactional delivery attempts generated by that deployment only. The list and detail views are administrator-authenticated and provide status, delivery method, provider, and redacted HTML and plain-text renderings. Magic-link tokens, authorization headers, bearer credentials, and provider secrets are removed before storage.

When email is configured, the log records the configured delivery method and provider, including production webhook deliveries. Mock environments record mock delivery. A preview redeploy resets its database, while canonical demo reset clears the canonical tenant's messages.

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.
  • A scheduled feature-spec maintenance run retains the configured newest report builds (30 by default) and marks links to deleted builds in pull-request comments as deleted by the cleanup job.
  • 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 deployment-eligible successful verification. Separate Deploy production and Deploy demo workflows consume that image and back up, migrate, verify, and switch independently. Deploy preview is a separate workflow for same-repository pull requests. Their run names identify the target and include the PR preview URL, and one target's failure does not make another target's workflow fail.
  • 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.
  • Preview deployment comments link both the preview URL and pull request, and show the deployment time in the viewer's local date and time format.
  • Closing a same-repository PR removes its route, container, user, credentials, and database, whether the PR was merged or abandoned.
  • Closing a same-repository PR also removes the GitHub deployment records for its numbered preview environment and deletes that GitHub environment definition.
  • The cleanup workflow uses the repository secret BOOKING_ENVIRONMENT_ADMIN_TOKEN for GitHub environment deletion. Configure it as a narrowly scoped GitHub App or fine-grained token with repository Administration: write permission; the ordinary Actions token is insufficient for this operation.
  • 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