generated from bisco/codex-bootstrap
feat: add nginx reverse proxy
This commit is contained in:
@@ -78,6 +78,7 @@ Configure the canonical test command for this repository:
|
|||||||
docker compose run --rm backend python manage.py test
|
docker compose run --rm backend python manage.py test
|
||||||
docker compose run --rm frontend npm run check
|
docker compose run --rm frontend npm run check
|
||||||
docker compose run --rm frontend npm run build
|
docker compose run --rm frontend npm run build
|
||||||
|
docker compose run --rm nginx nginx -t
|
||||||
docker compose config --quiet
|
docker compose config --quiet
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -5,6 +5,8 @@ POSTGRES_PASSWORD=replace-with-a-local-password
|
|||||||
DATABASE_URL=postgresql://theatre_lab:replace-with-a-local-password@postgres:5432/theatre_lab
|
DATABASE_URL=postgresql://theatre_lab:replace-with-a-local-password@postgres:5432/theatre_lab
|
||||||
DJANGO_SECRET_KEY=replace-with-a-long-random-development-key
|
DJANGO_SECRET_KEY=replace-with-a-long-random-development-key
|
||||||
DJANGO_DEBUG=true
|
DJANGO_DEBUG=true
|
||||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,backend
|
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,backend,azionelab.org
|
||||||
WAGTAILADMIN_BASE_URL=http://localhost:8000
|
WAGTAILADMIN_BASE_URL=http://azionelab.org:8080
|
||||||
PUBLIC_CMS_API_URL=http://backend:8000
|
PUBLIC_CMS_API_URL=http://backend:8000
|
||||||
|
NGINX_BIND_ADDRESS=127.0.0.1
|
||||||
|
NGINX_HTTP_PORT=8080
|
||||||
|
|||||||
@@ -10,16 +10,20 @@ stores content, and Docker Compose supplies a reproducible local environment.
|
|||||||
aggregate read-only API at `GET /api/site/home/`.
|
aggregate read-only API at `GET /api/site/home/`.
|
||||||
- `frontend/`: Astro components, responsive design system, typed API adapter, and
|
- `frontend/`: Astro components, responsive design system, typed API adapter, and
|
||||||
Italian fallback content for development when the CMS is unavailable.
|
Italian fallback content for development when the CMS is unavailable.
|
||||||
|
- `nginx/`: reverse-proxy virtual host for `azionelab.org`, routing public requests to
|
||||||
|
Astro or Wagtail.
|
||||||
- `postgres`: persistent CMS database.
|
- `postgres`: persistent CMS database.
|
||||||
- Docker volumes: `postgres_data` for the database and `media_data` for uploads.
|
- Docker volumes: `postgres_data` for the database and `media_data` for uploads.
|
||||||
|
|
||||||
See [the architecture document](docs/architecture.md) and
|
See [the architecture document](docs/architecture.md),
|
||||||
[ADR-0001](docs/adr/0001-headless-wagtail-astro.md) for the rationale.
|
[ADR-0001](docs/adr/0001-headless-wagtail-astro.md), and
|
||||||
|
[ADR-0002](docs/adr/0002-nginx-reverse-proxy.md) for the rationale.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Docker Engine with Docker Compose v2.
|
- Docker Engine with Docker Compose v2.
|
||||||
- Ports `4321` and `8000` available on localhost.
|
- Ports `8080`, `4321`, and `8000` available on localhost.
|
||||||
|
- A local hosts-file entry mapping `azionelab.org` to `127.0.0.1`.
|
||||||
|
|
||||||
No host Python or Node.js installation is required.
|
No host Python or Node.js installation is required.
|
||||||
|
|
||||||
@@ -32,15 +36,24 @@ docker compose ps
|
|||||||
docker compose exec backend python manage.py seed_demo
|
docker compose exec backend python manage.py seed_demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For local domain resolution, add this line to `/etc/hosts` using your normal system
|
||||||
|
administration workflow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
127.0.0.1 azionelab.org
|
||||||
|
```
|
||||||
|
|
||||||
Open:
|
Open:
|
||||||
|
|
||||||
- public site: <http://localhost:4321>
|
- public site: <http://azionelab.org:8080>
|
||||||
- Wagtail admin: <http://localhost:8000/admin/>
|
- Wagtail admin: <http://azionelab.org:8080/admin/>
|
||||||
- aggregate API: <http://localhost:8000/api/site/home/>
|
- aggregate API: <http://azionelab.org:8080/api/site/home/>
|
||||||
|
|
||||||
|
Direct loopback ports `4321` and `8000` remain available for local diagnostics only.
|
||||||
|
|
||||||
The frontend remains usable with curated fallback content if the API is unavailable.
|
The frontend remains usable with curated fallback content if the API is unavailable.
|
||||||
On first startup the backend applies migrations and collects static files before
|
On first startup the backend applies migrations and collects static files before
|
||||||
starting Gunicorn. Wait until `postgres`, `backend`, and `frontend` report healthy.
|
starting Gunicorn. Wait until all four services report healthy.
|
||||||
|
|
||||||
## Create an editor account
|
## Create an editor account
|
||||||
|
|
||||||
@@ -48,7 +61,7 @@ starting Gunicorn. Wait until `postgres`, `backend`, and `frontend` report healt
|
|||||||
docker compose exec backend python manage.py createsuperuser
|
docker compose exec backend python manage.py createsuperuser
|
||||||
```
|
```
|
||||||
|
|
||||||
Sign in to Wagtail at <http://localhost:8000/admin/>. The public site has no user
|
Sign in to Wagtail at <http://azionelab.org:8080/admin/>. The public site has no user
|
||||||
accounts or authentication.
|
accounts or authentication.
|
||||||
|
|
||||||
## Demo content
|
## Demo content
|
||||||
@@ -75,14 +88,14 @@ after seeding may be overwritten for records managed by the command.
|
|||||||
|
|
||||||
The frontend reads `PUBLIC_CMS_API_URL`; inside Compose it defaults to
|
The frontend reads `PUBLIC_CMS_API_URL`; inside Compose it defaults to
|
||||||
`http://backend:8000`. Media URLs use `WAGTAILADMIN_BASE_URL` so browsers receive the
|
`http://backend:8000`. Media URLs use `WAGTAILADMIN_BASE_URL` so browsers receive the
|
||||||
public `http://localhost:8000` address. Change both values for another environment.
|
public reverse-proxy address. Change both values for another environment.
|
||||||
The standard published-page endpoint is also available at `/api/v2/pages/`.
|
The standard published-page endpoint is also available at `/api/v2/pages/`.
|
||||||
|
|
||||||
## Useful commands
|
## Useful commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Follow application logs
|
# Follow application logs
|
||||||
docker compose logs -f postgres backend frontend
|
docker compose logs -f postgres backend frontend nginx
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
docker compose exec backend python manage.py migrate
|
docker compose exec backend python manage.py migrate
|
||||||
@@ -91,6 +104,7 @@ docker compose exec backend python manage.py migrate
|
|||||||
docker compose run --rm backend python manage.py test
|
docker compose run --rm backend python manage.py test
|
||||||
docker compose run --rm frontend npm run check
|
docker compose run --rm frontend npm run check
|
||||||
docker compose run --rm frontend npm run build
|
docker compose run --rm frontend npm run build
|
||||||
|
docker compose run --rm nginx nginx -t
|
||||||
docker compose config --quiet
|
docker compose config --quiet
|
||||||
|
|
||||||
# Stop the stack without deleting content
|
# Stop the stack without deleting content
|
||||||
@@ -109,6 +123,7 @@ local environment; run the seed command again after the next startup.
|
|||||||
.
|
.
|
||||||
├── backend/ # Wagtail/Django CMS and API
|
├── backend/ # Wagtail/Django CMS and API
|
||||||
├── frontend/ # Astro public website
|
├── frontend/ # Astro public website
|
||||||
|
├── nginx/ # Reverse-proxy virtual host
|
||||||
├── docs/ # Architecture, operations, security, and ADRs
|
├── docs/ # Architecture, operations, security, and ADRs
|
||||||
├── docker-compose.yml
|
├── docker-compose.yml
|
||||||
├── .env.example
|
├── .env.example
|
||||||
@@ -134,5 +149,5 @@ cautions. Do not commit database dumps, `.env`, or uploaded media.
|
|||||||
## Production TODOs
|
## Production TODOs
|
||||||
|
|
||||||
This repository deliberately targets a simple, working local deployment. Before a
|
This repository deliberately targets a simple, working local deployment. Before a
|
||||||
public production launch, add a TLS reverse proxy, production-grade static/media
|
public production launch, add TLS certificates, production-grade static/media
|
||||||
serving, off-host backups, monitoring, and a deployment-specific secret manager.
|
serving, off-host backups, monitoring, and a deployment-specific secret manager.
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ ALLOWED_HOSTS = [
|
|||||||
for host in os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
for host in os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
||||||
if host.strip()
|
if host.strip()
|
||||||
]
|
]
|
||||||
|
if "azionelab.org" not in ALLOWED_HOSTS:
|
||||||
|
ALLOWED_HOSTS.append("azionelab.org")
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"home",
|
"home",
|
||||||
|
|||||||
+28
-2
@@ -27,8 +27,8 @@ services:
|
|||||||
DATABASE_URL: ${DATABASE_URL:-postgresql://theatre_lab:replace-with-a-local-password@postgres:5432/theatre_lab}
|
DATABASE_URL: ${DATABASE_URL:-postgresql://theatre_lab:replace-with-a-local-password@postgres:5432/theatre_lab}
|
||||||
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-replace-with-a-long-random-development-key}
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-replace-with-a-long-random-development-key}
|
||||||
DJANGO_DEBUG: ${DJANGO_DEBUG:-true}
|
DJANGO_DEBUG: ${DJANGO_DEBUG:-true}
|
||||||
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-localhost,127.0.0.1,backend}
|
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-localhost,127.0.0.1,backend,azionelab.org}
|
||||||
WAGTAILADMIN_BASE_URL: ${WAGTAILADMIN_BASE_URL:-http://localhost:8000}
|
WAGTAILADMIN_BASE_URL: ${WAGTAILADMIN_BASE_URL:-http://azionelab.org:8080}
|
||||||
volumes:
|
volumes:
|
||||||
- media_data:/app/media
|
- media_data:/app/media
|
||||||
ports:
|
ports:
|
||||||
@@ -72,6 +72,32 @@ services:
|
|||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.30.0-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
init: true
|
||||||
|
volumes:
|
||||||
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
ports:
|
||||||
|
- "${NGINX_BIND_ADDRESS:-127.0.0.1}:${NGINX_HTTP_PORT:-8080}:80"
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
frontend:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"wget -q -O /dev/null --header='Host: azionelab.org' http://127.0.0.1/health/ || exit 1",
|
||||||
|
]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
media_data:
|
media_data:
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# ADR-0002: NGINX reverse proxy for the public virtual host
|
||||||
|
|
||||||
|
Date: 2026-06-24
|
||||||
|
|
||||||
|
Status: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The Compose stack exposes Astro and Wagtail on separate local ports. Azione!Lab needs
|
||||||
|
one domain-aware HTTP entry point for `azionelab.org` while keeping application routing
|
||||||
|
and service discovery inside Docker Compose.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Add a pinned NGINX Alpine container. Its `azionelab.org` virtual host routes Wagtail
|
||||||
|
admin, API, documents, health, static files, and media to Django; all other paths go to
|
||||||
|
Astro. An explicit default virtual host returns 404 for unknown host names.
|
||||||
|
|
||||||
|
For local development NGINX binds to `127.0.0.1:8080`, configurable through
|
||||||
|
`NGINX_BIND_ADDRESS` and `NGINX_HTTP_PORT`. Existing loopback-only application ports
|
||||||
|
remain available for diagnostics. PostgreSQL remains private to the Compose network.
|
||||||
|
TLS termination is outside this local implementation.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- The preferred local URL becomes `http://azionelab.org:8080` after a hosts-file entry.
|
||||||
|
- Wagtail receives the original host and standard forwarded client/protocol headers.
|
||||||
|
- Media URLs must use the reverse-proxy address through `WAGTAILADMIN_BASE_URL`.
|
||||||
|
- Production deployment still requires TLS, trusted proxy policy, and removal or
|
||||||
|
firewalling of diagnostic application ports.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- Routing only the frontend was rejected because Wagtail admin, API, and media should
|
||||||
|
share the public domain.
|
||||||
|
- Replacing Astro or Django with static files served directly by NGINX was rejected as
|
||||||
|
unnecessary for the requested local stack.
|
||||||
|
- Adding automatic certificates was deferred because DNS and certificate ownership are
|
||||||
|
not part of this task.
|
||||||
|
|
||||||
|
## Security impact
|
||||||
|
|
||||||
|
Unknown host names are rejected. NGINX adds basic content-type and referrer headers,
|
||||||
|
runs without added capabilities or privileged mode, and is bound to loopback by default.
|
||||||
|
No TLS guarantee is made by this local configuration.
|
||||||
|
|
||||||
|
## Operational impact
|
||||||
|
|
||||||
|
NGINX depends on healthy frontend and backend services and has its own health check.
|
||||||
|
Operators must configure DNS/hosts, the public Wagtail base URL, and the published port.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
Remove the NGINX service and configuration, restore direct application URLs, and revert
|
||||||
|
the related allowed-host and public-base-URL values. Database and media data are not
|
||||||
|
changed.
|
||||||
+10
-5
@@ -1,11 +1,15 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
The system has three runtime components on the Docker Compose default network:
|
The system has four runtime components on the Docker Compose default network:
|
||||||
|
|
||||||
1. Astro renders the public single page and requests one aggregate JSON document.
|
1. NGINX accepts requests for `azionelab.org` and routes them by path.
|
||||||
2. Wagtail/Django manages editorial content, serves media in development, and exposes
|
2. Astro renders the public single page and requests one aggregate JSON document.
|
||||||
|
3. Wagtail/Django manages editorial content, serves media in development, and exposes
|
||||||
the read-only `/api/site/home/` endpoint.
|
the read-only `/api/site/home/` endpoint.
|
||||||
3. PostgreSQL persists Wagtail content and metadata.
|
4. PostgreSQL persists Wagtail content and metadata.
|
||||||
|
|
||||||
|
NGINX sends `/admin`, `/api`, `/documents`, `/health`, `/media`, and `/static` paths to
|
||||||
|
Wagtail. All remaining paths go to Astro. Unknown virtual hosts receive a 404 response.
|
||||||
|
|
||||||
The aggregate response contains `settings`, `homepage`, `feature_cards`, `teacher`,
|
The aggregate response contains `settings`, `homepage`, `feature_cards`, `teacher`,
|
||||||
`lesson_info`, `shows`, and `gallery_items`. Image values contain browser-facing URLs
|
`lesson_info`, `shows`, and `gallery_items`. Image values contain browser-facing URLs
|
||||||
@@ -18,4 +22,5 @@ has no database access and no public write API. Uploaded media is stored in a de
|
|||||||
Compose volume.
|
Compose volume.
|
||||||
|
|
||||||
The deployment topology and content model are recorded in
|
The deployment topology and content model are recorded in
|
||||||
[ADR-0001](adr/0001-headless-wagtail-astro.md).
|
[ADR-0001](adr/0001-headless-wagtail-astro.md) and
|
||||||
|
[ADR-0002](adr/0002-nginx-reverse-proxy.md).
|
||||||
|
|||||||
+15
-8
@@ -14,22 +14,29 @@ docker compose exec backend python manage.py seed_demo
|
|||||||
The backend applies migrations and collects static files before starting Gunicorn.
|
The backend applies migrations and collects static files before starting Gunicorn.
|
||||||
All services have health checks; wait for healthy status before opening the site.
|
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
|
Add `127.0.0.1 azionelab.org` to the local hosts file, then use
|
||||||
port `8000`. PostgreSQL is available only on the Compose network. `postgres_data` and
|
`http://azionelab.org:8080`. NGINX binds to loopback port `8080` by default and routes
|
||||||
`media_data` are persistent named volumes.
|
the domain to Astro or Wagtail. Their direct loopback ports `4321` and `8000` remain
|
||||||
|
available for diagnostics. 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
|
The stack uses explicit PostgreSQL 16.9, Python 3.12.12, Node.js 22.20, and NGINX 1.30.0
|
||||||
versions. Containers are not privileged and use `no-new-privileges`.
|
image versions. Containers are not privileged and use `no-new-privileges`.
|
||||||
|
|
||||||
Required runtime variables are `DATABASE_URL`, `DJANGO_SECRET_KEY`, `DJANGO_DEBUG`,
|
Required runtime variables are `DATABASE_URL`, `DJANGO_SECRET_KEY`, `DJANGO_DEBUG`,
|
||||||
`DJANGO_ALLOWED_HOSTS`, `WAGTAILADMIN_BASE_URL`, and `PUBLIC_CMS_API_URL`. PostgreSQL
|
`DJANGO_ALLOWED_HOSTS`, `WAGTAILADMIN_BASE_URL`, `PUBLIC_CMS_API_URL`,
|
||||||
bootstrap variables are also documented in `.env.example`. Do not use the example
|
`NGINX_BIND_ADDRESS`, and `NGINX_HTTP_PORT`. PostgreSQL bootstrap variables are also
|
||||||
credentials outside local development.
|
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.
|
`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
|
`PUBLIC_CMS_API_URL` must be reachable by Astro; within Compose it is
|
||||||
`http://backend:8000`.
|
`http://backend:8000`.
|
||||||
|
|
||||||
|
The local virtual host is HTTP-only. Production must publish NGINX through the intended
|
||||||
|
network interface, configure DNS for `azionelab.org`, terminate TLS, and prevent direct
|
||||||
|
external access to application ports.
|
||||||
|
|
||||||
## Production boundary
|
## Production boundary
|
||||||
|
|
||||||
The Compose stack is a local development deployment. A public environment still needs
|
The Compose stack is a local development deployment. A public environment still needs
|
||||||
|
|||||||
+11
-4
@@ -5,13 +5,19 @@
|
|||||||
```bash
|
```bash
|
||||||
docker compose up --build -d
|
docker compose up --build -d
|
||||||
docker compose ps
|
docker compose ps
|
||||||
docker compose logs -f backend frontend postgres
|
docker compose logs -f nginx backend frontend postgres
|
||||||
docker compose down
|
docker compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
Compose health checks PostgreSQL and Django. The public health endpoint is
|
Compose health checks all services. Through the virtual host, the public health endpoint
|
||||||
`http://localhost:8000/health/`; the aggregate content endpoint is
|
is `http://azionelab.org:8080/health/`; the aggregate content endpoint is
|
||||||
`http://localhost:8000/api/site/home/`.
|
`http://azionelab.org:8080/api/site/home/`.
|
||||||
|
|
||||||
|
Test routing without changing the hosts file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --resolve azionelab.org:8080:127.0.0.1 http://azionelab.org:8080/health/
|
||||||
|
```
|
||||||
|
|
||||||
Apply schema changes with `docker compose exec backend python manage.py migrate`.
|
Apply schema changes with `docker compose exec backend python manage.py migrate`.
|
||||||
Create editors with `createsuperuser`; use Wagtail permissions for later non-superuser
|
Create editors with `createsuperuser`; use Wagtail permissions for later non-superuser
|
||||||
@@ -42,3 +48,4 @@ Keep real backups encrypted and off-host with a defined retention policy.
|
|||||||
- Frontend fallback content can mask a CMS outage, so monitor backend health directly.
|
- Frontend fallback content can mask a CMS outage, so monitor backend health directly.
|
||||||
- Local named volumes are not off-host backups.
|
- Local named volumes are not off-host backups.
|
||||||
- Development media serving is unsuitable for production traffic.
|
- Development media serving is unsuitable for production traffic.
|
||||||
|
- The local reverse proxy provides HTTP only; it does not manage certificates.
|
||||||
|
|||||||
+10
-1
@@ -3,7 +3,7 @@
|
|||||||
## Site shows fallback content
|
## Site shows fallback content
|
||||||
|
|
||||||
1. Check `docker compose ps` and the backend health status.
|
1. Check `docker compose ps` and the backend health status.
|
||||||
2. Request `http://localhost:8000/api/site/home/` directly.
|
2. Request `http://azionelab.org:8080/api/site/home/` through NGINX.
|
||||||
3. Inspect `docker compose logs backend postgres`.
|
3. Inspect `docker compose logs backend postgres`.
|
||||||
4. Verify `PUBLIC_CMS_API_URL` resolves from the frontend container.
|
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.
|
5. Restart the frontend after recovery so its static page is rebuilt from CMS data.
|
||||||
@@ -29,6 +29,15 @@
|
|||||||
4. For the frontend, request port `4321`, then check the API separately because
|
4. For the frontend, request port `4321`, then check the API separately because
|
||||||
fallback content can mask a CMS outage.
|
fallback content can mask a CMS outage.
|
||||||
|
|
||||||
|
## NGINX returns 404 or 502
|
||||||
|
|
||||||
|
1. Confirm the request host is exactly `azionelab.org`; unknown hosts intentionally
|
||||||
|
receive 404.
|
||||||
|
2. Run `docker compose exec nginx nginx -t`.
|
||||||
|
3. Check `docker compose ps` and verify both backend and frontend are healthy.
|
||||||
|
4. Inspect `docker compose logs --tail=200 nginx backend frontend`.
|
||||||
|
5. Verify local DNS or `/etc/hosts` maps `azionelab.org` to the proxy address.
|
||||||
|
|
||||||
## Rollback
|
## Rollback
|
||||||
|
|
||||||
Revert the application commit and rebuild containers. Preserve database/media volumes.
|
Revert the application commit and rebuild containers. Preserve database/media volumes.
|
||||||
|
|||||||
+5
-1
@@ -4,7 +4,8 @@
|
|||||||
and password hashing. The public site does not add accounts or write endpoints.
|
and password hashing. The public site does not add accounts or write endpoints.
|
||||||
- The aggregate API is intentionally unauthenticated and returns published editorial
|
- The aggregate API is intentionally unauthenticated and returns published editorial
|
||||||
content only; secrets and unpublished drafts must never be serialized.
|
content only; secrets and unpublished drafts must never be serialized.
|
||||||
- PostgreSQL has no host-published port. Web ports bind to loopback for local use.
|
- PostgreSQL has no host-published port. NGINX and diagnostic application ports bind to
|
||||||
|
loopback for local use. Unknown NGINX virtual hosts are rejected with 404.
|
||||||
- Containers are unprivileged at the Compose level: no privileged mode, host network,
|
- 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
|
Docker socket, or added capabilities are used. `no-new-privileges` is enabled and
|
||||||
the application images run as non-root users.
|
the application images run as non-root users.
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
legitimately exposes only contact details approved for publication.
|
legitimately exposes only contact details approved for publication.
|
||||||
- Dependency and image versions are explicit. Operators remain responsible for patch
|
- Dependency and image versions are explicit. Operators remain responsible for patch
|
||||||
upgrades, vulnerability scans, and production digest pinning.
|
upgrades, vulnerability scans, and production digest pinning.
|
||||||
|
- NGINX forwards the original host and standard client/protocol headers. The local
|
||||||
|
configuration is HTTP-only; production must add TLS and ensure application ports are
|
||||||
|
not externally reachable.
|
||||||
|
|
||||||
Manual production hardening remains required for reverse proxy headers, TLS, media
|
Manual production hardening remains required for reverse proxy headers, TLS, media
|
||||||
storage, backup retention, monitoring, and admin network policy.
|
storage, backup retention, monitoring, and admin network policy.
|
||||||
|
|||||||
+6
-3
@@ -8,6 +8,7 @@ All tests should run inside Docker containers.
|
|||||||
docker compose run --rm backend python manage.py test
|
docker compose run --rm backend python manage.py test
|
||||||
docker compose run --rm frontend npm run check
|
docker compose run --rm frontend npm run check
|
||||||
docker compose run --rm frontend npm run build
|
docker compose run --rm frontend npm run build
|
||||||
|
docker compose run --rm nginx nginx -t
|
||||||
docker compose config --quiet
|
docker compose config --quiet
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -18,11 +19,13 @@ The test suite covers:
|
|||||||
- Django model and API integration tests;
|
- Django model and API integration tests;
|
||||||
- Astro static type and template validation;
|
- Astro static type and template validation;
|
||||||
- production frontend build;
|
- production frontend build;
|
||||||
|
- NGINX syntax and upstream configuration validation;
|
||||||
- Docker Compose configuration validation.
|
- Docker Compose configuration validation.
|
||||||
|
|
||||||
All commands run in containers. The backend test container starts PostgreSQL through
|
All commands run in containers. The backend test container starts PostgreSQL through
|
||||||
Compose; the frontend checks use the checked-in lockfile for reproducible installs.
|
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
|
After seeding, smoke-test `/health/`, `/api/site/home/`, and the public page through
|
||||||
keyboard navigation, responsive layout, and accessible names require a manual browser
|
NGINX using the `azionelab.org` host. Also verify that an unknown host returns 404.
|
||||||
pass until browser automation is added.
|
Basic keyboard navigation, responsive layout, and accessible names require a manual
|
||||||
|
browser pass until browser automation is added.
|
||||||
|
|||||||
@@ -6,4 +6,9 @@ export default defineConfig({
|
|||||||
host: true,
|
host: true,
|
||||||
port: 4321,
|
port: 4321,
|
||||||
},
|
},
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
allowedHosts: ["azionelab.org"],
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
"" close;
|
||||||
|
}
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
upstream wagtail_backend {
|
||||||
|
server backend:8000;
|
||||||
|
keepalive 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream astro_frontend {
|
||||||
|
server frontend:4321;
|
||||||
|
keepalive 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name azionelab.org;
|
||||||
|
|
||||||
|
client_max_body_size 10m;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
location ~ ^/(admin|api|documents|health|media|static)(/|$) {
|
||||||
|
proxy_pass http://wagtail_backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://astro_frontend;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user