generated from bisco/codex-bootstrap
Merge branch 'infra/docker-compose-deployment' into develop
This commit is contained in:
@@ -77,7 +77,7 @@ All tests MUST be executed inside Docker containers.
|
|||||||
Configure the canonical test command for this repository:
|
Configure the canonical test command for this repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CHANGE_ME
|
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|||||||
24
.env.example
Normal file
24
.env.example
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# AzioneLab Docker Compose example environment.
|
||||||
|
# Copy this file to .env and replace placeholder values before deployment.
|
||||||
|
|
||||||
|
COMPOSE_PROJECT_NAME=azionelab
|
||||||
|
|
||||||
|
NGINX_HTTP_PORT=8080
|
||||||
|
|
||||||
|
BACKEND_HOST=backend
|
||||||
|
BACKEND_PORT=8000
|
||||||
|
FRONTEND_HOST=frontend
|
||||||
|
FRONTEND_PORT=8080
|
||||||
|
|
||||||
|
DJANGO_SECRET_KEY=change-me
|
||||||
|
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
|
||||||
|
DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:8080
|
||||||
|
DJANGO_DEBUG=false
|
||||||
|
|
||||||
|
POSTGRES_DB=azionelab
|
||||||
|
POSTGRES_USER=azionelab
|
||||||
|
POSTGRES_PASSWORD=change-me
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
DATABASE_URL=postgres://azionelab:change-me@postgres:5432/azionelab
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
27
docs/adr/0007-use-docker-compose-for-deployment.md
Normal file
27
docs/adr/0007-use-docker-compose-for-deployment.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# ADR-0007: Use Docker Compose for Deployment
|
||||||
|
|
||||||
|
Date: 2026-04-28
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
AzioneLab needs a simple production-oriented deployment for a small theatre company website. The initial runtime services are Django with gunicorn, an Angular frontend served by nginx, PostgreSQL, and an nginx reverse proxy.
|
||||||
|
|
||||||
|
The project does not need Celery, Redis, a container orchestrator, or a more complex platform at this stage.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Use Docker Compose as the initial deployment mechanism.
|
||||||
|
|
||||||
|
The Compose setup will define explicit `backend`, `frontend`, `postgres`, and `nginx` services under `infra/docker/compose.yml`. Configuration is provided through `.env`, PostgreSQL data is stored in a named volume, and only the reverse proxy publishes a host port.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- The deployment remains easy to understand, run, and review.
|
||||||
|
- The same topology can support local infrastructure checks and small production deployments.
|
||||||
|
- PostgreSQL persistence is explicit through a named volume.
|
||||||
|
- The setup can be replaced later if hosting or scaling needs outgrow Docker Compose.
|
||||||
|
- Operators must manage `.env`, backups, TLS, and image updates carefully.
|
||||||
@@ -137,10 +137,12 @@ The initial deployment uses Docker Compose with these services:
|
|||||||
- `nginx`: public reverse proxy and static frontend server;
|
- `nginx`: public reverse proxy and static frontend server;
|
||||||
- `frontend`: Angular build stage or static asset build source;
|
- `frontend`: Angular build stage or static asset build source;
|
||||||
- `backend`: Django application served by gunicorn;
|
- `backend`: Django application served by gunicorn;
|
||||||
- `db`: PostgreSQL database.
|
- `postgres`: PostgreSQL database.
|
||||||
|
|
||||||
Only nginx should be publicly exposed. The backend and database should be reachable only on the internal Compose network.
|
Only nginx should be publicly exposed. The backend and database should be reachable only on the internal Compose network.
|
||||||
|
|
||||||
|
The initial Compose files live under `infra/docker/`. The backend and frontend images are placeholders until the Django and Angular applications are implemented.
|
||||||
|
|
||||||
## Architectural Constraints
|
## Architectural Constraints
|
||||||
|
|
||||||
- Keep the booking workflow synchronous and explicit.
|
- Keep the booking workflow synchronous and explicit.
|
||||||
@@ -153,4 +155,8 @@ Only nginx should be publicly exposed. The backend and database should be reacha
|
|||||||
|
|
||||||
## Relevant ADRs
|
## Relevant ADRs
|
||||||
|
|
||||||
No ADRs are recorded yet. The technology stack and initial constraints are documented here from the project request.
|
- [ADR-0001: Use Django Monolith](adr/0001-use-django-monolith.md)
|
||||||
|
- [ADR-0002: Do Not Add an Async Task Queue Yet](adr/0002-no-async-task-queue.md)
|
||||||
|
- [ADR-0003: Use Opaque Tokens in QR Codes](adr/0003-qr-code-token-strategy.md)
|
||||||
|
- [ADR-0004: Use Email Confirmation for Reservations](adr/0004-email-confirmation-flow.md)
|
||||||
|
- [ADR-0007: Use Docker Compose for Deployment](adr/0007-use-docker-compose-for-deployment.md)
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ AzioneLab should deploy with a simple Docker Compose topology:
|
|||||||
- `nginx`: public reverse proxy and static frontend server;
|
- `nginx`: public reverse proxy and static frontend server;
|
||||||
- `frontend`: Angular build source or build stage for static assets;
|
- `frontend`: Angular build source or build stage for static assets;
|
||||||
- `backend`: Django 5.2 LTS application served by gunicorn;
|
- `backend`: Django 5.2 LTS application served by gunicorn;
|
||||||
- `db`: PostgreSQL database.
|
- `postgres`: PostgreSQL database.
|
||||||
|
|
||||||
Only nginx should expose public ports. The backend and database should stay on the internal Compose network.
|
Only nginx should expose public ports. The backend and database should stay on the internal Compose network.
|
||||||
|
|
||||||
|
The initial Compose setup is located at `infra/docker/compose.yml`.
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
### nginx
|
### nginx
|
||||||
@@ -37,6 +39,8 @@ Deployment options:
|
|||||||
|
|
||||||
The first option is preferred for a simple production deployment because nginx can serve immutable built assets without a long-running Node process.
|
The first option is preferred for a simple production deployment because nginx can serve immutable built assets without a long-running Node process.
|
||||||
|
|
||||||
|
At the infrastructure placeholder stage, the `frontend` service serves a static placeholder page with nginx. The Angular build will replace this placeholder later.
|
||||||
|
|
||||||
### backend
|
### backend
|
||||||
|
|
||||||
The backend is a Django application served by gunicorn.
|
The backend is a Django application served by gunicorn.
|
||||||
@@ -51,7 +55,9 @@ Responsibilities:
|
|||||||
|
|
||||||
The backend should run database migrations before or during deployment through an explicit operational command, not as hidden startup magic unless that choice is documented later.
|
The backend should run database migrations before or during deployment through an explicit operational command, not as hidden startup magic unless that choice is documented later.
|
||||||
|
|
||||||
### db
|
At the infrastructure placeholder stage, the `backend` service runs gunicorn against a minimal placeholder WSGI application. The real Django application will replace it later.
|
||||||
|
|
||||||
|
### postgres
|
||||||
|
|
||||||
PostgreSQL is the only database service.
|
PostgreSQL is the only database service.
|
||||||
|
|
||||||
@@ -84,6 +90,8 @@ Generated QR codes may also be generated on demand instead of stored as files. I
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` and replace all placeholder values before running or deploying the stack.
|
||||||
|
|
||||||
Required backend configuration:
|
Required backend configuration:
|
||||||
|
|
||||||
- `DJANGO_SECRET_KEY`;
|
- `DJANGO_SECRET_KEY`;
|
||||||
@@ -129,21 +137,25 @@ The exact commands will be finalized when application code and Compose files are
|
|||||||
Expected production-style flow:
|
Expected production-style flow:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
docker compose --env-file .env -f infra/docker/compose.yml build
|
||||||
docker compose run --rm backend python manage.py migrate
|
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py migrate
|
||||||
docker compose run --rm backend python manage.py collectstatic --noinput
|
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py collectstatic --noinput
|
||||||
docker compose up -d
|
docker compose --env-file .env -f infra/docker/compose.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected validation commands:
|
Expected validation commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose config
|
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||||
docker compose run --rm backend python manage.py check --deploy
|
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py check --deploy
|
||||||
docker compose run --rm backend python manage.py test
|
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py test
|
||||||
```
|
```
|
||||||
|
|
||||||
The repository does not yet define the canonical Docker-based test command.
|
The canonical repository check for the current infrastructure stage is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||||
|
```
|
||||||
|
|
||||||
## Rollback
|
## Rollback
|
||||||
|
|
||||||
|
|||||||
@@ -131,12 +131,15 @@ Expected secret configuration:
|
|||||||
|
|
||||||
Use environment variables, Docker secrets, or deployment-managed secret injection. Documentation and example configuration should use placeholders only.
|
Use environment variables, Docker secrets, or deployment-managed secret injection. Documentation and example configuration should use placeholders only.
|
||||||
|
|
||||||
|
For the Docker Compose setup, copy `.env.example` to `.env` and replace placeholder values outside version control. The repository ignores `.env` and `.env.*` files except `.env.example`.
|
||||||
|
|
||||||
## Deployment Security
|
## Deployment Security
|
||||||
|
|
||||||
Deployment should follow least privilege:
|
Deployment should follow least privilege:
|
||||||
|
|
||||||
- expose only nginx publicly;
|
- expose only nginx publicly;
|
||||||
- keep backend and database on an internal Docker network;
|
- keep backend and database on an internal Docker network;
|
||||||
|
- do not publish backend, frontend, or PostgreSQL ports to the host in production;
|
||||||
- avoid privileged containers;
|
- avoid privileged containers;
|
||||||
- use explicit image tags rather than `latest`;
|
- use explicit image tags rather than `latest`;
|
||||||
- persist PostgreSQL data in a named volume;
|
- persist PostgreSQL data in a named volume;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ All tests should run inside Docker containers.
|
|||||||
## Canonical test command
|
## Canonical test command
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
CHANGE_ME
|
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test categories
|
## Test categories
|
||||||
|
|||||||
22
infra/docker/backend/Dockerfile
Normal file
22
infra/docker/backend/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.13.4-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
"Django==5.2.3" \
|
||||||
|
"djangorestframework==3.16.0" \
|
||||||
|
"gunicorn==23.0.0" \
|
||||||
|
"psycopg[binary]==3.2.9"
|
||||||
|
|
||||||
|
RUN useradd --create-home --shell /usr/sbin/nologin appuser
|
||||||
|
|
||||||
|
COPY placeholder_wsgi.py /app/placeholder_wsgi.py
|
||||||
|
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "placeholder_wsgi:application"]
|
||||||
10
infra/docker/backend/placeholder_wsgi.py
Normal file
10
infra/docker/backend/placeholder_wsgi.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
def application(environ, start_response):
|
||||||
|
body = b"AzioneLab backend placeholder\n"
|
||||||
|
start_response(
|
||||||
|
"503 Service Unavailable",
|
||||||
|
[
|
||||||
|
("Content-Type", "text/plain; charset=utf-8"),
|
||||||
|
("Content-Length", str(len(body))),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return [body]
|
||||||
77
infra/docker/compose.yml
Normal file
77
infra/docker/compose.yml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
image: azionelab-backend:local
|
||||||
|
environment:
|
||||||
|
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
|
||||||
|
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS}
|
||||||
|
DJANGO_CSRF_TRUSTED_ORIGINS: ${DJANGO_CSRF_TRUSTED_ORIGINS}
|
||||||
|
DJANGO_DEBUG: ${DJANGO_DEBUG:-false}
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
|
||||||
|
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
||||||
|
expose:
|
||||||
|
- "${BACKEND_PORT:-8000}"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
image: azionelab-frontend:local
|
||||||
|
expose:
|
||||||
|
- "${FRONTEND_PORT:-8080}"
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16.3-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.27.0-alpine
|
||||||
|
ports:
|
||||||
|
- "${NGINX_HTTP_PORT:-8080}:80"
|
||||||
|
environment:
|
||||||
|
BACKEND_HOST: ${BACKEND_HOST:-backend}
|
||||||
|
BACKEND_PORT: ${BACKEND_PORT:-8000}
|
||||||
|
FRONTEND_HOST: ${FRONTEND_HOST:-frontend}
|
||||||
|
FRONTEND_PORT: ${FRONTEND_PORT:-8080}
|
||||||
|
NGINX_ENVSUBST_FILTER: "^(BACKEND_HOST|BACKEND_PORT|FRONTEND_HOST|FRONTEND_PORT)$"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/templates:/etc/nginx/templates:ro
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
- frontend
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
driver: bridge
|
||||||
6
infra/docker/frontend/Dockerfile
Normal file
6
infra/docker/frontend/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM nginx:1.27.0-alpine
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY html/ /usr/share/nginx/html/
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
14
infra/docker/frontend/html/index.html
Normal file
14
infra/docker/frontend/html/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>AzioneLab</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>AzioneLab frontend placeholder</h1>
|
||||||
|
<p>The Angular application build will replace this page.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
infra/docker/frontend/nginx.conf
Normal file
11
infra/docker/frontend/nginx.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
infra/docker/nginx/templates/default.conf.template
Normal file
50
infra/docker/nginx/templates/default.conf.template
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
upstream azionelab_backend {
|
||||||
|
server ${BACKEND_HOST}:${BACKEND_PORT};
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream azionelab_frontend {
|
||||||
|
server ${FRONTEND_HOST}:${FRONTEND_PORT};
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
client_max_body_size 10m;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://azionelab_backend;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /admin/ {
|
||||||
|
proxy_pass http://azionelab_backend;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /static/ {
|
||||||
|
proxy_pass http://azionelab_backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /media/ {
|
||||||
|
proxy_pass http://azionelab_backend;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://azionelab_frontend;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user