dockerizing for production

This commit is contained in:
2025-08-05 14:00:07 +09:00
parent 9527b7d385
commit 27844ef237
10 changed files with 159 additions and 84 deletions

17
.dockerignore Normal file
View File

@@ -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

2
.gitignore vendored
View File

@@ -25,5 +25,3 @@ dist-ssr
.env
.env.local
.DS_Store

47
Dockerfile Normal file
View File

@@ -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;"]

View File

@@ -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 오류 해결.

View File

@@ -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}`);
});

13
docker-compose.yml Normal file
View File

@@ -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

32
docker-entrypoint.sh Executable file → Normal file
View File

@@ -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 "$@"

View File

@@ -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;
}
}
}

44
nginx.conf.template Normal file
View File

@@ -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;
}
}

View File

@@ -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