generated from bisco/codex-bootstrap
feat: build headless theatre laboratory website
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# ADR-0001: Headless Wagtail with an Astro frontend
|
||||
|
||||
Date: 2026-06-22
|
||||
|
||||
Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The theatre workshop needs an editorially polished public single page whose core
|
||||
content can be updated without code changes. The required stack is Wagtail/Django,
|
||||
Astro, PostgreSQL, and Docker Compose. A small team must be able to run and maintain
|
||||
the system locally without unnecessary services.
|
||||
|
||||
## Decision
|
||||
|
||||
Use Wagtail as a headless CMS backed by PostgreSQL and Astro as the public frontend.
|
||||
Wagtail owns uploaded media and exposes a read-only aggregate endpoint at
|
||||
`/api/site/home/`. Astro renders the page server-side in development/build time and
|
||||
uses curated Italian fallback content when the CMS is temporarily unavailable.
|
||||
Docker Compose runs PostgreSQL, Django, and Astro on one private default network.
|
||||
|
||||
The content model uses one Wagtail `HomePage` with three ordered feature cards, site
|
||||
settings, and reusable snippets for teacher details, lesson details, shows, and gallery
|
||||
items. This keeps editing simple and avoids a custom frontend state layer.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Editors use the authenticated Wagtail admin while the public API remains read-only.
|
||||
- The public page needs only one API request and degrades gracefully during CMS outages.
|
||||
- Uploaded media and PostgreSQL data require separate persistent volumes and backups.
|
||||
- Local frontend rendering depends on container networking; browser-facing media URLs
|
||||
are generated from the configured public Wagtail base URL.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Calling the standard Wagtail endpoints separately was rejected because it adds
|
||||
frontend orchestration for a single page.
|
||||
- Embedding the frontend in Django templates was rejected because Astro is required.
|
||||
- A JavaScript SPA and client-side state library were rejected as unnecessary.
|
||||
|
||||
## Security impact
|
||||
|
||||
The aggregate endpoint exposes published editorial content only. Wagtail admin keeps
|
||||
its normal authentication and CSRF protections. Services bind to loopback for local
|
||||
development, containers are not privileged, and secrets are supplied through the
|
||||
environment.
|
||||
|
||||
## Operational impact
|
||||
|
||||
Operators must back up the PostgreSQL and media volumes. CMS schema changes require
|
||||
normal Django migrations. Health checks order local startup.
|
||||
|
||||
## Rollback
|
||||
|
||||
Revert the feature commit and remove the Compose volumes only if their stored content
|
||||
is no longer needed. Keep database and media backups when rolling back code alone.
|
||||
+17
-9
@@ -1,13 +1,21 @@
|
||||
# Architecture
|
||||
|
||||
Describe the project architecture here.
|
||||
The system has three runtime components on the Docker Compose default network:
|
||||
|
||||
Include:
|
||||
1. Astro renders the public single page and requests one aggregate JSON document.
|
||||
2. Wagtail/Django manages editorial content, serves media in development, and exposes
|
||||
the read-only `/api/site/home/` endpoint.
|
||||
3. PostgreSQL persists Wagtail content and metadata.
|
||||
|
||||
- main components;
|
||||
- runtime dependencies;
|
||||
- data flow;
|
||||
- persistence;
|
||||
- external integrations;
|
||||
- deployment topology;
|
||||
- relevant ADRs.
|
||||
The aggregate response contains `settings`, `homepage`, `feature_cards`, `teacher`,
|
||||
`lesson_info`, `shows`, and `gallery_items`. Image values contain browser-facing URLs
|
||||
and alternative text. Only published home content and records marked for home display
|
||||
are exposed. Astro validates/normalizes the response and falls back to checked-in
|
||||
Italian demo content if fetching fails.
|
||||
|
||||
Wagtail's authenticated admin remains the only editing interface. The public frontend
|
||||
has no database access and no public write API. Uploaded media is stored in a dedicated
|
||||
Compose volume.
|
||||
|
||||
The deployment topology and content model are recorded in
|
||||
[ADR-0001](adr/0001-headless-wagtail-astro.md).
|
||||
|
||||
+39
-11
@@ -1,15 +1,43 @@
|
||||
# Deployment
|
||||
|
||||
Describe how this project is deployed.
|
||||
## Local environment
|
||||
|
||||
Include:
|
||||
Copy `.env.example` to `.env`, replace the development placeholders, then run:
|
||||
|
||||
- environments;
|
||||
- Docker/Compose usage;
|
||||
- required configuration;
|
||||
- secrets handling;
|
||||
- exposed ports;
|
||||
- volumes;
|
||||
- networks;
|
||||
- deployment commands;
|
||||
- rollback procedure.
|
||||
```bash
|
||||
cp .env.example .env
|
||||
docker compose up --build -d
|
||||
docker compose ps
|
||||
docker compose exec backend python manage.py seed_demo
|
||||
```
|
||||
|
||||
The backend applies migrations and collects static files before starting Gunicorn.
|
||||
All services have health checks; wait for healthy status before opening the site.
|
||||
|
||||
Compose exposes the Astro site on loopback port `4321` and Django/Wagtail on loopback
|
||||
port `8000`. PostgreSQL is available only on the Compose network. `postgres_data` and
|
||||
`media_data` are persistent named volumes.
|
||||
|
||||
The stack uses explicit PostgreSQL 16.9, Python 3.12.12, and Node.js 22.20 image
|
||||
versions. Containers are not privileged and use `no-new-privileges`.
|
||||
|
||||
Required runtime variables are `DATABASE_URL`, `DJANGO_SECRET_KEY`, `DJANGO_DEBUG`,
|
||||
`DJANGO_ALLOWED_HOSTS`, `WAGTAILADMIN_BASE_URL`, and `PUBLIC_CMS_API_URL`. PostgreSQL
|
||||
bootstrap variables are also documented in `.env.example`. Do not use the example
|
||||
credentials outside local development.
|
||||
|
||||
`WAGTAILADMIN_BASE_URL` must be browser-reachable because it is used for media URLs.
|
||||
`PUBLIC_CMS_API_URL` must be reachable by Astro; within Compose it is
|
||||
`http://backend:8000`.
|
||||
|
||||
## Production boundary
|
||||
|
||||
The Compose stack is a local development deployment. A public environment still needs
|
||||
TLS termination, production static/media serving, restricted admin access, managed
|
||||
secrets, off-host backups, monitoring, and an explicit domain/allowed-host policy.
|
||||
|
||||
## Rollback
|
||||
|
||||
Revert the application commit and rebuild images. Keep the database and media volumes
|
||||
unless content deletion is intentional. Schema rollback must be evaluated per Django
|
||||
migration; take coordinated database and media backups first.
|
||||
|
||||
+40
-9
@@ -1,13 +1,44 @@
|
||||
# Operations
|
||||
|
||||
Describe operational procedures.
|
||||
## Routine commands
|
||||
|
||||
Include:
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
docker compose ps
|
||||
docker compose logs -f backend frontend postgres
|
||||
docker compose down
|
||||
```
|
||||
|
||||
- startup and shutdown;
|
||||
- health checks;
|
||||
- logs;
|
||||
- monitoring;
|
||||
- backup and restore;
|
||||
- routine maintenance;
|
||||
- known operational risks.
|
||||
Compose health checks PostgreSQL and Django. The public health endpoint is
|
||||
`http://localhost:8000/health/`; the aggregate content endpoint is
|
||||
`http://localhost:8000/api/site/home/`.
|
||||
|
||||
Apply schema changes with `docker compose exec backend python manage.py migrate`.
|
||||
Create editors with `createsuperuser`; use Wagtail permissions for later non-superuser
|
||||
accounts.
|
||||
|
||||
The backend applies migrations at container startup. To permanently reset local
|
||||
state, use `docker compose down --volumes`; this deletes both database and media.
|
||||
|
||||
## Backup and restore
|
||||
|
||||
Back up the database and media at the same logical point in time:
|
||||
|
||||
```bash
|
||||
mkdir -p backups/media
|
||||
docker compose stop frontend backend
|
||||
docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backups/database.sql
|
||||
docker compose cp backend:/app/media/. backups/media/
|
||||
docker compose start backend frontend
|
||||
```
|
||||
|
||||
Restore is destructive: verify the backup in a disposable environment and take a fresh
|
||||
backup first. Then stop both applications, recreate and restore the database, replace
|
||||
the media content, restart, and verify health, API, admin login, and several images.
|
||||
Keep real backups encrypted and off-host with a defined retention policy.
|
||||
|
||||
## Known risks
|
||||
|
||||
- Frontend fallback content can mask a CMS outage, so monitor backend health directly.
|
||||
- Local named volumes are not off-host backups.
|
||||
- Development media serving is unsuitable for production traffic.
|
||||
|
||||
+26
-10
@@ -1,19 +1,35 @@
|
||||
# Runbook
|
||||
|
||||
Operational runbook for this project.
|
||||
## Site shows fallback content
|
||||
|
||||
## Common tasks
|
||||
1. Check `docker compose ps` and the backend health status.
|
||||
2. Request `http://localhost:8000/api/site/home/` directly.
|
||||
3. Inspect `docker compose logs backend postgres`.
|
||||
4. Verify `PUBLIC_CMS_API_URL` resolves from the frontend container.
|
||||
5. Restart the frontend after recovery so its static page is rebuilt from CMS data.
|
||||
|
||||
Document routine operational tasks here.
|
||||
## Wagtail does not start
|
||||
|
||||
## Troubleshooting
|
||||
1. Confirm PostgreSQL is healthy.
|
||||
2. Check `DATABASE_URL` and allowed hosts without printing secret values in shared logs.
|
||||
3. Run `docker compose exec backend python manage.py migrate`.
|
||||
4. Review backend logs for a specific migration or configuration error.
|
||||
|
||||
Document known issues, symptoms, checks, and remediation steps.
|
||||
## Images are missing
|
||||
|
||||
1. Verify the `media_data` volume is mounted at `/app/media`.
|
||||
2. Check `WAGTAILADMIN_BASE_URL` uses a URL reachable by the browser.
|
||||
3. Confirm the image still exists in Wagtail and has not been removed from the volume.
|
||||
|
||||
## A service is unhealthy
|
||||
|
||||
1. Run `docker compose ps` and inspect `docker compose logs --tail=200 SERVICE`.
|
||||
2. For PostgreSQL, check volume capacity and database/user configuration.
|
||||
3. For the backend, check `/health/`, migrations, and database connectivity.
|
||||
4. For the frontend, request port `4321`, then check the API separately because
|
||||
fallback content can mask a CMS outage.
|
||||
|
||||
## Rollback
|
||||
|
||||
Document rollback procedures here.
|
||||
|
||||
## Emergency contacts
|
||||
|
||||
Document project-specific escalation paths if appropriate.
|
||||
Revert the application commit and rebuild containers. Preserve database/media volumes.
|
||||
Before reversing migrations or deleting volumes, make and validate coordinated backups.
|
||||
|
||||
+20
-13
@@ -1,16 +1,23 @@
|
||||
# Security
|
||||
|
||||
Describe security assumptions and controls.
|
||||
- Wagtail admin uses Django authentication, authorization, sessions, CSRF protection,
|
||||
and password hashing. The public site does not add accounts or write endpoints.
|
||||
- The aggregate API is intentionally unauthenticated and returns published editorial
|
||||
content only; secrets and unpublished drafts must never be serialized.
|
||||
- PostgreSQL has no host-published port. Web ports bind to loopback for local use.
|
||||
- Containers are unprivileged at the Compose level: no privileged mode, host network,
|
||||
Docker socket, or added capabilities are used. `no-new-privileges` is enabled and
|
||||
the application images run as non-root users.
|
||||
- `.env` is ignored. `.env.example` contains replaceable development placeholders,
|
||||
never production credentials. Use a deployment secret manager outside local use.
|
||||
- `DJANGO_DEBUG` must be false and allowed hosts explicit outside development. Add TLS
|
||||
at the edge before public exposure.
|
||||
- Database and uploaded media backups may contain personal data. Restrict, encrypt,
|
||||
retain, and delete them according to the operator's privacy policy.
|
||||
- Avoid placing personal phone numbers or private contact details in logs. The API
|
||||
legitimately exposes only contact details approved for publication.
|
||||
- Dependency and image versions are explicit. Operators remain responsible for patch
|
||||
upgrades, vulnerability scans, and production digest pinning.
|
||||
|
||||
Include:
|
||||
|
||||
- authentication;
|
||||
- authorization;
|
||||
- network exposure;
|
||||
- TLS/certificates;
|
||||
- secrets management;
|
||||
- logging of sensitive data;
|
||||
- container privileges;
|
||||
- filesystem permissions;
|
||||
- dependency management;
|
||||
- relevant ADRs.
|
||||
Manual production hardening remains required for reverse proxy headers, TLS, media
|
||||
storage, backup retention, monitoring, and admin network policy.
|
||||
|
||||
+16
-11
@@ -1,23 +1,28 @@
|
||||
# Testing
|
||||
|
||||
Describe how tests are executed.
|
||||
|
||||
All tests should run inside Docker containers.
|
||||
|
||||
## Canonical test command
|
||||
|
||||
```bash
|
||||
CHANGE_ME
|
||||
docker compose run --rm backend python manage.py test
|
||||
docker compose run --rm frontend npm run check
|
||||
docker compose run --rm frontend npm run build
|
||||
docker compose config --quiet
|
||||
```
|
||||
|
||||
## Test categories
|
||||
|
||||
Describe applicable categories:
|
||||
The test suite covers:
|
||||
|
||||
- unit tests;
|
||||
- integration tests;
|
||||
- linting;
|
||||
- formatting checks;
|
||||
- Ansible syntax checks;
|
||||
- Docker/Compose validation;
|
||||
- smoke tests.
|
||||
- Django model and API integration tests;
|
||||
- Astro static type and template validation;
|
||||
- production frontend build;
|
||||
- Docker Compose configuration validation.
|
||||
|
||||
All commands run in containers. The backend test container starts PostgreSQL through
|
||||
Compose; the frontend checks use the checked-in lockfile for reproducible installs.
|
||||
|
||||
After seeding, smoke-test `/health/`, `/api/site/home/`, and the public page. Basic
|
||||
keyboard navigation, responsive layout, and accessible names require a manual browser
|
||||
pass until browser automation is added.
|
||||
|
||||
Reference in New Issue
Block a user