From 5da2a9a708a9f33575534a05f48816e29f254dd7 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Sat, 21 Mar 2026 16:23:08 +0200 Subject: [PATCH] infra: add Docker setup for portable deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dockerfile (Python 3.12 slim) + docker-compose (backend + nginx). Backend on port 8000 inside container, nginx proxies API and serves frontend static. SQLite persisted in named volume. Nginx listens on 127.0.0.1:8080 — external SSL handled by host reverse proxy. Co-Authored-By: Claude Opus 4.6 --- .dockerignore | 16 ++++++++++++ Dockerfile | 12 +++++++++ docker-compose.yml | 23 +++++++++++++++++ nginx/docker.conf | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx/docker.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c29bee9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.gitignore +.env +.venv +venv +__pycache__ +*.pyc +*.db +tests/ +docs/ +deploy/ +frontend/ +nginx/ +*.md +.kin_worktrees/ +PROGRESS.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8345948 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY backend/ backend/ + +EXPOSE 8000 + +CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f3a1680 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + backend: + build: . + restart: unless-stopped + env_file: .env + environment: + DB_PATH: /data/baton.db + volumes: + - db_data:/data + + nginx: + image: nginx:alpine + restart: unless-stopped + ports: + - "127.0.0.1:8080:80" + volumes: + - ./frontend:/usr/share/nginx/html:ro + - ./nginx/docker.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - backend + +volumes: + db_data: diff --git a/nginx/docker.conf b/nginx/docker.conf new file mode 100644 index 0000000..54df415 --- /dev/null +++ b/nginx/docker.conf @@ -0,0 +1,61 @@ +map $request_uri $masked_uri { + default $request_uri; + "~^(/bot)[^/]+(/.*)$" "$1[REDACTED]$2"; +} + +log_format baton_secure '$remote_addr - $remote_user [$time_local] ' + '"$request_method $masked_uri $server_protocol" ' + '$status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + +server { + listen 80; + server_name _; + + access_log /var/log/nginx/baton_access.log baton_secure; + error_log /var/log/nginx/baton_error.log warn; + + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'" always; + + # API + health + admin → backend + location /api/ { + proxy_pass http://backend:8000; + 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_read_timeout 30s; + proxy_send_timeout 30s; + proxy_connect_timeout 5s; + } + + location /health { + proxy_pass http://backend:8000; + 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/users { + proxy_pass http://backend:8000; + 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_read_timeout 30s; + } + + # Frontend static + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + expires 1h; + add_header Cache-Control "public" always; + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'" always; + } +}