Troubleshooting
| Symptom | Check |
|---|---|
401 Authentication required | Bearer header exists and session is not expired/logged out |
403 Insufficient permission | Membership contains a role allowed for the operation |
403 Attendee is outside the allowed groups | User and instance group memberships overlap |
409 Instance is not bookable | Instance is scheduled and audience is not nobody |
409 Instance is full | Capacity and confirmed bookings |
409 Entitlement has expired | Entitlement expiry and server clock |
402 Payment failed | Payment gateway result and merchant configuration |
| Readiness fails | Database connectivity, credentials, migrations, and application logs |
/docs assets return 404 | VitePress base is /docs/ and npm run build:docs ran |
Failed public-environment replacements
Production and demo deploy in parallel from the same immutable image, but each has its own backup, migration, candidate, readiness check, deployment record, and route switch. If one candidate fails during image pull, migration, startup, readiness, release verification, or route switching, the other environment continues independently. The failed job prints a phase-specific ::error annotation and a bounded diagnostic block with that container's state, restart count, health state, candidate release, and still-active release. Recent application logs are redacted before they are printed, and only the failed candidate is removed while its active route and the other environment remain untouched by the failed job.
On the VPS, inspect the same state with:
sudo cat /opt/booking-saas/releases/production-active
sudo cat /opt/booking-saas/releases/demo-active
sudo docker ps -a --filter 'name=booking-production-' --filter 'name=booking-demo-'
sudo docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} restart={{.RestartCount}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' CONTAINER
sudo docker logs --tail 80 CONTAINER 2>&1 | sed -E \
-e 's#(mysql://[^:/[:space:]]+):[^@[:space:]]+@#\1:[REDACTED]@#gi' \
-e 's#(Bearer[[:space:]]+)[^[:space:]]+#\1[REDACTED]#gi' \
-e 's#((password|passwd|token|secret|authorization|cookie|set-cookie)[=:][[:space:]]*)[^[:space:]]+#\1[REDACTED]#gi' \
-e 's#[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}#[REDACTED_EMAIL]#gi' \
-e 's#https?://[^/@[:space:]]+:[^/@[:space:]]+@#https://[REDACTED]@#gi'Replace CONTAINER with the name printed by docker ps. Each active marker is the release that remains routed after a failed replacement for that environment. If a failure is caused by configuration, correct the relevant file under /opt/booking-saas/secrets/, verify its mode is 0600, and rerun the Deploy workflow; do not delete or restart the active containers as recovery.
API errors are JSON. Reverse-proxy, browser, or HTML errors usually indicate routing rather than domain validation.
When reporting a problem, include the request path, status, timestamp, environment, and correlation information available from the gateway—but never include magic-link tokens, sessions, database credentials, or payment secrets.