# 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 " 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"