dockerizing for production
This commit is contained in:
17
.dockerignore
Normal file
17
.dockerignore
Normal 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
2
.gitignore
vendored
@@ -25,5 +25,3 @@ dist-ssr
|
||||
|
||||
.env
|
||||
.env.local
|
||||
|
||||
.DS_Store
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal 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;"]
|
||||
8
TODO.md
8
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 오류 해결.
|
||||
@@ -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
13
docker-compose.yml
Normal 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
32
docker-entrypoint.sh
Executable file → Normal 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 "$@"
|
||||
|
||||
36
nginx.conf
36
nginx.conf
@@ -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
44
nginx.conf.template
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user