정말 너무 쉬운 Docker
우리가 Docker를 사용해야하는 가장 큰 이유는, 어떤 컴퓨터에서든 똑같은 개발 환경을 보장해주기 떄문입니다. 로컬 컴퓨터에서 열심히 개발하고 AWS에 코드를 올렸는데, 에러를 마주하며 스트레스를 받았던 경험이 한 번쯤은 있을겁니다. 내 컴퓨터랑 클라우드 컴퓨터의 환경이 100% 똑같지 않기 때문이죠. 근데 이 어려움을 한 번에 해결해준다? 쓰지 말아야 할 이유가 없습니다.
이 뿐만이 아닙니다. 내 앱을 실행하기 위한 모든 과정을 미리 패키징해두면 몇 초만에 앱 수 십개를 구동할 수도 있습니다. 서비스를 확장할수록 더 많은 인스턴스를 운영해야하기 때문에 반드시 도커를 사용해야 할 시점이 오게 됩니다.
이번 포스팅에서는 node.js와 typescript를 사용하는 아주 간단한 서버를 도커라이징하고, 도커를 더 쉽고 효율적으로 사용하는 몇 가지 팁들을 알려드리겠습니다.
Node.js 서버 만들기
일단 프로젝트 폴더를 구성합니다.
$ mkdir node-docker
$ cd node-docker
$ yarn init -y # package.json 만들기
다음은 노드 서버를 돌리기 위한 패키지를 설치하고, 앱의 진입점이 될 타입스크립트 파일까지 만들어줍니다.
$ yarn add express
$ yarn add -D typescript ts-node nodemon @types/node @types/express
$ touch index.ts
$ npx tsc --init # tsconfig.json 만들기
여기까지 하면 우리 프로젝트 폴더의 구조는 다음과 같습니다.
node-docker/
|-- node_modules
|-- index.ts
|-- package.json
|-- tsconfig.json
|-- yarn.lock
express는 노드에서 가장 많이 사용하는 서버 라이브러리입니다. 그럼 바로 스크립트를 작성하도록 합니다.
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.json({ message: "Hello, Docker!" });
});
app.listen(3000);
console.log("http://localhost:3000..");
서버에 요청할 때 마다 Hello, Docker!를 응답하는 간단한 서버입니다.
서버 코드를 작성했으니 앱을 실행시켜야겠죠. package.json 파일을 수정합니다.
{
"scripts": {
"start": "node index.js",
"dev": "nodemon -L --exec ts-node index.ts",
"build": "tsc"
}
}
터미널에 yarn dev를 입력하고, 브라우저를 통해 localhost:3000으로 접속해보면 메세지를 응답 받을 수 있습니다.
추가로 개발 환경 때 시간 단축을 위해 nodemon 패키지를 사용합니다. -L (Legacy Watch) 플래그를 넣는 이유는 WindowsOS에서 ts 파일을 수정할 때 인식이 안되는 버그가 있어서 그렇습니다.

Dockerfile
도커를 사용하기 위해 사용자 컴퓨터에 도커를 설치해야하는데, 도커 홈페이지에서 Docker Desktop을 다운로드 해주세요.
잘 설치했다면 터미널에서 docker 명령어를 사용할 수 있습니다.
$ docker -v
Docker version 20.10.6, build 370c289
자 그럼 도커를 사용하기 위해 Dockerfile 을 작성해야 합니다. 별 거 없습니다. 그냥 어떤 순서로 앱을 패키징할지 나열하는 것 뿐입니다.
프로젝트 루트 폴더에 도커파일을 만들어주세요. (대문자 D 오타 아닙니다)
$ touch Dockerfile
그럼 node 앱을 돌리기 위한 간단한 도커파일을 작성합니다.
# 어떤 환경에서 도커 이미지를 만들지 결정하기.
FROM node:14-slim
# 도커 컨테이너 내부의 작업 디렉토리 결정하기. 원하는 대로 정하면 됩니다.
WORKDIR /usr/src/app
# 외부 패키지 설치를 위해 package.json과 yarn.lock 파일 복사
COPY package.json .
COPY yarn.lock .
# 패키지 설치
RUN yarn
# 나머지 모두 복사
COPY . .
# 도커 컨테이너에 접근할 수 있게 포트 열어주기
EXPOSE 3000
# 앱 실행시키기
CMD [ "yarn", "dev" ]
참고로 도커 파일을 통해 패키징한 결과물을 이미지라고 합니다. 그리고 이 이미지를 저장하는 곳은 레지스트리 라고 합니다. 우리가 깃 프로젝트를 깃허브 리파지토리에 올리는 것과 비슷한 겁니다. 그래서 이 이미지들도 Docker Hub나 AWS ECR 같은 원격 레지스트리에 저장시켜서 사용합니다.
아무튼 Dockerfile 작성에 대한 방법은, 그냥 받아들이면 됩니다. 한 가지 COPY 명령어가 직관적으로 이해가 안갈 수 있습니다.
COPY A B 이런식으로 사용하면 되고, A가 내 컴퓨터 쪽, B가 도커 컨테이너 쪽입니다. A를 B로 복사한다는 뜻입니다.
이제 이 도커파일을 이용해 이미지를 만들어봅시다. 터미널에 입력해주세요.
$ docker build . -t node_app
[+] Building 11.6s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 187B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:14-slim 2.2s
=> [auth] library/node:pull token for registry-1.docker.io 0.0s
=> CACHED [1/6] FROM docker.io/library/node:14-slim@sha256:a3ff0656dfa88cc5c4092af3e18d16cbbbf50417ce4d0 0.0s
=> [internal] load build context 1.3s
=> => transferring context: 1.07MB 1.1s
=> [2/6] WORKDIR /usr/src/app 0.0s
=> [3/6] COPY package.json . 1.1s
=> [4/6] COPY yarn.lock . 0.0s
=> [5/6] RUN yarn 5.1s
=> [6/6] COPY . . 0.9s
=> exporting to image 0.9s
=> => exporting layers 0.9s
=> => writing image sha256:33c768313fd785507812a137e90fdf97f629edd91d06851846ba416df6a62277 0.0s
=> => naming to docker.io/library/node_app 0.0s
한 가지 알아둘 부분은, -t 는 태그를 지정한다는 뜻입니다. 지정하지 않으면 이름이 NONE 으로 지정되면서 사용하는 데 불편하므로 태깅을 잘 해주세요.
아래는 빌드된 이미지를 확인하는 방법입니다.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node_app latest 33c768313fd7 About a minute ago 382MB
빌드된 이미지를 실행시켜봐야겠죠. 아래 명령어를 입력해주세요.
$ docker run -p 3000:3000 node_app
# 이렇게 컨테이너를 여러 개 실행시킬 수도 있음. 포트 바꿔서 들어가보세요.
$ docker run -p 3001:3000 node_app
$ docker run -p 3002:3000 node_app
브라우저를 통해 접속이 된다면 성공입니다. 이렇게 빌드된 이미지를 실행시키면, 그걸 컨테이너라고 부릅니다.
현재 실행 중인 모든 컨테이너 목록을 보고 싶으면 아래 명령어를 입력해주세요.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36f74a13d90d node_app "docker-entrypoint.s…" 12 seconds ago Up 10 seconds 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp upbeat_blackburn
쓱 한 번 보고 넘어가세요. STATUS 부분이 Up이라고 되어있으면 앱이 돌아간다는 뜻이니까, 컨테이너를 삭제하고 싶다면 CONTAINER ID를 이용해 삭제해줍시다.
$ docker rm -f 36f74a13d90d
Docker Compose
지금까지 Docker CLI를 이용해서 이것 저것 해보았습니다만, 저는 CLI로 docker를 사용하는 것을 추천하지 않습니다. 도커 특성상 많은 옵션을 주어야 하는데 언제 일일히 치고 있나요. 명령어가 길어져서 보기 안좋습니다.
그리고 지금은 이미지 1개만 띄우니까 괜찮지만, 나중에는 몇 개를 띄워야 할 지 모릅니다. 그 때 마다 일일히 이미지 하나씩 올리는 건 상당히 귀찮은 일이겠죠.
그래서 여러 이미지를 한 번에 관리할 수 있게끔 개발된 게 Docker Compose 입니다. 바로 예제를 보도록 하죠.
version: "3.9"
services:
app: # 이미지 이름 (마음대로 설정해도 됩니다)
build: . # Dockerfile이 있는 경로를 넣어주기
ports:
- "3000:3000" # docker CLI의 "-p 3000:3000" 과 같은 표현
프로젝트 루트 디렉토리에 docker-compose.yml 파일을 만들어 주고 터미널에 docker compose up 을 입력하면 정상적으로 컨테이너가 생성되고, 로컬호스트로 접근이 가능해집니다.
docker compose CLI는 이제 docker CLI에서 제공됩니다.
docker-compose CMD대신docker compose CMD를 사용해주세요.
그리고 여러 개의 컨테이너를 한 꺼번에 띄우고 싶다면 이런식으로 하면 됩니다.
version: "3.9"
services:
app1:
build: .
ports:
- "3000:3000"
app2:
build: .
ports:
- "3001:3000"
app3:
build: .
ports:
- "3002:3000"
app4:
build: .
ports:
- "3003:3000"
이러고 3000 ~ 3003 포트까지 들어가보면 잘 접속됩니다.
이미지가 잘 안보이는데, 아무튼 4개 앱이 각자 다른 포트로 열렸다는 뜻
도커 컴포즈는 일반 도커 명령어와 다르게, 터미널에서 작업을 종료하면 그대로 컨테이너들이 모두 비활성화됩니다. 백그라운드에서도 계속 실행시키고 싶다면 docker compose up -d 명령어를 사용해주세요.
그리고 백그라운드에서 실행된 컨테이너들을 한 번에 지우려면 docker compose down을 하면 됩니다. (다른 디렉토리에서 하면 당연히 안됩니다..)
프로젝트 구조 개선하기
여기까지 간단하게 Docker 에 대해 살펴봤습니다. 근데 사실 지금 상태로 앱을 개발하고, 배포하기엔 몇 가지 문제가 있습니다.
첫 번째는 핫 리로딩입니다. 도커 컨테이너로 개발을 하는 경우, 코드를 수정할 때 마다 개발 서버가 다시 시작되지 않습니다. 이유는 우리가 수정하는 코드는 로컬 컴퓨터의 코드지, 컨테이너 안의 코드가 아니기 때문입니다.
두 번째는 배포입니다. 일반적으로 배포용 앱은 webpack 같은 번들링 도구를 이용해 코드를 변형하거나 압축시키는 작업을 하게 됩니다. 그렇게 하기 위해서는 배포용 Dockerfile 이 필요합니다.
핫 리로딩 개선하기
해결 방법은 간단합니다. 내 로컬 컴퓨터와 컨테이너의 저장 공간을 공유하면 됩니다. 로컬 코드를 수정하면 바로 컨테이너 안의 코드도 같이 수정이 되는거죠.
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- ".:/usr/src/api" # Dockerfile의 WORKDIR와 맞추기
- "/usr/src/api/node_modules" # 핫 리로드 성능 개선
이렇게 하고 docker compose up --build 명령어로 새로 빌드하면서 컨테이너를 띄워주고, 코드를 수정하면 바로 서버가 다시 시작하게 됩니다.
개발용, 배포용 이미지 분리하기
일단 기존 도커파일은 개발용이었으니 파일 이름을 Dockerfile.dev 로 변경해주고, 배포용 파일인 Dockerfile 을 새로 만들어주세요.
배포용 도커 파일은 이렇게 작성합니다.
FROM node:14-slim
WORKDIR /usr/src/app
COPY package.json .
COPY yarn.lock .
RUN yarn
COPY . .
RUN yarn build # 빌드하는 부분 추가
EXPOSE 3000
CMD [ "yarn", "start" ] # `yarn dev`에서 `yarn start`로 변경
그리고 기존 docker-compose.yml 도 이름을 docker-compose.dev.yml 로 바꾸고, 새로운 컴포즈 파일을 만들고 아래 내용을 작성합니다.
version: "3.9"
services:
app:
build: .
ports:
- "80:3000"
물론 배포용 앱을 웹서버 없이 그냥 올리는 사람은 없을겁니다. nginx 같은 웹서버로 프록시를 해줘야 하지만, 여기서 다루면 또 내용이 비대해지기 때문에, 일단은 대충 이렇게 한다는 걸 알아두시면 되겠습니다.
개발용인 Dockerifle.dev 와 docker-compose.dev.yml 은 이렇게 사용하면 됩니다.
도커파일은 똑같고, 컴포즈 파일은 조금 수정이 필요합니다.
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ".:/usr/src/app"
- "/usr/src/app/node_modules"
build 부분이 조금 바뀌는데, 개발 때는 Dockerfile.dev 을 읽도록 바꿔주었습니다.
CLI로 실행할 땐 -f 플래그를 이용하면 됩니다.
$ docker compose -f docker-compose.dev.yml up --build
참고
같은 카테고리의 다른 글
[Nuxt 3] Composition API로 자동 스크롤링 기능 구현하기
이번 포스팅에서는 실시간 채팅 서비스에서 새로운 대화 내용이 추가되었을 때 자동으로 스크롤이 계속해서 아래로 내려가면서, 스크롤을 조작함에 따라 자동 스크롤이 활성화/비활성화되는 기능을 Vue 3에서 새로 추가된 Composition API를 통해 만들어볼겁니다.
Twilio 번호 구매 없이 연락처 인증 서비스 5분만에 구현하기
이번 포스팅에선 Twilio를 이용해 Node.js에서 개인 번호를 발급받지 않고, 핸드폰 번호 인증을 매우 간단하게 구현하는 방법에 대해 소개해드리겠습니다.
[Nuxt 3] 사이드 프로젝트 만들기 - 개발 환경 설정편
저번 사이드 프로젝트 만들기 - 기획편의 다음 편입니다. 이번엔 nuxt3의 주요 변경사항 일부를 알아보고, 쾌적한 개발 환경을 위해 몇 가지 세팅을 해보도록 하겠습니다.
[Nuxt 3] 사이드 프로젝트 만들기 - 기획편
올해 첫 개발 관련 주제를 뭘로할까 고민하다가 사이드 프로젝트 아이디어가 떠올라서 그걸 같이 만들어볼까 합니다. 하지만 이미 잘 알고 있던 기술을 사용해서 만들면 재미없겠죠. 사이드 프로젝트는 역시 신기술을 이용해서 만드는게 가장 좋습니다. 나만의 프로젝트를 만들면서 최신 기술도 마음껏 써볼 수 있으니까요.
평생 무료로 반응형 이메일 템플릿 무한대로 만들기 - mjml.io
저는 이메일을 데스크톱과 모바일 환경에서 매일매일 확인합니다. 그런데 아직도 모바일 디스플레이에 최적화되지 않은 이메일을 받을 때가 많습니다.
평생 무료로 커스텀 이메일 사용하기
안녕하세요. 또 다시 찾아온 평생 무료 시리즈입니다. 저는 틈만나면 1인 사이드 프로젝트를 진행하기 때문에, 어떻게든 공짜로 서버를 돌리기 위해 온갖 노력을 하고 있습니다. 그래서 무료로 이용하는 방법에 관한 글을 몇 개 올렸는데 GA를 살펴보니 다른 주제보다 조회수가 높더군요. 역시 공짜가 좋네요.
클립보드 이미지를 1초만에 링크로 만드는 툴 개발하기
저는 이 블로그를 운영하면서 가장 귀찮은 일이 하나 있습니다. 바로 이미지 주소를 만드는 일인데요, 저는 @nuxt/content 모듈을 이용해 마크다운 포맷을 이용하는 정적 블로그를 운영 중이라 글 작성 중에 원격 이미지 주소를 삽입하는 기능을 사용하지 않습니다.
평생 무료인 모니터링 도구 10분만에 만들기
서버를 운영하다보면 예상치 못한 서버 다운이나 응답 속도 저하를 반드시 겪게 됩니다. 원인은 둘째 치구요. 근데 문제는 서버 장애를 원천 차단할 방법이 사실상 없기 때문에, 우리 개발자들이 24시간 눈을 뜨고 지켜볼 수 밖에 없겠습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 3부
이전 포스팅에서는 Vite을 이용해 크롬 확장 프로그램을 만들기 위한 기본적인 프로젝트 환경 설정까지 마쳤습니다. 본격적으로 Vue.js 코드를 작성해보도록 합시다.
웹소켓과 socket.io
예전에 회사 프로젝트를 진행할 때, 지도에 실시간으로 사용자의 위치를 보여주는 기능이 필요해서 socket.io 를 사용해서 구현했던 적이 있습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 2부
이전 포스팅에서 index.html과 manifest.json 파일을 이용해서 확장 프로그램을 개발자 모드로 실행시키는 것 까지 진행했습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 1부
제가 최근 우연히 크롬 확장 프로그램을 개발했는데, 이게 생각보다 꽤 괜찮은 시장이라는 걸 알게 되었습니다.
웹팩보다 100배 빠른 번들러, esbuild
이번 포스팅은 떠오르는 차세대 자바스크립트 번들러 esbuild에 대한 내용입니다. 작년 Github에서 떠오르는 번들링 프로젝트 중 1위를 차지했고, 오늘을 기준으로 20만개의 가까운 Github Star를 받았습니다.
Rollup.js - 플러그인으로 완성도를 높이다
지난 포스팅에서 rollup.js 를 이용해 두 개의 자바스크립트 파일을 하나로 묶고, rollup.config.js 파일을 구성해서 CLI가 아닌 스크립트로 설정 파일을 관리하는 것 까지 진행했습니다.
Rollup.js - 번들링, 파일을 하나로 합쳐보자
번들링 이라는 말을 프론트엔드 개발자라면 많이 들어보셨을겁니다. 번들링은, 파일을 하나로 묶는 것을 말합니다. 그럼 왜 굳이 파일을 하나로 묶어야 할까요? 바로 HTTP 통신의 특성 때문입니다.
코인 시세 1초만에 보는 크롬 확장 프로그램 만들기
가상화폐 거래소 API를 활용해 브라우저에서 단축키로 빠르게 코인 시세를 확인할 수 있는 툴을 크롬 확장 프로그램으로 만들어보았다.
평생 무료로 개인 블로그 운영하기
거의 대부분의 개발자들이 개인 블로그를 운영하라고 얘기한다. 나도 그렇게 생각한다. 왜냐면 분명히 내가 작성했던 코드인데도, 일주일만 지나도 기억이 안나기 때문이다. 그리고 웬만하면 공개해서 작성하라고 하고 싶다. 이미 우리는 누군가가 옛날에 썼던 글을 보고, 문제를 해결한 경험히 굉장히 많기 때문이다. 나는 이런 개발자들의 문화가 너무 좋다. 이런 개발자들의 문화가 다른 업종에도 접목된다면 정말 좋으련만.
Firebase를 대체할 오픈소스 프로젝트, Supabase
Supabase는 구글 Firebase를 엔터프라이즈 레벨에서도 사용 가능하도록 만든 오픈소스 프로젝트이다. 현재는 베타 서비스이다.