# TimeSafari Docker Build # Author: Matthew Raymer # Description: Multi-stage Docker build for TimeSafari web application # # Build Process: # 1. Base stage: Node.js with build dependencies # 2. Builder stage: Copy pre-built web assets from host # 3. Production stage: Nginx server with optimized assets # # Note: Web assets are built on the host using npm scripts before Docker build # # Security Features: # - Non-root user execution # - Minimal attack surface with Alpine Linux # - Multi-stage build to reduce image size # - No build dependencies in final image # # Usage: # IMPORTANT: Build web assets first, then build Docker image # # Using npm scripts (recommended): # Production: npm run build:web:docker:prod # Test: npm run build:web:docker:test # Development: npm run build:web:docker # # Manual workflow: # 1. Build web assets: npm run build:web:build -- --mode production # 2. Build Docker: docker build -t timesafari:latest . # # Note: For development, use npm run build:web directly (no Docker needed) # # Build Arguments: # BUILD_MODE: development, test, or production (default: production) # NODE_ENV: node environment (default: production) # # Environment Variables: # NODE_ENV: Build environment (development/production) # BUILD_MODE: Build mode for asset selection (development/test/production) # ============================================================================= # BASE STAGE - Common dependencies and setup # ============================================================================= FROM node:22-alpine3.20 AS base # Install system dependencies for build process RUN apk add --no-cache \ bash \ git \ python3 \ py3-pip \ py3-setuptools \ make \ g++ \ gcc \ && rm -rf /var/cache/apk/* # Create non-root user for security RUN addgroup -g 1001 -S nodejs && \ adduser -S nextjs -u 1001 # Set working directory WORKDIR /app # Copy package files for dependency installation COPY package*.json ./ # Install dependencies with security audit RUN npm ci --only=production --audit --fund=false && \ npm audit fix --audit-level=moderate || true # ============================================================================= # BUILDER STAGE - Copy pre-built assets # ============================================================================= FROM base AS builder # Define build arguments with defaults ARG BUILD_MODE=production ARG NODE_ENV=production # Set environment variables from build arguments ENV BUILD_MODE=${BUILD_MODE} ENV NODE_ENV=${NODE_ENV} # Copy pre-built assets from host COPY dist/ ./dist/ # Verify build output exists RUN ls -la dist/ || (echo "Build output not found in dist/ directory" && exit 1) # ============================================================================= # PRODUCTION STAGE - Nginx server # ============================================================================= FROM nginx:alpine AS production # Define build arguments for production stage ARG BUILD_MODE=production ARG NODE_ENV=production # Set environment variables ENV BUILD_MODE=${BUILD_MODE} ENV NODE_ENV=${NODE_ENV} # Install security updates and clean cache RUN apk update && \ apk upgrade && \ apk add --no-cache \ curl \ && rm -rf /var/cache/apk/* # Create non-root user for nginx RUN addgroup -g 1001 -S nginx && \ adduser -S nginx -u 1001 -G nginx # Copy appropriate nginx configuration based on build mode COPY docker/nginx.conf /etc/nginx/nginx.conf COPY docker/default.conf /etc/nginx/conf.d/default.conf # Copy staging configuration if needed COPY docker/staging.conf /etc/nginx/conf.d/staging.conf # Copy built assets from builder stage COPY --from=builder --chown=nginx:nginx /app/dist /usr/share/nginx/html # Create necessary directories with proper permissions RUN mkdir -p /var/cache/nginx /var/log/nginx /var/run && \ chown -R nginx:nginx /var/cache/nginx /var/log/nginx /var/run && \ chown -R nginx:nginx /usr/share/nginx/html # Switch to non-root user USER nginx # Expose port 80 EXPOSE 80 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost/ || exit 1 # Start nginx with proper signal handling CMD ["nginx", "-g", "daemon off;"] # ============================================================================= # TEST STAGE - For test environment testing # ============================================================================= FROM production AS test # Define build arguments for test stage ARG BUILD_MODE=test ARG NODE_ENV=test # Set environment variables ENV BUILD_MODE=${BUILD_MODE} ENV NODE_ENV=${NODE_ENV} # Copy test-specific nginx configuration COPY docker/staging.conf /etc/nginx/conf.d/default.conf # Expose port 80 EXPOSE 80 # Health check for staging HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost/health || exit 1 # Start nginx CMD ["nginx", "-g", "daemon off;"]