
React 앱을 Nginx와 Docker Compose로 배포하려고 하면서 너무 많은 문제들을 경험했습니다…
복잡할 것이라 예상했지만 생각 훨씬 이상으로 배포 과정에서 예상치 못한 문제들이 계속 발생했고, 해결하는 데에 많은 어려움을 겪었습니다.
이번 글에서는 배포 과정에서 어떤 문제들이 있었고, 어떻게 해결했는지 간단하게나마 기록하려 합니다.
(원래 겪은 문제는 이것보다 훨씬 이상으로…… 많았고……. 힘들었습니다………)
인증서 받아서 HTTPS로 배포하는 과정도 복잡했고, 모듈을 설치하지 못하는 문제 등도 많이 겪었습니다.
그건 이전 포스팅들을 많이 해두었기에 이번에는 …. welcome to nginx! 화면만 주구장창 보였던….. 경험을 작성해보려 합니다…
React 앱과 Node.js 백엔드 서버를 각각 도커 컨테이너로 띄우고, Nginx를 Reverse Proxy로 설정해 프론트엔드와 백엔드를 연결하려 했습니다.
먼저 docker-compose.yml 파일을 아래와 같이 설정했습니다.
version: '3.7'
services:
backend:
build:
context: ./backend
ports:
- "3001:3001"
frontend:
build:
context: ./frontend
ports:
- "3000:80"
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
3001 포트에서 Node.js 서버가 동작하도록 설정.frontend/dist 폴더에 빌드된 정적 파일을 Nginx가 제공하도록 설정.그 전에는 새로고침을 하거나 /add-channel과 같은 다른 리액트의 라우터로 접속했을 때 그냥 Gateway 오류가 발생했었는데, 이 문제를 해결하기 위해 이전 포스팅에서 아래 코드처럼 수정해주었습니다.
location / {
root /usr/share/nginx/html; # React 빌드 파일 경로 (dockerfile에서 설정
index index.html;
try_files $uri /index.html; # 없는 파일 요청은 index.html로 전달
}
해결 된 줄 알았는데… 배포 후 브라우저에서 확인해보니 / 라우터로 접속했을 때 "Welcome to nginx!" 화면만 뜨는 문제가 발생했습니다.
React 앱의 화면이 아니라 Nginx 기본 페이지가 나타났던 것입니다.
진짜 배포 여러번 했는데 welcome to nginx 화면만 주구장창 나와서… 저 정말 이제 PTSD 올 것 같습니다…..
가장 먼저 Nginx 설정 파일(nginx.conf)을 확인했습니다.
위에서 언급했듯 처음 상황은 아래 코드였습니다.
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri; # HTTP를 HTTPS로 리다이렉트
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/pem주소
ssl_certificate_key /etc/letsencrypt/live/pem주소
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# API 처리
location /api/ {
proxy_pass http://backend: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;
}
location /ws/ {
proxy_pass http://backend:3001/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
# React 앱 처리
location / {
root /usr/share/nginx/html; # React 빌드 파일 경로 (dockerfile에서 설정
index index.html;
try_files $uri /index.html; # 없는 파일 요청은 index.html로 전달
}
}
}
/usr/share/nginx/html 경로가 React 앱의 빌드 파일을 참조하도록 설정되어 있었기에, 이 경로에 index.html이 존재하는지부터 확인해보려 하였습니다.
문제 해결을 위해 컨테이너 내부를 살펴보기로 했습니다. 아래 명령어로 컨테이너 내부에 들어가 확인했습니다.
docker exec -it Web28-ddara_nginx_1 bash
ls /usr/share/nginx/html
해당 경로에는 Nginx의 기본 index.html 파일만 존재했습니다.
제가 빌드한 파일은 존재하지 않았죠……
프론트엔드 빌드 파일이 Nginx 컨테이너에 복사되지 않았다는 것을 알게 되었고, 다음으로는 그럼 빌드가 제대로 되었는지를 확인해보았습니다.
프론트엔드 컨테이너에서 빌드 파일이 제대로 생성되었는지 확인했습니다.
docker exec -it Web28-ddara_frontend_1 bash
ls /app/frontend/dist
초기에는 dist 폴더가 아예 없었고, 이는 Dockerfile에서 빌드 명령이 제대로 동작하지 않았기 때문이었습니다.
하지만 도커 실행 콘솔 등을 살펴보아도 아무런 문제가 없었습니다.
또, 제 로컬 컴퓨터에서는 docker-compose up을 실행한 뒤 도커 컨테이너의 파일들을 살펴보면,
dist 폴더가 제대로 있었기 때문에 서버에서만 문제가 되는 것이었습니다.
콘솔에도 문제가 없었기 때문에 여기서 시간을 정말 많이 썼는데, 여러 디버깅 과정을 거치다보니
node_modules 폴더 안에 내용도 존재하지 않는다는 것을 알게 되었습니다.
폴더는 다 존재했기 때문에 문제가 없는 것처럼 보였지만, 실제로 내부에는 아무 파일도 설치되어 있지 않았습니다.
그런데 제일 의아했던 건, 컨테이너를 빌드하거나 실행할 때 아무런 문제가 없었다는 것이었죠….
결국 로컬과 어떤 차이가 있는지 생각해보다가, (로컬에서는 잘 되었기 때문)
로컬에서는 node_modules나 build 폴더가 이미 잘 채워져있는 상태로 docker-compose up이 실행된다는 점을 생각해냈습니다.
물론 아직도 이 부분은 이해가 가지 않고, 옳은 해결 방안인지 의문이지만…
어쨌든 혹시나 하는 마음에 ubuntu 서버에서 npm install pnpm을 해보았고,
(사실 pnpm 설치도 node 버전이 맞지 않다고… NCloud 서버는 기본적으로 Node 버전이 10.뭐시기더라고요… 그래서 node 버전을 18로 업그레이드 해준 후에 pnpm을 설치할 수 있었습니다)
pnpm을 설치한 후에 pnpm install을 통해 제 프로젝트의 패키지들을 설치해주었습니다.
서버에서 (container 아님) 결국 node_modules 폴더가 생겼고,
이후 pnpm build도 해주어 dist 폴더에 빌드 파일이 잘 생긴 것을 확인할 수 있었습니다.
이후 도커 컨테이너, 이미지, 볼륨을 모두 제거하고 다시 빌드 후 실행시켜주었습니다.
이렇게 하니 이제는 도커 컨테이너에서 node_modules와 dist 폴더 안에 파일들이 잘 들어가있는 것을 확인할 수 있었습니다.
(사실 이 부분이 조금 이해가 안 가긴 하는데… 도커를 사용하는 이유는 모든 환경에서 동일한 환경을 구축시켜주기 위함인데,,, 패키지를 설치하고 빌드해야 하는게…. 제 생각에는 pnpm 을 사용해서 그런 것 같습니다. npm 이었다면 문제 없이 바로 container 내에 파일들이 잘 생성되었을 것 같습니다. 물론 제 생각…..)
이젠 진짜 될 줄 알았는데……….
또 Welcome to nginx 더군요 ㅎ ^^
(데이식스 welcome to the show 진짜 좋아하는데 welcome에 진짜 ptsd 와서 당분간 듣고 싶지 않을 것 같아요;;)
그래도 frontend 컨테이너의 /app/frontend/dist 폴더와 /usr/share/nginx/html 폴더에 빌드 파일이 잘 존재한다는 것까진 확인했으니, 끝이 보였습니다.
정신이 혼미해져있던 상태라 frontend 컨테이너에서만 확인하고, 정작 nginx 컨테이너에서 파일을 확인하지 못했다는 것을 깨닫고 급하게 확인해보니…
Nginx 컨테이너의 /usr/share/nginx/html 디렉토리가 빌드 파일을 참조하지 않는 것을 알게 되었습니다.
그래도 여긴 크게 어렵지 않게 빠르게 해결할 수 있었습니다.
(물론 이게 맞는 건지 확신은 없어요….. 한번 더 돌아가는 느낌이 되었달까…..)
아니 사실 이걸 쓰면서 깨달은 건데… 이렇게 아래 코드 추가할 거였으면 위에 /usr/share/nginx/html 폴더로 복사해주지 않았어도 되었겠다 싶은 생각이 드네요….. 애초에 로컬 빌드 파일을 가져올 거면 docker 쓰는 의미도 줄어든 것 같고….. 우선 이건 나중에 더 공부해봐야겠습니다…
어쨌든 Docker Compose 설정에서 nginx 서비스에 아래 코드를 추가해 로컬 빌드 파일을 Nginx 컨테이너에 복사하도록 설정했습니다.
volumes:
- ./frontend/dist:/usr/share/nginx/html
모든 설정을 마치고 아래 명령어로 컨테이너를 재시작했습니다.
docker-compose down
docker-compose up --build
이후 브라우저에서 / 라우터에 접속했을 때 React 앱의 홈 화면이 정상적으로 표시되었고, /add-channel과 같은 React Router 경로도 문제없이 작동했습니다…..
마지막으로 완성된 docker-compose.yml 파일은 아래와 같습니다.
version: '3'
services:
backend:
build:
context: .
dockerfile: backend/Dockerfile
environment:
- NODE_ENV=production
networks:
- app-network
volumes:
- .:/app
- /app/node_modules
- /app/backend/node_modules
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
networks:
- app-network
volumes:
- .:/app
- /app/node_modules
- /app/frontend/node_modules
depends_on:
- backend
nginx:
image: nginx:alpine
volumes:
- ./frontend/dist:/usr/share/nginx/html
- /etc/letsencrypt:/etc/letsencrypt
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
- "443:443"
depends_on:
- backend
- frontend
networks:
- app-network
networks:
app-network:
driver: bridge
이번 배포 과정은 정말정말정말정말 X100000000 많은 시행착오가 있었지만, 결과적으로 프론트와 백엔드가 모두 Nginx와 Docker Compose 환경에서 제대로 배포되는 것을 확인할 수 있었습니다.
앞으로 이렇게 모노레포로 배포하게 될 때 이런 경험이 큰 도움이 될 것 같습니다 😊