From 27844ef237b0fbdbde5e4a962620fe745e97433c Mon Sep 17 00:00:00 2001 From: Lectom C Han Date: Tue, 5 Aug 2025 14:00:07 +0900 Subject: [PATCH] dockerizing for production --- .dockerignore | 17 ++++++++++++++++ .gitignore | 2 -- Dockerfile | 47 ++++++++++++++++++++++++++++++++++++++++++++ TODO.md | 8 +++++++- bff-server.js | 41 -------------------------------------- docker-compose.yml | 13 ++++++++++++ docker-entrypoint.sh | 32 +++++++++++++++++++++++++++--- nginx.conf | 36 --------------------------------- nginx.conf.template | 44 +++++++++++++++++++++++++++++++++++++++++ viewer/.env | 3 ++- 10 files changed, 159 insertions(+), 84 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 bff-server.js create mode 100644 docker-compose.yml mode change 100755 => 100644 docker-entrypoint.sh delete mode 100644 nginx.conf create mode 100644 nginx.conf.template diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e3898df --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Ignore node_modules +node_modules +viewer/node_modules + +# Ignore build artifacts +viewer/dist + +# Ignore environment files to prevent them from being baked into the image +*.env.local + +# Ignore git directory +.git + +# Ignore Docker files +Dockerfile +docker-compose.yml +.dockerignore diff --git a/.gitignore b/.gitignore index 7fcc45f..314cd33 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,3 @@ dist-ssr .env .env.local - -.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a2456b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Stage 1: Build the React application +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package manager files for the viewer app +COPY viewer/package.json viewer/pnpm-lock.yaml ./viewer/ + +# Install pnpm and dependencies +RUN npm install -g pnpm +WORKDIR /app/viewer +RUN pnpm install --frozen-lockfile + +# Copy the rest of the application source code +COPY viewer/ ./ + +RUN unset VITE_API_PROXY_TARGET +RUN unset VITE_API_PROXY_TARGET +# Build the application. Vite will automatically use the .env file we just created. +RUN pnpm build + +# Stage 2: Serve the application with Nginx +FROM nginx:1.27-alpine + + +# Remove the default Nginx configuration +RUN rm /etc/nginx/conf.d/default.conf + +# Copy the build output from the builder stage to the Nginx html directory +COPY --from=builder /app/viewer/dist /usr/share/nginx/html + +# Copy the Nginx configuration template and the entrypoint script +COPY nginx.conf.template /etc/nginx/templates/ +COPY docker-entrypoint.sh / + +# Make the entrypoint script executable +RUN chmod +x /docker-entrypoint.sh + +# Set the entrypoint +ENTRYPOINT ["/docker-entrypoint.sh"] + +# Expose port 80 +EXPOSE 80 + +# Start Nginx in the foreground +CMD ["nginx", "-g", "daemon off;"] diff --git a/TODO.md b/TODO.md index e34a61b..abc20f5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,9 @@ +### 2025-08-05 13:56:51 KST +- **Docker 환경 Nginx 프록시 오류 해결**: Docker 컨테이너 환경에서 Nginx가 API 서버로 요청을 올바르게 프록시하지 못하던 404 오류를 해결했습니다. + - **원인 분석**: `proxy_pass`에 `http://`와 경로가 포함된 환경 변수가 그대로 사용되어 `upstream` 설정 오류가 발생하고, 경로가 잘못 조합되는 문제를 확인했습니다. + - **해결**: `docker-entrypoint.sh` 스크립트에서 기존 `VITE_API_PROXY_TARGET` 변수를 `VITE_API_HOST` (호스트:포트)와 `VITE_API_DIR` (경로)로 분리하도록 수정했습니다. + - **Nginx 설정 수정**: `nginx.conf.template`에서 `upstream` 블록은 `${VITE_API_HOST}`를 사용하고, `proxy_pass`에서는 `${VITE_API_DIR}`를 사용하여 최종 경로를 조합하도록 변경하여 문제를 근본적으로 해결했습니다. + ### 2025-08-05 11:27:58 KST - **빌드 오류 수정**: `pnpm build` 시 발생하던 12개의 타입스크립트 오류를 모두 해결하여 빌드 프로세스를 안정화했습니다. - `DynamicForm`: `value` prop의 타입 불일치 오류를 해결했습니다. @@ -34,4 +40,4 @@ - **UI/UX 개선**: - `DynamicTable`의 검색창과 카드 간 여백 조정. - `IssueDetailCard`의 제목, 레이블, 컨텐츠 스타일을 개선하여 가독성 향상. - - **접근성(a11y) 수정**: `DynamicTable`의 컬럼 리사이저에 `slider` 역할을 부여하여 웹 접근성 lint 오류 해결. + - **접근성(a11y) 수정**: `DynamicTable`의 컬럼 리사이저에 `slider` 역할을 부여하여 웹 접근성 lint 오류 해결. \ No newline at end of file diff --git a/bff-server.js b/bff-server.js deleted file mode 100644 index ecd09c5..0000000 --- a/bff-server.js +++ /dev/null @@ -1,41 +0,0 @@ -const express = require('express'); -const morgan = require('morgan'); -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const app = express(); - -// Configuration -const PORT = process.env.PORT || 3001; -const API_SERVICE_URL = process.env.VITE_API_PROXY_TARGET; -const API_KEY = process.env.VITE_API_KEY; - -if (!API_SERVICE_URL) { - throw new Error('VITE_API_PROXY_TARGET environment variable is not set.'); -} -if (!API_KEY) { - throw new Error('VITE_API_KEY environment variable is not set.'); -} - -// Logging -app.use(morgan('dev')); - -// Proxy middleware -app.use('/api', createProxyMiddleware({ - target: API_SERVICE_URL, - changeOrigin: true, - pathRewrite: { - '^/api': '', // remove /api prefix when forwarding to the target - }, - onProxyReq: (proxyReq, req, res) => { - // Add the API key to the request header - proxyReq.setHeader('X-API-KEY', API_KEY); - }, - onError: (err, req, res) => { - console.error('Proxy error:', err); - res.status(500).send('Proxy error'); - } -})); - -app.listen(PORT, () => { - console.log(`BFF server listening on port ${PORT}`); -}); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9939088 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + qna-viewer: + build: + context: . + dockerfile: Dockerfile + env_file: + # Also load the .env file for the runtime container environment (for the entrypoint script). + - viewer/.env + ports: + # Map port on the host to port 80 in the container + - "8073:80" + restart: unless-stopped + container_name: qna-viewer-react diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh old mode 100755 new mode 100644 index 79a6655..efe90be --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,8 +3,34 @@ # Exit immediately if a command exits with a non-zero status. set -e -# Substitute environment variables in the nginx template -envsubst '${VITE_API_PROXY_TARGET}' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf +echo "--- Docker Entrypoint Script Started ---" +echo "Listing all environment variables:" +printenv +echo "----------------------------------------" -# Execute the command passed to this script, e.g., "nginx -g 'daemon off;'" +# Check if the required environment variables are set. Exit with an error if they are not. +: "${VITE_API_PROXY_TARGET:?Error: VITE_API_PROXY_TARGET is not set. Please check your .env file and docker-compose.yml}" +: "${VITE_API_KEY:?Error: VITE_API_KEY is not set. Please check your .env file and docker-compose.yml}" + +# Extract host and directory from VITE_API_PROXY_TARGET +export VITE_API_HOST=$(echo $VITE_API_PROXY_TARGET | sed -e 's,http://\([^/]*\).*$,\1,g') +export VITE_API_DIR=$(echo $VITE_API_PROXY_TARGET | sed -e 's,http://[^/]*\(/.*\)$,\1,g' -e 's,/$,,') + +echo "Extracted VITE_API_HOST: ${VITE_API_HOST}" +echo "Extracted VITE_API_DIR: ${VITE_API_DIR}" + +# Define the template and output file paths +TEMPLATE_FILE="/etc/nginx/templates/nginx.conf.template" +OUTPUT_FILE="/etc/nginx/conf.d/default.conf" + +# Substitute environment variables in the template file. +envsubst '${VITE_API_HOST} ${VITE_API_DIR} ${VITE_API_KEY}' < "$TEMPLATE_FILE" > "$OUTPUT_FILE" + + +echo "Nginx configuration generated successfully. Content:" +echo "----------------------------------------" +cat "$OUTPUT_FILE" +echo "----------------------------------------" + +# Execute the command passed to this script (e.g., "nginx -g 'daemon off;'") exec "$@" diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index 1b48470..0000000 --- a/nginx.conf +++ /dev/null @@ -1,36 +0,0 @@ -user nginx; -worker_processes auto; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - gzip on; - - server { - listen 80; - server_name localhost; - root /app/dist; # React app's build output - index index.html; - - # Proxy API requests to the internal BFF server - location /api { - proxy_pass http://localhost:3001; # BFF server is running on port 3001 - 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; - } - - # Serve React app for all other requests - location / { - try_files $uri $uri/ /index.html; - } - } -} diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..4b8611d --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,44 @@ +upstream api_back { + server ${VITE_API_HOST}; + } + +server { + listen 80; + server_name localhost; + + # Root directory for static files + root /usr/share/nginx/html; + index index.html; + + # Proxy API requests + location /api/ { + # IMPORTANT: The resolver is necessary for Nginx to resolve DNS inside a Docker container + # when using variables in proxy_pass. 127.0.0.11 is Docker's internal DNS server. + # resolver 127.0.0.11; + + # Set headers for the proxied request + 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; + + # Inject the custom API key header + proxy_set_header X-API-KEY '${VITE_API_KEY}'; + + # Use environment variables for the proxy target and API key. + # These will be substituted by envsubst in the entrypoint script. + proxy_pass http://api_back${VITE_API_DIR}/api/; + } + + # Serve static files directly + location / { + # Fallback to index.html for Single Page Application (SPA) routing + try_files $uri $uri/ /index.html; + } + + # Optional: Add error pages for better user experience + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/viewer/.env b/viewer/.env index 95512a4..29092f6 100644 --- a/viewer/.env +++ b/viewer/.env @@ -1,4 +1,5 @@ -VITE_API_PROXY_TARGET=https://feedback.hmac.kr/_back +# VITE_API_PROXY_TARGET=https://feedback.hmac.kr/_back +VITE_API_PROXY_TARGET=http://172.16.10.175:3030/_back # API 요청 시 필요한 인증 키 VITE_API_KEY=F5FE0363E37C012204F5