nestjs와 typeorm 그리고 postgres 환경에서 docker-compose로 한번에 띄우기
매번 개발을 할 때 db(postgres)를 docker로 띄웠었습니다. 그리고 서버를 따로 띄웠었습니다. 이 과정이 너무나 귀찮았기 때문에 서버+db를 한번에 띄우는걸 해보자 라고 결심했습니다.
제가 생각했던 흐름은
- 서버를 docker로 띄운다.
- db를 docker로 띄운다.(이미 postgres를 5432로 띄우고 있음)
- 서로 연결시켜준다.
위 과정을 진행시키려고 했습니다.
서버를 docker로 띄웁니다.
처음에 docker라는 계념을 잘 몰라서 서버를 Docker로 만다(?)라는 의미를 몰랐습니다. 이제는 알것도 같은데요. 서버를 docker 이미지 파일로 만든다 라는 뜻으로 이해하고 있습니다.
제가 이해한 docker의 계념은 어떤 환경에서든 잘 풀어쓸 수 있는 설계도를 이미지라 생각하고 설계도대로 물건을 만들면 컨테이너가 된다고 생각합니다.
제가 이해한 docker의 장점은 어떤 환경에서든 똑같은 물건을 만들어준다는게 장점이라고 합니다. 이를 “생산성”이라는 말로 멋지게 말할 수 있습니다.
서버를 docker 이미지 파일로 만들겠습니다.
🟦 먼저 Docker 이미지화 진행하고 싶은 프로젝트 root 경로에 Dockerfile을 생성합니다.
Dockerfile에 프로젝트 image화를 진행하기 위한 스크립트를 작성해줍니다.
Docker 파일 내용에는 본인 프로젝트를 도커 이미지화할 수 있는 스크립트가 포함되게 됩니다.
Dockerfile
FROM node:18 # 베이스 이미지
RUN mkdir -p /var/app # RUN 명령뒤에 리눅스 커맨드 사용가능
WORKDIR /var/app # 실행 위치 설정
COPY . . # COPY [복사할 파일] [복사할 목적지]
RUN npm install # 실행 시 node_module 설치를 위함
RUN npm run build # 스크립트 실행히 자동으로 빌드가 되도록
EXPOSE 3586 # 실행할 포트를 열기
CMD ["node", "dist/main.js"] # dist 파일의 main.js(빌드된 프로젝트)를 실행시키위한 명령어
이렇게 Dockerfile 생성을 완료했다면, 프로젝트를 이미지화 할 때 본인이 원하지 않은 파일까지 이미지화되는 것을 방지하기 위해 .dockerignore 파일도 생성해 줍니다.
.dockerignore
.git
Dockerfile
node_modules
dist
.dockerignore 은 .gitignore와 비슷하죠? 이것도 Dockerfile이 위치해있는 루트 폴더에 넣어줍니다.
🟦 docker build
이렇게 Dockerfile과 .dockerignore 파일을 구성한 이후 아래 명령어를 실행합니다.
docker build . -t [프로젝트 이름]
그럼 본인의 프로젝트가 이미지로 빌드되는 화면을 볼 수 있는데요.
프로젝트 이름은 무조건 lowercase로 해야하는거같습니다. 안그러면 repository name must be lowercase 라는 오류가 뜹니다.
🟦 docker container start
이미지를 만들었다면 실제로 물건을 만들어봐야 합니다.
프로젝트 이름의 이미지가 잘 생성되었다면 아래 명령어를 입력해 봅시다.
(window 에서 Docker Desktop은 반드시 실행되어야 합니다.)
docker run -d --name [컨테이너 이름] [프로젝트 이미지 이름]
- -d: 는 detact 입니다. Run container in background and print counter ID(백그라운드로 컨테이너를 돌립니다. 그리고 id의 개수를 프린트해줍니다.)
- -p: public list 입니다. Publish a container’s port(s) to the host ( 호스트의 포트를 정할 수 있다?)
자, 여기까지 잘 실행되었습니다. db도 띄웠고 서버도 따로 띄웠습니다. 하지만 문제가 생겼습니다.
👀 문제
아래와 같이 Connection 오류가 발생합니다.
Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1146:16)
에러 로그에 나와있듯이 이는 접근하고자 하는 DB의 주소가 localhost (127.0.0.1) 이기 때문입니다.
Docker 입장에서 localhost는 내 PC일까요?
당연하게도 Docker 내부가 localhost 이기 때문에 현재 Docker 컨테이너 내부에서 PostgreSQL DB를 찾으려했지만 찾지 못하여 발생한 것입니다.
이를 해결하려면 DB의 접속 주소를 link된 DB로 인식할 수 있도록 코드 변경이 필요합니다.
기존에는 postgres 컨테이너만 돌리고 local로 server를 돌려서 서로간의 영역을 인지를 했습니다. 하지만 바로 컨테이너간 통신에, locahost를 사용할 수 없다고 합니다.
이유는 컨테이너는 개별 호스트처럼 이식되어 개별 컨테이너마다 localhost를 본인으로 지정하게 됩니다.
그래서 찾아보니 docker network를 같이하면 된다고 합니다.
(docker network에 대한 글)
하지만 위 방법으로 하면 명령어를 2번 치는것입니다. 옛날에 하던 방법과 똑같죠. 그래서 다른방법을 찾아봤습니다.
🟦 docker-compose
docker-compose란 여러 개의 컨테이너로부터 이루어진 서비스를 구축, 실행하는 순서를 자동으로 편리하게 관리를 간단히 하는 기능입니다.
컨테이너를 하나하나 명령어를 치지 않고 파일안에서 한꺼번에 관리할 수 있게합니다.
docker-compose.yml 파일
version: '3.8'
services:
db:
image: postgres
ports:
- '5432:5432'
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=test
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=1234
- POSTGRES_INITDB_ARGS=--encoding=UTF-8
app:
build:
context: .
dockerfile: ./Dockerfile
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=postgres
- DB_USERNAME=postgres
- DB_PASSWORD=1234
depends_on:
- db
ports:
- "3586:3586"
volumes:
- ./:/app/
volumes:
db_data:
.yml 이란 확장자가 나오는데요. YAML(YAML Ain't Markup Language)은 사람이 쉽게 읽고 작성할 수 있는 데이터 직렬화 형식입니다. YAML은 계층적인 구조를 갖고 있으며, 들여쓰기를 통해 데이터의 계층 구조를 나타냅니다. 파이썬과 비슷하죠? 보기 쉽게 환경변수를 구성할 수 있습니다.
docker-compose 설정
원문
2. Docker Compose로 Nodejs 개발/배포환경 구성하기 - Docker Compose로 개선하기 (tistory.com)
원문을 꼭 보세요! 더 자세한 설명이 있습니다.
🟩 version: 3.8
- compose 파일 버전을 명시합니다.
- 공식문서(Compose file versions and upgrading | Docker Documentation) 를 참고해서 버전을 설정하면 됩니다.
🟩 service
- docker-compose로 관리할 컨테이너를 명시합니다.
- 컨테이너 = 서비스(services)
- 명시한 순서대로 실행됩니다.
🟩 db 서비스 설정
image
- 도커 이미지 입니다. 도커 공식 postgres 이미지를 사용합니다.
ports
- 어떤 port로 db를 열지 설정합니다. 저는 5432포트로 접근하게끔 설정했습니다.
volumes
- 컨테이너를 껏다 키면 db가 초기화 됩니다! 이를 방지하기 위함입니다.
- : 데이터베이스 컨테이너 내부의 디렉터리를 로컬 컴퓨터의 디렉터리로 연결하였습니다. 이제 데이터베이스가 데이터를 저장할 파일 시스템으로 로컬 컴퓨터(/var/lib/postgresql/data)의 디렉터리를 사용합니다.
enviroment
- docker run실행시 -e 옵션에 적었던 환경변수입니다. 환경변수는 컨테이너에 박혀서 실행됩니다.
🟩 app 서비스 설정
- build
- DB와 같이 이미지를 사용하는 것이 아니기 때문에 build를 사용합니다.
- docker build 명령어를 수행합니다.
- context
- docker build 를 실행할 디렉토리입니다.
- .로 했기 때문에 현재 위치가 됩니다.
- dockerfile
- 빌드에 사용될 Dockerfile을 선택합니다.
- depends_on
- 특정 서비스에 대한 의존도를 나타냅니다.
- ports
- 외부 포트와 컨테이너내 포트를 연결합니다.
- docker run 명령어의 p 옵션과 동일합니다.
- volumes
- 로컬 PC의 디렉토리와 컨테이너 안의 디렉토리를 연결합니다.
docker-compose에서는 —link가 없더라도, 동일한 docker-compose.yml에 속해있는 services들은 서로 통신할 수 있는 지원합니다.
🟦 실행
명령어로 실행합니다.
docker-compose up
db와 app이 모두 실행되면 성공입니다.
-d 옵션을 붙이면 백그라운드 실행합니다.
docker-compose up -d
만약 Dockerfile도 변경되어서 이미지 생성부터 다시해아한다면 --build를 사용하면 됩니다.
docker-compose up -d--build
🟦 서비스 실행/종료
한번 실행된 docker 서비스를 실행/종료 합니다.
docker-compose stop
docker-compose start
이 둘을 합친 restart 옵션도 사용할 수 있습니다.
docker-compose restart
마무리
개발 속도를 빠르게 하기 위한 설정이였습니다. 배포할 때도 잘 동작하면 좋겠습니다.
참고
- Docker Compose 네트워크 | Engineering Blog by Dale Seo
- 1. Docker Compose로 Nodejs 개발/배포환경 구성하기 - Dockerfile로 구성하기 (tistory.com)
- 2. Docker Compose로 Nodejs 개발/배포환경 구성하기 - Docker Compose로 개선하기 (tistory.com)
- [Docker] docker compose 사용법 (tistory.com)
- Nestjs 프로젝트를 Docker로 배포하기 :: 코드 기어 Code Gear - 개발 강좌 (tistory.com)