From 56eaf8c9b5ce77c0d80e45946024db87eb8407c6 Mon Sep 17 00:00:00 2001 From: happybell80 Date: Mon, 7 Jul 2025 21:52:52 +0900 Subject: [PATCH] Initial nginx-infra setup with GitOps deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add GitHub Actions CI/CD pipeline - Configure Nginx reverse proxy for robeing services - Setup docker-compose for full stack deployment - Include health checks for all services - Support for PostgreSQL and Redis data persistence πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/deploy.yml | 44 ++++++++++++++ README.md | 68 ++++++++++++++++++++++ docker-compose.yml | 109 +++++++++++++++++++++++++++++++++++ nginx/Dockerfile | 21 +++++++ nginx/nginx.conf | 96 ++++++++++++++++++++++++++++++ 5 files changed, 338 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 nginx/Dockerfile create mode 100644 nginx/nginx.conf diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2f76fcb --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,44 @@ +name: Deploy Nginx + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Nginx + uses: docker/build-push-action@v5 + with: + context: ./nginx + push: true + tags: | + ${{ secrets.DOCKER_USERNAME }}/robeing-nginx:latest + ${{ secrets.DOCKER_USERNAME }}/robeing-nginx:${{ github.sha }} + + - name: Update docker-compose.yml + run: | + sed -i "s|image: .*/robeing-nginx:.*|image: ${{ secrets.DOCKER_USERNAME }}/robeing-nginx:${{ github.sha }}|g" docker-compose.yml + + - name: Commit and push changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add docker-compose.yml + git commit -m "Update nginx image to ${{ github.sha }}" || exit 0 + git push \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..91e175b --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Nginx Deploy - Robeing Project + +GitOps λ°©μ‹μœΌλ‘œ κ΄€λ¦¬λ˜λŠ” Robeing ν”„λ‘œμ νŠΈμ˜ 배포 λ§€λ‹ˆνŽ˜μŠ€νŠΈ μ €μž₯μ†Œμž…λ‹ˆλ‹€. + +## ꡬ쑰 + +``` +nginx-deploy/ +β”œβ”€β”€ .github/workflows/deploy.yml # GitHub Actions CI/CD +β”œβ”€β”€ nginx/ +β”‚ β”œβ”€β”€ nginx.conf # Nginx μ„€μ • +β”‚ └── Dockerfile # Nginx μ»¨ν…Œμ΄λ„ˆ λΉŒλ“œ +└── docker-compose.yml # 전체 μ„œλΉ„μŠ€ μ •μ˜ +``` + +## 배포 ν”Œλ‘œμš° + +1. **μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ €μž₯μ†Œ**μ—μ„œ μ½”λ“œ λ³€κ²½ μ‹œ +2. **GitHub Actions**κ°€ μƒˆ 이미지 λΉŒλ“œ ν›„ Docker Hub에 push +3. **Webhook**으둜 이 μ €μž₯μ†Œμ˜ `docker-compose.yml` μ—…λ°μ΄νŠΈ +4. **μˆ˜λ™ λ˜λŠ” μžλ™**으둜 μ„œλ²„μ— 배포 + +## μ„€μ • + +### GitHub Secrets μ„€μ • +``` +DOCKER_USERNAME: Docker Hub μ‚¬μš©μžλͺ… +DOCKER_PASSWORD: Docker Hub νŒ¨μŠ€μ›Œλ“œ λ˜λŠ” 토큰 +``` + +### ν™˜κ²½ λ³€μˆ˜ μ„€μ • +```bash +# .env 파일 생성 +DATABASE_URL=postgresql://user:password@postgres:5432/robeing +REDIS_URL=redis://redis:6379/0 +POSTGRES_USER=robeing +POSTGRES_PASSWORD=your-secure-password +``` + +## 배포 방법 + +### 개발 ν™˜κ²½ +```bash +docker-compose up -d +``` + +### ν”„λ‘œλ•μ…˜ 배포 +```bash +# ν™˜κ²½ λ³€μˆ˜ μ„€μ • ν›„ +docker-compose -f docker-compose.yml up -d +``` + +## μ„œλΉ„μŠ€ ꡬ성 + +- **nginx**: λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ (포트 80, 443) +- **api-base**: API μ„œλ²„ (포트 8000) +- **test_api**: ν…ŒμŠ€νŠΈ API μ„œλ²„ (포트 8001) +- **frontend**: ν”„λ‘ νŠΈμ—”λ“œ μ„œλ²„ (포트 5173) +- **postgres**: PostgreSQL λ°μ΄ν„°λ² μ΄μŠ€ +- **redis**: Redis μΊμ‹œ + +## Health Check + +λͺ¨λ“  μ„œλΉ„μŠ€λŠ” health checkκ°€ μ„€μ •λ˜μ–΄ 있으며, `/health` μ—”λ“œν¬μΈνŠΈλ‘œ μƒνƒœ 확인 κ°€λŠ₯ν•©λ‹ˆλ‹€. + +```bash +curl http://localhost/health +``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a88f1d8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,109 @@ +version: '3.8' + +services: + nginx: + image: your-username/robeing-nginx:latest + ports: + - "80:80" + - "443:443" + depends_on: + - api-base + - test_api + - frontend + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + + api-base: + image: your-username/robeing-api-base:latest + ports: + - "8000:8000" + environment: + - NODE_ENV=production + - DATABASE_URL=${DATABASE_URL} + - REDIS_URL=${REDIS_URL} + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + + test_api: + image: your-username/robeing-test-api:latest + ports: + - "8001:8001" + environment: + - NODE_ENV=production + - DATABASE_URL=${DATABASE_URL} + - REDIS_URL=${REDIS_URL} + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8001/health"] + interval: 30s + timeout: 10s + retries: 3 + + frontend: + image: your-username/robeing-frontend:latest + ports: + - "5173:5173" + environment: + - NODE_ENV=production + - VITE_API_URL=http://localhost/api + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5173"] + interval: 30s + timeout: 10s + retries: 3 + + postgres: + image: postgres:15-alpine + environment: + - POSTGRES_DB=robeing + - POSTGRES_USER=${POSTGRES_USER:-robeing} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-robeing}"] + interval: 30s + timeout: 10s + retries: 3 + + redis: + image: redis:7-alpine + command: redis-server --appendonly yes + volumes: + - redis_data:/data + networks: + - robeing-network + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + +networks: + robeing-network: + driver: bridge + +volumes: + postgres_data: + redis_data: \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..918777a --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,21 @@ +FROM nginx:1.25-alpine + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Create log directory +RUN mkdir -p /var/log/nginx + +# Set proper permissions +RUN chown -R nginx:nginx /var/log/nginx +RUN chown -R nginx:nginx /var/cache/nginx + +# Add healthcheck +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/health || exit 1 + +# Expose port +EXPOSE 80 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..c7c6761 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,96 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Upstream servers + upstream api_backend { + server api-base:8000; + server test_api:8001; + } + + upstream frontend_backend { + server frontend:5173; + } + + # Main server block + server { + listen 80; + server_name localhost; + + # Frontend routing + location / { + proxy_pass http://frontend_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # API routing + location /api/ { + proxy_pass http://api_backend/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS headers + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Static files caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } +} \ No newline at end of file