Supprime nginx : Express sert le frontend, ports peu utilisés

- Un seul conteneur applicatif : Express sert l'API, le frontend React
  compilé (fallback SPA, cache immutable sur /assets) et les images
  /uploads (nosniff + CSP default-src 'none')
- En-têtes de sécurité (CSP stricte, X-Frame-Options DENY, etc.) portés
  de nginx vers helmet, compression gzip ajoutée
- Application exposée sur le port 8321 (APP_PORT), PostgreSQL sur
  127.0.0.1:56432 (DB_PORT, loopback uniquement pour l'admin locale)
- TRUST_PROXY pilote la confiance aux en-têtes X-Forwarded-* (désactivé
  par défaut hors reverse-proxy)
- Dockerfile multi-étages unique à la racine (build React → deps → image
  finale non-root, fs en lecture seule)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
This commit is contained in:
Claude committed 2026-07-10 19:52:52 +00:00
1 parent 96dca2a802
commit 499b753c22
15 files changed
+203 -145

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
node_modules
dist
*.log
-13
View File
@@ -1,13 +0,0 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci 2>/dev/null || npm install
COPY index.html vite.config.js ./
COPY public ./public
COPY src ./src
RUN npm run build
FROM nginxinc/nginx-unprivileged:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080
-55
View File
@@ -1,55 +0,0 @@
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
server_tokens off;
client_max_body_size 3m;
# Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
# API proxied to the backend container
location /api/ {
proxy_pass http://api:3000;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_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;
}
# Uploaded images: served directly from the shared volume, never executed
location /uploads/ {
alias /var/www/uploads/;
types { image/jpeg jpg; image/png png; image/webp webp; }
default_type application/octet-stream;
# user-uploaded content: never allow it to script or be framed
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Disposition "inline" always;
add_header Cache-Control "public, max-age=86400" always;
}
# Static assets (immutable hashed filenames from Vite)
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header X-Content-Type-Options "nosniff" always;
try_files $uri =404;
}
# SPA fallback
location / {
try_files $uri /index.html;
}
}