From d42b7cb307c386ab33785ea9cefa880dde34c38b Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Tue, 11 Nov 2025 16:59:58 +0000 Subject: [PATCH] Prod/Dev mode --- README.md | 65 ++++++++++++++++++++++++++++++-- docker-compose.yml | 21 ++++++++++- frontend/Dockerfile | 25 ++++++++++-- frontend/nginx.conf | 36 ++++++++++++++++++ frontend/src/pages/Dashboard.tsx | 2 +- frontend/vite.config.ts | 2 +- 6 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 frontend/nginx.conf diff --git a/README.md b/README.md index 167e2ce..63f0271 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,13 @@ membership/ - SMTP2GO API key (for email notifications) - Database password (if desired) -3. **Start the services**: +3. **Start the services in your preferred mode**: ```bash - docker-compose up -d + # For development (with hot reloading) + docker-compose --profile dev up -d + + # For production (optimized static files) + docker-compose --profile prod up -d ``` 4. **Wait for services to be ready** (about 30 seconds for MySQL initialization): @@ -79,10 +83,63 @@ membership/ ``` Press Ctrl+C when you see "Application startup complete" -5. **Access the API**: +5. **Access the application**: + - Frontend: http://localhost:3500 (development) or http://localhost:8080 (production) - API: http://localhost:8000 - API Documentation: http://localhost:8000/docs - - Alternative Docs: http://localhost:8000/redoc + +## Frontend Development vs Production + +The frontend supports both development and production modes using Docker Compose profiles: + +### Development Mode (Vite) +- Hot reloading and development features +- Access at: http://localhost:3500 +- Start with: `docker-compose --profile dev up -d` + +### Production Mode (Nginx) +- Optimized static files served by Nginx +- Access at: http://localhost:8080 +- Start with: `docker-compose --profile prod up -d` + +### Usage Examples + +```bash +# Start all services in development mode +docker-compose --profile dev up -d + +# Start all services in production mode +docker-compose --profile prod up -d + +# Start only the frontend in development mode +docker-compose --profile dev up -d frontend + +# Start only the frontend in production mode +docker-compose --profile prod up -d frontend-prod + +# Stop all services +docker-compose down + +# Check which services are running +docker-compose ps +``` + +### Profile Details + +- **`dev` profile**: Includes the `frontend` service (Vite dev server) +- **`prod` profile**: Includes the `frontend-prod` service (Nginx) +- **Default**: No frontend service runs unless you specify a profile + +### For Production Deployment + +When deploying to production with Caddy: + +1. Start services in production mode: + ```bash + docker-compose --profile prod up -d + ``` + +2. Configure Caddy to proxy to `localhost:8080` for the frontend and `localhost:6000` for the API ## Default Credentials diff --git a/docker-compose.yml b/docker-compose.yml index 1b15f49..f574d5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,12 +44,13 @@ services: build: context: ./frontend dockerfile: Dockerfile + target: development # Default to development container_name: membership_frontend restart: unless-stopped environment: - VITE_HOST_CHECK=false ports: - - "3500:3000" # Expose frontend to host + - "8050:3000" # Expose frontend to host volumes: - ./frontend/src:/app/src - ./frontend/public:/app/public @@ -58,6 +59,24 @@ services: - backend networks: - membership_private # Access to backend on private network + profiles: + - dev # Only run in development + + frontend-prod: + build: + context: ./frontend + dockerfile: Dockerfile + target: production + container_name: membership_frontend_prod + restart: unless-stopped + ports: + - "8050:80" # Nginx default port + depends_on: + - backend + networks: + - membership_private + profiles: + - prod # Only run in production networks: membership_private: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6e28e41..9ec3886 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,5 @@ -FROM node:18-alpine +# Multi-stage Dockerfile for development and production +FROM node:18-alpine AS base WORKDIR /app @@ -11,8 +12,24 @@ RUN npm install # Copy application files COPY . . -# Expose port +# Development stage +FROM base AS development EXPOSE 3000 - -# Start development server CMD ["npm", "run", "dev"] + +# Production build stage +FROM base AS build +RUN npm run build + +# Production stage with Nginx +FROM nginx:alpine AS production + +# Copy built files from build stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..b008728 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,36 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Handle client-side routing - serve index.html for all non-file requests + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 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; +} \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 6f20761..6d88639 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -112,7 +112,7 @@ const Dashboard: React.FC = () => { const handleUpdateUserRole = async (userId: number, newRole: string) => { try { - await userService.updateUserRole(userId, newRole); + await userService.updateUser(userId, { role: newRole }); // Reload data to reflect changes await loadData(); } catch (error) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ef125d1..d8ccb06 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ host: true, port: 3000, strictPort: true, - allowedHosts: ['sasaprod', 'localhost'], + allowedHosts: ['sasaprod', 'localhost', 'members.sasalliance.org'], watch: { usePolling: true },