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:
|
||||
|
||||
```bash
|
||||
CHANGE_ME
|
||||
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||
```
|
||||
|
||||
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;
|
||||
- `frontend`: Angular build stage or static asset build source;
|
||||
- `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.
|
||||
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
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;
|
||||
- `frontend`: Angular build source or build stage for static assets;
|
||||
- `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.
|
||||
|
||||
The initial Compose setup is located at `infra/docker/compose.yml`.
|
||||
|
||||
## Services
|
||||
|
||||
### 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.
|
||||
|
||||
At the infrastructure placeholder stage, the `frontend` service serves a static placeholder page with nginx. The Angular build will replace this placeholder later.
|
||||
|
||||
### backend
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -84,6 +90,8 @@ Generated QR codes may also be generated on demand instead of stored as files. I
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy `.env.example` to `.env` and replace all placeholder values before running or deploying the stack.
|
||||
|
||||
Required backend configuration:
|
||||
|
||||
- `DJANGO_SECRET_KEY`;
|
||||
@@ -129,21 +137,25 @@ The exact commands will be finalized when application code and Compose files are
|
||||
Expected production-style flow:
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose run --rm backend python manage.py migrate
|
||||
docker compose run --rm backend python manage.py collectstatic --noinput
|
||||
docker compose up -d
|
||||
docker compose --env-file .env -f infra/docker/compose.yml build
|
||||
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py migrate
|
||||
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py collectstatic --noinput
|
||||
docker compose --env-file .env -f infra/docker/compose.yml up -d
|
||||
```
|
||||
|
||||
Expected validation commands:
|
||||
|
||||
```bash
|
||||
docker compose config
|
||||
docker compose run --rm backend python manage.py check --deploy
|
||||
docker compose run --rm backend python manage.py test
|
||||
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||
docker compose --env-file .env -f infra/docker/compose.yml run --rm backend python manage.py check --deploy
|
||||
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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
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 should follow least privilege:
|
||||
|
||||
- expose only nginx publicly;
|
||||
- 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;
|
||||
- use explicit image tags rather than `latest`;
|
||||
- persist PostgreSQL data in a named volume;
|
||||
|
||||
@@ -7,7 +7,7 @@ All tests should run inside Docker containers.
|
||||
## Canonical test command
|
||||
|
||||
```bash
|
||||
CHANGE_ME
|
||||
docker compose --env-file .env.example -f infra/docker/compose.yml config
|
||||
```
|
||||
|
||||
## 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