From c62af10b022b6c4ba5a7fe410372512ad949dc4e Mon Sep 17 00:00:00 2001 From: "sirin.ph" Date: Thu, 20 Nov 2025 17:13:55 +0700 Subject: [PATCH] add docker compose --- .dockerignore | 55 +++++++ .env.example | 6 + DOCKER_README.md | 353 +++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 35 +++++ Dockerfile.dev | 23 +++ docker-compose.yml | 51 +++++++ nginx.conf | 35 +++++ 7 files changed, 558 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 DOCKER_README.md create mode 100644 Dockerfile create mode 100644 Dockerfile.dev create mode 100644 docker-compose.yml create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..26ce508 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,55 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build output +dist +build +.vite + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ +.DS_Store + +# Git +.git +.gitignore +.gitattributes + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Documentation +README.md +STYLE_GUIDE.md +*.md + +# Testing +coverage +.nyc_output +*.test.ts +*.test.tsx +*.spec.ts +*.spec.tsx + +# Misc +.cache +*.log +tmp +temp diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5feee5f --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Gemini API Key +# Get your API key from: https://aistudio.google.com/app/apikey +GEMINI_API_KEY=your_gemini_api_key_here + +# Node Environment (development | production) +NODE_ENV=development diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 0000000..9ad1f7d --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,353 @@ +# Docker Deployment Guide + +This guide explains how to deploy the Pradit application using Docker and Docker Compose. + +## Prerequisites + +- Docker Engine 20.10+ +- Docker Compose 2.0+ +- Gemini API Key (get from https://aistudio.google.com/app/apikey) + +## Quick Start + +### 1. Setup Environment Variables + +Copy the example environment file and add your Gemini API key: + +```bash +cp .env.example .env +``` + +Edit `.env` and set your `GEMINI_API_KEY`: + +``` +GEMINI_API_KEY=your_actual_api_key_here +``` + +### 2. Run Development Mode + +Development mode includes hot reload for rapid iteration: + +```bash +docker-compose --profile dev up +``` + +Access the app at: http://localhost:3000 + +### 3. Run Production Mode + +Production mode uses optimized build with nginx: + +```bash +docker-compose --profile prod up -d +``` + +Access the app at: http://localhost:80 + +## Docker Compose Profiles + +This project uses Docker Compose profiles to separate dev and prod environments. + +### Development Profile (`dev`) + +- **Port**: 3000 +- **Hot Reload**: Enabled +- **Volume Mounts**: Source code mounted for live changes +- **Server**: Vite dev server +- **Optimizations**: None (fast rebuild) + +**Commands**: +```bash +# Start dev server +docker-compose --profile dev up + +# Start in background +docker-compose --profile dev up -d + +# View logs +docker-compose --profile dev logs -f + +# Stop +docker-compose --profile dev down +``` + +### Production Profile (`prod`) + +- **Port**: 80 +- **Hot Reload**: Disabled +- **Build**: Optimized static build +- **Server**: Nginx with compression and caching +- **Health Check**: Enabled on /health endpoint + +**Commands**: +```bash +# Build and start production +docker-compose --profile prod up -d + +# Rebuild after code changes +docker-compose --profile prod up -d --build + +# View logs +docker-compose --profile prod logs -f + +# Stop +docker-compose --profile prod down +``` + +## Manual Docker Commands + +### Build Images + +```bash +# Development image +docker build -f Dockerfile.dev -t pradit:dev . + +# Production image +docker build -f Dockerfile -t pradit:prod . +``` + +### Run Containers + +```bash +# Development +docker run -p 3000:3000 -v $(pwd):/app -v /app/node_modules \ + -e GEMINI_API_KEY=your_key pradit:dev + +# Production +docker run -p 80:80 pradit:prod +``` + +## Architecture + +### Development Container + +- **Base**: node:20-alpine +- **Package Manager**: pnpm +- **Volumes**: Source code mounted for hot reload +- **Port**: 3000 +- **Command**: `pnpm run dev` + +### Production Container + +Multi-stage build for minimal image size: + +1. **Builder Stage**: + - Installs dependencies with pnpm + - Builds optimized production bundle + - Uses node:20-alpine + +2. **Runtime Stage**: + - Serves static files with nginx:alpine + - Configured for SPA routing + - Gzip compression enabled + - Security headers added + - Health check endpoint at `/health` + +## Configuration Files + +### Dockerfile +Production multi-stage build configuration. + +### Dockerfile.dev +Development container with hot reload support. + +### docker-compose.yml +Orchestration for both dev and prod profiles. + +### nginx.conf +Nginx configuration for production: +- SPA routing (serves index.html for all routes) +- Gzip compression +- Static asset caching (1 year) +- Security headers +- Health check endpoint + +### .dockerignore +Excludes unnecessary files from Docker build context: +- node_modules +- Build artifacts +- Environment files +- IDE configurations +- Documentation + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `GEMINI_API_KEY` | Yes | API key for Gemini AI | +| `NODE_ENV` | No | Environment mode (development/production) | + +**Security Note**: Never commit `.env` file. Use `.env.example` as template. + +## Port Configuration + +| Service | Port | Description | +|---------|------|-------------| +| Development | 3000 | Vite dev server | +| Production | 80 | Nginx web server | + +To use different ports, edit `docker-compose.yml`: + +```yaml +ports: + - "8080:80" # Host:Container +``` + +## Troubleshooting + +### Port Already in Use + +```bash +# Find process using port 3000 +netstat -ano | findstr :3000 # Windows +lsof -i :3000 # Linux/Mac + +# Change port in docker-compose.yml +ports: + - "3001:3000" +``` + +### API Key Not Working + +Ensure `.env` file exists and contains valid key: +```bash +cat .env # Linux/Mac +type .env # Windows +``` + +Restart containers after changing `.env`: +```bash +docker-compose --profile dev down +docker-compose --profile dev up +``` + +### Container Won't Start + +Check logs for errors: +```bash +docker-compose --profile dev logs +docker-compose --profile prod logs +``` + +Remove containers and rebuild: +```bash +docker-compose down -v +docker-compose --profile prod up -d --build +``` + +### Volume Permission Issues (Linux) + +If encountering permission issues with mounted volumes: +```bash +# Add user ID to docker-compose.yml +user: "${UID}:${GID}" +``` + +### Build Cache Issues + +Clear Docker build cache: +```bash +docker builder prune -a +docker-compose build --no-cache +``` + +## Health Checks + +Production container includes health check: + +```bash +# Check container health +docker ps + +# Manual health check +curl http://localhost/health +``` + +## Logs and Debugging + +```bash +# Follow logs +docker-compose logs -f + +# Specific service logs +docker-compose --profile dev logs pradit-dev +docker-compose --profile prod logs pradit-prod + +# Enter running container +docker exec -it pradit-dev sh +docker exec -it pradit-prod sh +``` + +## Cleaning Up + +```bash +# Stop and remove containers +docker-compose down + +# Remove containers and volumes +docker-compose down -v + +# Remove images +docker rmi pradit:dev pradit:prod + +# Full cleanup (all unused Docker resources) +docker system prune -a +``` + +## Production Deployment + +For cloud deployment (AWS, GCP, Azure, etc.): + +1. Build and tag image: +```bash +docker build -t your-registry/pradit:latest . +``` + +2. Push to registry: +```bash +docker push your-registry/pradit:latest +``` + +3. Deploy to orchestration platform (Kubernetes, ECS, etc.) + +## Performance Optimization + +### Production Build + +- Tree shaking removes unused code +- Minification reduces bundle size +- Gzip compression (nginx) +- Static asset caching (1 year) +- Multi-stage build minimizes image size + +### Development Build + +- Fast rebuild with hot module replacement +- Source maps for debugging +- No minification for faster builds + +## Security Best Practices + +1. **Never commit API keys** - Use environment variables +2. **Use multi-stage builds** - Minimize attack surface +3. **Run as non-root** - Production nginx runs as nginx user +4. **Keep images updated** - Regularly update base images +5. **Scan for vulnerabilities** - Use `docker scan pradit:prod` +6. **Use secrets management** - For production, use Docker secrets or cloud provider solutions + +## Additional Resources + +- [Docker Documentation](https://docs.docker.com/) +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [Vite Documentation](https://vitejs.dev/) +- [Nginx Documentation](https://nginx.org/en/docs/) + +## Support + +For issues specific to Docker setup, check: +1. Docker daemon is running +2. Environment variables are set correctly +3. Ports are not already in use +4. Sufficient disk space for images and containers + +For application issues, refer to main [README.md](README.md). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2f2d921 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Build stage +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml ./ + +# Install pnpm +RUN npm install -g pnpm + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN pnpm run build + +# Production stage +FROM nginx:alpine + +# Copy built assets from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..1107dd8 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,23 @@ +# Development Dockerfile +FROM node:20-alpine + +# Set working directory +WORKDIR /app + +# Install pnpm globally +RUN npm install -g pnpm + +# Copy package files +COPY package.json pnpm-lock.yaml ./ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY . . + +# Expose development port +EXPOSE 3000 + +# Start development server with hot reload +CMD ["pnpm", "run", "dev"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f37b657 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3.8' + +services: + # Development service + pradit-dev: + build: + context: . + dockerfile: Dockerfile.dev + container_name: pradit-dev + profiles: + - dev + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development + - GEMINI_API_KEY=${GEMINI_API_KEY} + command: pnpm run dev + networks: + - pradit-network + restart: unless-stopped + + # Production service + pradit-prod: + build: + context: . + dockerfile: Dockerfile + args: + - GEMINI_API_KEY=${GEMINI_API_KEY} + container_name: pradit-prod + profiles: + - prod + ports: + - "80:80" + environment: + - NODE_ENV=production + networks: + - pradit-network + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +networks: + pradit-network: + driver: bridge diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..666f746 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,35 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json; + + # 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; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA routing - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +}