[INFRA-002] infra: Set up Docker production images
This commit is contained in:
parent
45d28fe386
commit
3c4ba9cba4
3 changed files with 154 additions and 3 deletions
|
|
@ -11190,8 +11190,11 @@
|
|||
"description": "Create optimized Docker images for production deployment",
|
||||
"owner": "devops",
|
||||
"estimated_hours": 4,
|
||||
"status": "todo",
|
||||
"files_involved": [],
|
||||
"status": "completed",
|
||||
"files_involved": [
|
||||
"apps/web/Dockerfile.production",
|
||||
"apps/web/nginx.production.conf"
|
||||
],
|
||||
"implementation_steps": [
|
||||
{
|
||||
"step": 1,
|
||||
|
|
@ -11211,7 +11214,18 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completed_at": "2025-12-25T21:31:19.086714",
|
||||
"validation": {
|
||||
"dockerfile_syntax": "Valid",
|
||||
"production_dockerfiles": "All services have optimized production Dockerfiles",
|
||||
"frontend_dockerfile": "apps/web/Dockerfile.production - Created with nginx optimization",
|
||||
"nginx_config": "apps/web/nginx.production.conf - Created with security headers and caching",
|
||||
"backend_dockerfile": "veza-backend-api/Dockerfile.production - Already optimized",
|
||||
"chat_dockerfile": "veza-chat-server/Dockerfile.production - Already optimized",
|
||||
"stream_dockerfile": "veza-stream-server/Dockerfile.production - Already optimized",
|
||||
"optimizations": "Multi-stage builds, non-root users, health checks, minimal base images"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "INFRA-003",
|
||||
|
|
|
|||
81
apps/web/Dockerfile.production
Normal file
81
apps/web/Dockerfile.production
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Production Dockerfile for Frontend Web App
|
||||
# Optimized for smaller size, security, and performance
|
||||
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies only
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
# Copy package files first for better caching
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies (this layer will be cached if package*.json don't change)
|
||||
RUN npm ci --only=production=false && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build arguments for environment variables
|
||||
ARG VITE_API_URL
|
||||
ARG VITE_WS_URL
|
||||
ARG VITE_STREAM_URL
|
||||
ARG VITE_APP_ENV=production
|
||||
|
||||
# Set build-time environment variables
|
||||
ENV VITE_API_URL=${VITE_API_URL}
|
||||
ENV VITE_WS_URL=${VITE_WS_URL}
|
||||
ENV VITE_STREAM_URL=${VITE_STREAM_URL}
|
||||
ENV VITE_APP_ENV=${VITE_APP_ENV}
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Build the application with optimizations
|
||||
RUN npm run build && \
|
||||
# Verify build output exists
|
||||
test -f dist/index.html || { echo "ERROR: dist/index.html not found after build!"; exit 1; } && \
|
||||
# Remove source maps in production (optional, for smaller size)
|
||||
find dist -name "*.map" -delete && \
|
||||
# Show build summary
|
||||
echo "✅ Build successful - dist/ contains $(find dist -type f | wc -l) files" && \
|
||||
du -sh dist/
|
||||
|
||||
# Production stage - nginx alpine
|
||||
FROM nginx:alpine
|
||||
|
||||
# Install minimal dependencies for healthcheck
|
||||
RUN apk add --no-cache wget && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
# Remove default nginx config
|
||||
RUN rm -rf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy custom nginx configuration optimized for production
|
||||
COPY nginx.production.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy built assets from builder
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Create health check endpoint
|
||||
RUN echo '#!/bin/sh' > /usr/share/nginx/html/health && \
|
||||
echo 'exit 0' >> /usr/share/nginx/html/health && \
|
||||
chmod +x /usr/share/nginx/html/health
|
||||
|
||||
# Set proper permissions
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chmod -R 755 /usr/share/nginx/html
|
||||
|
||||
# Nginx alpine image already runs as non-root user (nginx)
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1
|
||||
|
||||
# Run nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
56
apps/web/nginx.production.conf
Normal file
56
apps/web/nginx.production.conf
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Production nginx configuration for Veza Frontend
|
||||
# Optimized for performance and security
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/x-font-ttf application/vnd.ms-fontobject font/opentype image/svg+xml image/x-icon;
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# SPA routing - serve index.html for all routes
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
# Don't cache index.html
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires "0";
|
||||
}
|
||||
|
||||
# Security: deny access to hidden files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue