Files
FullStackTemplate/Dockerfile
Christian Schindler 4a6b4a0ae8 feat: Enhance security and validation in backend
- Added helmet for security headers and configured content security policy
- Implemented CORS with a whitelist for allowed origins
- Introduced express-validator for input validation in API endpoints
- Set request size limits to prevent DoS attacks
- Added global error handling and 404 response
- Updated TypeScript configuration to use node16 module resolution
- Improved Docker Compose configuration for security and resource limits
- Created a comprehensive .env.example for environment configuration
- Implemented automated security scans in CI/CD with Trivy
- Added cleanup script for debugging ports
- Established a detailed security policy document
2025-12-01 08:37:35 +01:00

272 lines
9.1 KiB
Docker

# Multi-Stage Dockerfile für Full Stack TypeScript Template
# Optimiert für Produktion mit Nginx + Node.js Backend für Traefik Deployment
# =============================================================================
# Build Stage für Frontend
# =============================================================================
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
# Package files kopieren für besseres Caching
COPY frontend/package*.json ./
RUN npm ci && npm cache clean --force
# Source Code kopieren und builden
COPY frontend/ ./
RUN npm run build
# =============================================================================
# Build Stage für Backend
# =============================================================================
FROM node:18-alpine AS backend-builder
WORKDIR /app/backend
# Package files kopieren
COPY backend/package*.json ./
RUN npm install
# Source Code kopieren und builden
COPY backend/ ./
RUN npm run build
# =============================================================================
# Production Stage mit Nginx + Node.js + Supervisor
# =============================================================================
FROM node:18-alpine AS production
# Build Arguments
ARG NODE_ENV=production
ARG BUILD_VERSION=unknown
# Environment Variables setzen
ENV NODE_ENV=${NODE_ENV}
ENV BUILD_VERSION=${BUILD_VERSION}
ENV TZ=Europe/Berlin
# System-Dependencies installieren
RUN apk add --no-cache \
nginx \
supervisor \
curl \
tzdata \
tini \
su-exec \
shadow \
&& rm -rf /var/cache/apk/*
# Non-root User erstellen (UID/GID 1000 für Kompatibilität)
RUN addgroup -g 1000 appuser \
&& adduser -D -u 1000 -G appuser appuser \
&& mkdir -p /var/www/html \
&& mkdir -p /app/backend/dist \
&& mkdir -p /etc/supervisor.d \
&& mkdir -p /data \
&& mkdir -p /var/log/nginx \
&& mkdir -p /var/lib/nginx \
&& mkdir -p /var/tmp/nginx \
&& mkdir -p /run/nginx
# Frontend Build von Builder Stage kopieren
COPY --from=frontend-builder /app/frontend/build /var/www/html
# Backend Build und Dependencies kopieren
COPY --from=backend-builder /app/backend/dist /app/backend/dist
COPY --from=backend-builder /app/backend/node_modules /app/backend/node_modules
# Nginx Konfiguration erstellen (non-root optimiert)
RUN printf 'worker_processes auto;\n\
error_log /var/log/nginx/error.log warn;\n\
pid /run/nginx/nginx.pid;\n\
\n\
events {\n\
worker_connections 1024;\n\
use epoll;\n\
multi_accept on;\n\
}\n\
\n\
http {\n\
include /etc/nginx/mime.types;\n\
default_type application/octet-stream;\n\
\n\
# Temp-Pfade für non-root\n\
client_body_temp_path /var/tmp/nginx/client_body;\n\
proxy_temp_path /var/tmp/nginx/proxy;\n\
fastcgi_temp_path /var/tmp/nginx/fastcgi;\n\
uwsgi_temp_path /var/tmp/nginx/uwsgi;\n\
scgi_temp_path /var/tmp/nginx/scgi;\n\
\n\
# Logging\n\
log_format main '\''$remote_addr - $remote_user [$time_local] "$request" '\''\n\
'\''$status $body_bytes_sent "$http_referer" '\''\n\
'\''"$http_user_agent" "$http_x_forwarded_for"'\'';\n\
access_log /var/log/nginx/access.log main;\n\
\n\
# Performance\n\
sendfile on;\n\
tcp_nopush on;\n\
tcp_nodelay on;\n\
keepalive_timeout 65;\n\
gzip on;\n\
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\
\n\
# Moderne Security Headers\n\
add_header X-Frame-Options "SAMEORIGIN" always;\n\
add_header X-Content-Type-Options "nosniff" always;\n\
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n\
add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\''; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data: https:; font-src '\''self'\'' data:; connect-src '\''self'\'' https:; frame-ancestors '\''self'\''; base-uri '\''self'\''; form-action '\''self'\'';" always;\n\
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;\n\
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;\n\
\n\
# Request-Limits gegen DoS\n\
client_max_body_size 50M;\n\
client_body_timeout 60s;\n\
client_header_timeout 60s;\n\
client_body_buffer_size 128k;\n\
large_client_header_buffers 4 16k;\n\
\n\
server {\n\
listen 8080;\n\
server_name _;\n\
\n\
# Health Check Endpoint\n\
location /health {\n\
access_log off;\n\
return 200 "healthy";\n\
add_header Content-Type text/plain;\n\
}\n\
\n\
# Serve Frontend (SPA)\n\
location / {\n\
root /var/www/html;\n\
index index.html;\n\
try_files $uri $uri/ /index.html;\n\
\n\
# Cache static assets\n\
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {\n\
expires 1y;\n\
add_header Cache-Control "public, immutable";\n\
}\n\
}\n\
\n\
# Proxy API requests to backend\n\
location /api/ {\n\
proxy_pass http://127.0.0.1:3001/api/;\n\
proxy_http_version 1.1;\n\
proxy_set_header Upgrade $http_upgrade;\n\
proxy_set_header Connection "upgrade";\n\
proxy_set_header Host $host;\n\
proxy_set_header X-Real-IP $remote_addr;\n\
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\
proxy_set_header X-Forwarded-Proto $scheme;\n\
proxy_set_header X-Forwarded-Host $host;\n\
proxy_cache_bypass $http_upgrade;\n\
\n\
# Timeouts\n\
proxy_connect_timeout 60s;\n\
proxy_send_timeout 60s;\n\
proxy_read_timeout 60s;\n\
\n\
# Buffer-Limits\n\
proxy_buffer_size 4k;\n\
proxy_buffers 8 4k;\n\
proxy_busy_buffers_size 8k;\n\
}\n\
}\n\
}' > /etc/nginx/nginx.conf
# Supervisor Konfiguration erstellen (non-root)
RUN printf '[supervisord]\n\
nodaemon=true\n\
loglevel=info\n\
logfile=/dev/stdout\n\
logfile_maxbytes=0\n\
user=appuser\n\
\n\
[program:nginx]\n\
command=/usr/sbin/nginx -g "daemon off;"\n\
autorestart=true\n\
startretries=3\n\
stdout_logfile=/dev/stdout\n\
stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=10\n\
user=appuser\n\
\n\
[program:backend]\n\
command=node /app/backend/dist/index.js\n\
directory=/app/backend\n\
environment=NODE_ENV="production",DATA_DIR="/data",PORT="3001"\n\
autorestart=true\n\
startretries=3\n\
stdout_logfile=/dev/stdout\n\
stdout_logfile_maxbytes=0\n\
stderr_logfile=/dev/stderr\n\
stderr_logfile_maxbytes=0\n\
priority=20\n\
user=appuser\n' > /etc/supervisor.d/supervisord.ini
# Health Check Script erstellen
RUN printf '#!/bin/sh\n\
# Health check for both nginx and backend\n\
nginx_status=$(curl -f -s http://localhost:8080/health > /dev/null && echo "ok" || echo "fail")\n\
backend_status=$(curl -f -s http://localhost:3001/api/health > /dev/null && echo "ok" || echo "fail")\n\
\n\
if [ "$nginx_status" = "ok" ] && [ "$backend_status" = "ok" ]; then\n\
exit 0\n\
else\n\
echo "Health check failed: nginx=$nginx_status, backend=$backend_status"\n\
exit 1\n\
fi' > /usr/local/bin/healthcheck.sh && chmod +x /usr/local/bin/healthcheck.sh
# Berechtigungen setzen für non-root User
RUN chown -R appuser:appuser \
/data \
/app/backend \
/var/www/html \
/var/log/nginx \
/var/lib/nginx \
/var/tmp/nginx \
/run/nginx \
/etc/nginx \
/usr/local/bin/healthcheck.sh
# Health Check konfigurieren
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD /usr/local/bin/healthcheck.sh
# Arbeitsverzeichnis setzen
WORKDIR /app
# Exponiere Port 8080 (non-root) statt 80
EXPOSE 8080
# Wechsle zu non-root User
USER appuser
# Startup mit Tini für proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]
# =============================================================================
# Traefik Labels für automatisches Service Discovery
# =============================================================================
LABEL traefik.enable=true
LABEL traefik.http.routers.app.rule="Host(\`your-domain.com\`)"
LABEL traefik.http.routers.app.entrypoints=websecure
LABEL traefik.http.routers.app.tls.certresolver=letsencrypt
LABEL traefik.http.services.app.loadbalancer.server.port=8080
# =============================================================================
# Metadata Labels
# =============================================================================
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="${BUILD_VERSION}"
LABEL description="Full Stack TypeScript Template - Traefik Ready"
LABEL org.opencontainers.image.source="https://github.com/your-org/fullstack-typescript-template"
LABEL org.opencontainers.image.documentation="https://github.com/your-org/fullstack-typescript-template#readme"
LABEL org.opencontainers.image.vendor="Your Organization"
LABEL org.opencontainers.image.licenses="MIT"