먼저,
Node.js 앱은 Node.js 런타임 환경에서 실행되는 애플리케이션이다. Node.js는 자바스크립트(JavaScript)코드를 서버 측에서도 실행할 수 있게 만들어주며, 주로 웹 서버, API서버, 또는 백엔드 애플리케이션을 개발하는 데 사용된다.
일반적으로 Node.js앱은 자바스크립트코드, 패키지관리(package.json), 주요 프레임워크(Express.js)로 구성된다.
1. JavaScript 코드
Node.js는 자바스크립트를 사용하여 서버 기능을 구현할 수 있다. 기본적인 웹 서버나 데이터베이스 연결, 파일 시스템 제어 등을 모두 자바스크립트로 작성할 수 있다.
2. 패키지 관리
Node.js앱에서는 npm(Node Package Manager)을 통해 다양한 라이브러리와 모듈을 설치하고 관리한다. package.json파일에 의존성 정보가 저장되어 있어 필요한 라이브러리들을 손쉽게 설치하고 관리할 수 있다.
3. 주요 프레임워크
Node.js 앱에서 가장 널리 사용되는 웹 프레임워크는 Express.js이다. Express는 웹 서비스나 RESTful API를 쉽게 구축할 수 있도록 해준다. 예를 들어, Express를 사용하면 클라이언트의 요청을 처리하고 응답을 반환하는 웹 서버를 간단하게 만들 수 있다.
package.json
프로젝트의 정보와 프로젝트에서 사용 중인 패키지의 의존성을 관리하는 곳이다.
npm init 명령어를 사용해 만들 수 있다.
(Node.js와 WSL설치가 되어있어야한다!)

dependencies의 express부분을 추가한 이유는 Node.js를 더욱 쉽고 유용하게 사용할 수 있도록 하기 위함이다.
Javascript와 jQuery의 관계처럼 Node.js의 API를 단순화하고 새로운 기능을 추가해준다.
server.js
Node.js에서 진입점이 되는 파일이다.

const express = require('express'); : express 모듈 불러온다.
const PORT = 8080; : express 서버를 위한 포트 설정
const app = express(); : 새로운 express어플 생성
app.get('/',(req,res)=>{ res.send("Hello World") });
: "/" 이 경로로 요청이 오면 Hello World를 결과값으로 전달
app.listen(PORT);: 해당 포트에서 HTTP서버를 시작
이렇게 두 파일을 이용해 기본적인 Node.js애플리케이션을 완성했다.
이제 이 Node.js 앱을 도커 환경에서 실행하기 위해서 도커와 관련된 부분을 만들어야한다.
Node.js앱을 도커 환경에서 실행하려면 먼저 이미지를 생성하고 그 이미지를 이용해서 컨테이너를 실행한 후 그 컨테이너 안에서 Nodejs앱을 실행해야한다. 그래서 그 이미지를 먼저 생성하려면 Dockerfile을 먼저 작성해야한다.


FROM,RUN,CMD에 명시한 것 각각이 무엇인지는 밑에서 차차 설명할 예정이다.


alpine으로 하면 npm not found 오류가 나는 것을 확인할 수 있다.
그 이유는 alpine이미지는 가장 최소한의 경량화된 파일들이 들어있기에 npm을 위한 파일이 들어있지 않아서 RUN부분에 npm install을 할 수가 없다.
알파인 이미지의 사이즈는 5MB정도밖에 안된다.
그래서 이미지 사이즈가 더 큰 node를 사용해야 오류가 발생하지 않는다.

이제, 만들어둔 Dockerfile을 docker build .으로 실행시켜보면 오류가 나는 것을 확인할 수 있다.

오류 화면을 보면, RUN npm install 부분에서 오류가 발생한다.
원인이 무엇일까?
지금 우리는 docker의 이미지를 만들고 있다. 이미지를 만들 때에는 Node 베이스 이미지를 통해 임시 컨테이너를 만든 다음에 임시 컨테이너로 실제 사용할 이미지를 만드는 것이다. 베이스 이미지에는 파일 스냅샷이 존재한다. 그래서 그것을 하드디스크에 넣어주고, RUN에 있는 npm install을 하게 된다.
RUN은 추가적으로 필요한 파일들을 다운로드 받는 역할을 한다.
RUN에 쓴 npm install은 package.json파일에 명시된 모든 패키지를 설치하는 역할을 수행한다.
Node.js 프로젝트에서는 package.json 파일에 의존성 정보가 포함되어 있으며, npm install을 실행하면 해당 의존성들이 node_modules 폴더에 설치됩니다.
그러나, package.json이 컨테이너의 밖에 존재하여 파일을 보고 설치하는 것이 불가능하다.
그 이유는 Node 베이스 이미지의 스냅샷이 임시 컨테이너의 하드디스크로 들어오게 되는데, Node 베이스 이미지의 스냅샷에는 두 파일이 없기 때문이다.
아래의 사진을 보면 이 말을 더욱 잘 이해할 수 있다.

그렇다면, 이 문제를 어떻게 해결해야할까?


Dockerfile내부에 COPY를 적은 뒤,
docker build -t <docker account>/<앱 이름>을 통해 실행시켜보았다.

빌드가 잘 되는 것을 확인할 수 있다.
이제 run을 시켜볼건데, 잘 되는지 확인해보기 위해

이렇게 콘솔 로그를 server.js에 넣어주었다.
그리고 다시 빌드시킨 후 run해보면,

이렇게 잘 실행되는 것을 확인할 수 있다.
하지만, localhost:8080주소로 가보면, 실행이 되지 않은 것을 확인할 수 있다.

위에서 컨테이너를 실행하기 위해 사용한 명령어는 다음과 같다.

여기에서 추가해야할 부분이 있다.

이미지를 만들 때 로컬에 있던 파일(package.json)등을 컨테이너에 복사해주어야했다.
그것과 유사하게 네트워크도 로컬 네트워크에 있던 것을 컨테이너 내부에 있는 네트워크에 연결을 시켜주어야한다.


브라우저에서 5001번 포트로 접속하고, 컨테이너 내부에서 컨테이너 속 8080포트로 접근하는 방식이다.


이제 hello world가 잘 보이는 것을 확인할 수 있다!
도커 파일에 WORKDIR이라는 부분을 추가해주어야 한다.
이미지 안에서 애플리케이션 소스 코드를 가지고 있을 디렉터리를 생성하는 것이다. 그리고 이 디렉터리가 애플리케이션에 working directory가 된다.
그런데, 왜 따로 working directory가 있어야할까?

현재 만들어놓은 이미지의 Root 디렉토리를 살펴보기 위해
docker run -it <이미지이름> sh
명령어를 사용했고, 그 터미널 안에서 ls 명령어를 입력했다.

여기 파일 중에서 COPY를 해서 컨테이너 안으로 들어온 것들은 다음과 같다.
그래서 모든 어플리케이션을 위한 소스들은 WORK 디렉토리를 따로 만들어서 보관한다.

Dockerfile에 WORKDIR을 추가시켰다.


처음 ls를 실행한 결과에 COPY된 것들만 존재한다. 그 이유는 WORKDIR을 지정하면 root가 아니라 WORKDIR로 가게 되기 때문이다. 따라서 처음 ls실행환경이 WORKDIR로 지정했던 /usr/src/app인 것이다.
그 후, cd /를 통해 root로 가서 ls를 치면, 원래 존재하는 다른 디렉토리들이 있는 것을 확인할 수 있다.

어플리케이션을 만들다보면 소스 코드를 계속 변경시켜주어야하며 그에따라 변경된 부분을 확인하면서 개발을 해나가야한다. 그래서 도커를 이용해 어떻게 실시간으로 소스가 반영되게 하는지 알아본다.
현재까지 만든 어플을 소스 변경 시 변경된 소스를 반영시켜주기 위해서는
현재까지 도커 컨테이너로 어플을 실행한 순서를 보면


그러면, 이러한 비효율적인 부분을 어떻게 해결할까?

위의 도표를 보면, 완성본 Dockerfile에는 RUN 위에 COPY package.json ./이 하나가 추가되고 원래의 COPY가 RUN 아래로 내려갔다.
그 이유는 무엇일까?
npm install할 때 불필요한 다운로드를 피하기 위해서이다.
코드 변경이 있을 때 문제점) COPY ./ ./ 명령어로 모든 파일을 복사하게 되면, 작은 소스 코드 변경이 있어도 Docker는 npm install 단계 이전의 모든 캐시를 무효화하고, 모듈을 다시 다운로드하게 된다.
그래서 npm install 단계 전에는 의존성파일(package.json)만 COPY해서 필요한 모듈만 설치하고, 그 이후에 다른 파일들을 다시 COPY해서 Docker이미지가 효율적으로 빌드되도록 한다.
이렇게 함으로써 코드 수정 시 npm install 이전 단계를 다시 실행하지 않아도 되며, 불필요한 다운로드를 방지할 수 있다.
이제는 npm install 전에 package.json만 따로 COPY 해주어서 모든 캐시를 무효화하고 모듈을 다시 다운로드 하는 일을 없앴다.
하지만 아직도 소스를 변경할 때마다 변경된 소스 부분은 COPY한 후 이미지를 다시 빌드를 해주고 컨테이너를 다시 실행해주어야지 변경된 소스가 화면에 반영이 된다.
이러한 작업은 너무나 시간 소요가 크며 이미지도 너무나 많이 빌드하게 된다.
이러한 문제점을 해결하기 위해!!! Volume을 사용한다.
💻 Docker Volume?
지금까지는 컨테이너에 소스 코드를 COPY로 넣어주었다.

COPY는 호스트 디렉터리에 있는 파일을 도커 컨테이너네 복사하는 것이고, 볼륨은 도커 컨테이너에서 호스트 디렉터리에 있는 파일들을 매핑해서 사용한다.

Volume을 사용하여 어플리케이션을 실행해보려면,
docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app <이미지 아이디>
-p 5000:8080: 호스트의 5000번 포트를 컨테이너의 8080포트에 매핑한다.-v /usr/src/app/node_modules: node_modules 폴더를 호스트에 공유하지 않고, 컨테이너 내부에만 유지시킨다. -v $(pwd):/usr/src/app: 현재 작업 디렉토리($(pwd))를 /usr/src/app경로에 매핑하여 소스 코드 파일이 컨테이너와 동기화되도록 한다.
일단, "Hello World" 출력하는 것에서 "안녕하세요."로 소스코드를 변경했다.

재빌드 하지 않고 볼륨도 사용하지 않고 서버 실행시키면,

이렇게 기존대로 실행이 된다.

재빌드 없이, 도커 볼륨을 이용해서 실행해보았다.

변경한 코드가 반영된 것을 확인할 수 있다!!