1. 개요
docker에 docker-compose를 사용해 nginx와 express-react 컨테이너를 생성하는 방법을 정리해보겠습니다!
먼저, docker가 설치가 되어있다는 가정하에 진행합니다
사용된 파일들의 디렉터리 구조는 위와 같습니다.
backend에 express, frontend에 react, nginx는 proxy 서버로 워크플로우가 구성되어있습니다.
순차적으로 따라하시면서 frontend와 backend를 구성하는데 어떤 식으로 구성을 하는지 잘 유의하시면서 보시면 될 것 같습니다.
이 워크 플로우 구조를 구현하는데 굉장히 애를 많이 먹었어요ㅠㅠ
막상 복습하면서 만드니까 뚝딱 만드는데 nginx와 express간의 프록시 문제도 있었고… react와 express 간의 문제도 있었고… 정말 많은 문제가 있었서 차근차근히 Dockerfile의 옵션과 docker-compose의 옵션들의 명령어들을 정리하면서 겨우겨우 구현할 수가 있었습니다!
2. backend
먼저 backend, frontend, nginx 총 3개의 폴더를 생성해줍니다!
그리고 backend 폴더로 이동해서
# npm init
으로 package.json을 생성해줍니다.
backend 파일의 구조는 위와 같습니다.
backend > package.json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "nodemon"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.7"
}
}
위 express 서버는 nodemon 라이브러리를 통해 기동시킵니다. 따라서 depedency에 nodemon이 포함되어있어야 하고, express도 포함이 되어있어야 합니다!
backend > nodemon.json
{
"watch": ["./"],
"exec": "node server.js",
"ext": "js json yaml",
"ignore" : ["./log/"]
}
nodemon을 통해 서버를 가동하기 때문에 nodemon을 실행시키기 위한 명령어 json 파일도 작성해야합니다!
backend > Dockerfile
FROM node:15.11.0-alpine
MAINTAINER KIMDONGJANG
RUN mkdir /app
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --no-cache
#RUN apk add --no-cache git
COPY . /app
CMD ["npm", "run", "server"]
dockerfile은 별도로 확장자가 없습니다. node 기반의 이미지를 생성해서 express 서버를 기동시키기 위해 /app 경로로 폴더를 만들고 backend에서 작성된 코드를 복사합니다.
위 이미지로 컨테이너를 생성할 때 CMD에서 지정한 명령어가 실행됩니다.
backend > server.js
const express = require('express');
const app = express();
app.use(express.json());
app.listen(8080, (err) => {
if (err) {
console.log('err 발생');
}
console.log('정상구동');
});
express 기반으로 작성된 javascript입니다. 포트는 8080으로 지정했습니다.
3. frontend
프론트엔드는 react를 기반으로 작성합니다. frontend 폴더로 이동해서
# npx create-react-app .
명령어를 입력해 react 프로젝트를 생성합니다.
프로젝트를 생성하면 기본적으로 이런 파일들이 생성이 될텐데 워크플로우에 따라 환경을 구축하는 것이 목적이기 때문에 별도로 수정할 내용은 없습니다.
frontend > package.json
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.21.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
package.json에서는 추가된 dependency 부분이 없는지만 체크해주시면 됩니다.
4. nginx
nginx는 express의 proxy 서버로 이용하는데, 외부에서 특정한 주소:포트로 연결요청이 들어오면 연결된 express 서버로 전달해주는 역할을 해줍니다.
nginx는 nginx.conf파일만 작성해주면 됩니다.
nginx > nginx.conf
user nginx;
worker_processes 1;
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;
upstream docker-server {
server server:8080;
}
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_pass http://docker-server;
proxy_redirect off;
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 /socket {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_pass http://docker-server;
}
}
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;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
여기서 주의깊게 보아야하는 부분은 upstream과 server 부분입니다.
5. docker-compose
이제 여러 개의 이미지를 한꺼번에 관리하기 위해 docker-compose를 사용합니다.
docker-compose.yml
version: '3.3'
services:
nginx_proxy:
image: nginx:latest
container_name: nginx_proxy
restart: "on-failure"
ports:
- 80:80
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./frontend/build:/usr/share/nginx/html
server:
build:
context: ./backend/
container_name: server
restart: "on-failure"
expose:
- 8080
volumes:
- './backend:/app'
- '/app/node_modules'
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
stdin_open: true
tty: true
volume이 하는 역할은 컨테이너에 올리는 파일의 데이터를 복사하는 것이 아니라 위 도커 컴포즈 파일을 구동하는 host, 즉 현재 윈도우 운영체제의 vs code에서 docker-compose를 실행하니 윈도우 운영체제의 경로를 host로 그 경로에 있는 파일들을 기준으로 컨테이너를 생성하게 됩니다.
server의 expose 포트를 8080으로 지정해서 nginx에서 express로 접근할 수 있도록 합니다.
package.json
{
"name": "docker_nginx_express_react_test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"client": "cd ./client && npm run start",
"server": "cd ./server && npm run server",
"dev": "concurrently \"npm run server\" \"npm run client\""
},
"author": "",
"license": "ISC",
"devDependencies": {
"concurrently": "^6.2.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kimdongjang/docker-nginx-express-react-test.git"
},
"bugs": {
"url": "https://github.com/kimdongjang/docker-nginx-express-react-test/issues"
},
"homepage": "https://github.com/kimdongjang/docker-nginx-express-react-test#readme"
}
위 프로젝트를 실행할 때의 스크립트를 지정하는 부분을 주의깊게 보시면 됩니다.
위 파일 까지 작성하면 기본적인 환경설정은 끝입니다!
6. 실행방법
먼저 frontend의 react쪽에 build를 먼저 진행해야 합니다.
# cd frontend
# npm install react
# npm run build
위와 같이 build파일을 생성해주면 됩니다.
그 후 최상위 폴더로 돌아와서(docker-compose 파일이 위치한 폴더)
# docker-compose up -d
를 입력해줍니다.
그러고 나서 도커를 확인해보면 아래와 같이 proxy와 express 서버가 가동이되고
localhost 주소로 접근해보면 react app이 실행되고 있는 것을 확인할 수 있습니다!
위 프로젝트의 전체 소스는 github를 참고하시면 됩니다.
https://github.com/kimdongjang/docker_nginx_express_react_test