NestJS 배포 전 과정 톺아보기

원민관·2025년 9월 15일

deployment

목록 보기
7/7
post-thumbnail

0. 백엔드 배포 과정 🎯

NestJS 백엔드 프로젝트를 Amazon 서비스를 통해 배포하는 과정을 다룹니다. 추가적으로 Github Actions를 활용해서 수정 사항의 통합과 배포의 자동화 즉, CI/CD를 구축하는 전 과정에 대해 기술하고자 합니다.

이전 글에서는 React 프로젝트를 Amazon 서비스를 통해 배포하는 과정을 설명했습니다. 백엔드를 Amazon 서비스를 통해 배포하는 과정은 프론트엔드 배포 과정에 비해 사용하는 AWS 서비스가 많아서 조금 더 복잡한 면이 있습니다.

하지만 복잡함이 항상 어려움과 일치하는 것은 아닙니다. 단계를 분할해서 하나씩 정복하면 충분히 이해하고, 개인의 상황에 맞게 적용할 수 있습니다.


각 서비스별 세부 설정은 이미 다룬 바 있습니다.

https://velog.io/@minkwan/Amazon-EC2-톺아보기
https://velog.io/@minkwan/Amazon-Route-53-HTTPS
https://velog.io/@minkwan/Amazon-RDS-톺아보기

이번 글의 목적은 전체적인 흐름을 이해하는 것입니다.

세부적인 설정은 구슬입니다. 구슬은 꿰어야 보배죠. 세부적인 설정에 대한 자료는 너무 많고, 막상 해보면 그리 어렵지 않습니다. 오히려 각 세부 설정들을 실로 꿸 때 어떤 어려움이 발생하는지를 아는 것이 더 중요하다고 생각합니다.

실제로 구슬을 실로 엮는 과정에 대한 자료는 그리 많지 않습니다. 설정에 대한 practice는 많이 봤지만 여전히 완전한 배포에 어려움을 겪고 있는 분께 이 글이 도움이 되길 바랍니다.

1. EC2 인스턴스 구성 🎯

1-1. EC2 기본 설정 ✍️

인스턴스 생성 단계에서는 다음 세 가지를 설정합니다.

  1. 이름 및 태그
  2. Operating System
  3. 인스턴스 유형

이름 및 태그는 인스턴스를 식별하기 위해 설정하는 식별자입니다.

저는 pullim-server라고 명명했습니다. 배포 과정에서 막히는 경우가 많아 인스턴스를 여러 번 생성해 볼 수 있습니다. 게다가 배포를 이번에만 할 것은 아니죠. 앞으로도 인스턴스를 생성할 일이 많을 텐데, 식별자를 제대로 네이밍 하지 않으면 나중에 혼란스러운 상황에 봉착할 수 있습니다.

오랜 시간이 지나도 인스턴스를 인지하는 데 혼동이 없도록, 이름을 명확하게 설정하는 것이 좋겠습니다.


다음으로 OS를 설정해야 합니다.

Windows나 macOS는 우리에게 더 친숙한 OS 일 수 있습니다. Windows나 macOS는, OS를 모르는 사람도 사용하는 데 어려움이 없도록 편리한 기능을 많이 넣어놓은 OS입니다.

다만, 그 편리함을 위해 추가한 기능들이 많은 용량을 차지하고, 용량 때문에 서버의 OS로 사용할 때에는 성능이 다소 떨어질 수 있습니다. 추가적으로, 처음 배포하는 입장에서 찾아볼 수 있는 자료가 많다는 것은 엄청난 장점인데요, 얼핏 봤을 때 Ubuntu에 대한 자료가 많다고 생각해서 최종적으로 Ubuntu를 선택하게 되었습니다.

사실 배포 과정은 하드웨어와 밀접한 관련이 있어서 추가적인 공부가 필요한 영역입니다. 우선은 배포에 성공하고 후에 기술 부채를 갚아나가는 방식을 택하시길 권합니다. 처음부터 다 알 수는 없으니까요.


기본 설정에서는 마지막으로 인스턴스 유형을 설정해야 합니다.

인스턴스 유형은 컴퓨터 사양을 의미한다고 볼 수 있습니다.

글을 작성하고 있는 25년 9월 15일 기준으로, t3.micro가 프리 티어로 사용 가능한 가장 성능이 좋은 유형이니 t3.micro를 선택하면 되겠습니다.

1-2. 보안 그룹 설정 ✍️

보안 그룹은 EC2라는 집을 둘러싼 담장과 대문이라고 볼 수 있습니다.

EC2 서버 컴퓨터에 들어오는 요청을 인바운드 트래픽, EC2 서버 컴퓨터가 반환하는 응답을 아웃바운드 트래픽이라고 합니다. 트래픽은 손님으로 비유할 수 있겠네요.

특히 인바운드 트래픽 설정이 중요합니다. 들어올 때에는 아무나 들어오면 안 되겠죠. 다음 네 가지를 설정하시면 됩니다.

  1. SSH - 포트 범위 22
  2. HTTP - 80
  3. HTTPS - 443
  4. 사용자 지정 TCP - 5432

SSH는 우리가 임대한 EC2 컴퓨터에 접속하기 위한 요청입니다. 컴퓨터를 임대해놓고 들어가지 못하면 안 되겠죠.

HTTP / HTTPS는 우리가 앞서 배포한 프론트엔드의 요청입니다.

마지막으로 사용자 지정 TCP는, EC2의 백엔드 애플리케이션이 외부에서 들어오는 데이터베이스 연결 요청을 받기 위해 사용합니다. 저는 PostgreSQL을 사용하기 때문에 5432로 지정했지만, DB의 포트 번호는 사용하는 Database 별로 상이하다는 점을 인지해야 합니다.

1-3. 스토리지 구성 ✍️

EC2가 컴퓨터라면 스토리지는 하드 디스크에 해당합니다. EC2도 결국 컴퓨터이기에 저장 공간이 필요하겠죠.

EBS(Elastic Block Storage)가 정확한 표현입니다.

글을 작성하고 있는 25년 9월 15일 기준으로, 30GiB(크기)와 gp3(볼륨 유형)가 프리 티어로 사용할 수 있는 최고 사양이니 참고하시길 바랍니다.

1-4. 탄력적 IP 설정 ✍️

위 과정을 따라서 EC2를 생성하면, 우리가 임대한 컴퓨터에 주소(IP)가 할당됩니다. 그런데 인스턴스를 재시동하면 IP가 바뀝니다. 즉 임시적인 IP입니다. 그런데 AWS는 왜 불편하게 IP를 임시적으로 할당하는 것일까요? 처음부터 고정적인 IP를 할당하면 될 텐데 말이죠.

AWS는 전 세계 수많은 사용자들에게 서비스를 제공해야 합니다. 고정 IP를 모든 인스턴스에 할당하면 IP 주소가 부족해지는 현상이 발생합니다. 사용하지 않는 인스턴스의 IP까지 예약해두는 것이 AWS 입장에서는 오히려 비효율적인 행동인 것이죠.

탄력적 IP를 할당한 뒤 우리가 임대한 EC2에 연결하면, 인스턴스를 재시동해도 고정적인 IP를 할당받는 것을 확인할 수 있습니다.

2. 서버 환경 구축 🎯

우리는 지금까지 컴퓨터를 빌리는 작업을 진행했습니다. 컴퓨터의 OS와 보안 그룹과 스토리지를 설정했고, 추가적으로 고정 IP도 할당했습니다.

이제 Github Repository에 저장해놓은 내 소스 코드를 임대한 EC2에서 클론 받아서, 실제로 서버 컴퓨터에서 나의 백엔드 소스가 돌아가도록 설정해야 합니다. 즉 내 컴퓨터에서 남의 컴퓨터를 조작하는 방법을 다룰 것입니다.

2-1. Node.js 설치 ✍️

$ sudo su
$ apt-get update && /
apt-get install -y ca-certificates curl gnupg && /
mkdir -p /etc/apt/keyrings && /
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && /
NODE_MAJOR=20 && /
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list && /
apt-get update && /
apt-get install nodejs -y

리눅스 명령어에 대한 설명은 다루지 않겠습니다. 위 명령어를 작성합니다.

2-2. 프로젝트 클론 및 의존성 설치 ✍️

$ git clone <깃허브-레포지토리>
$ cd <백엔드-경로>
$ npm i

Github Repository에서 내 프로젝트를 clone 한 뒤 npm을 설치합니다. yarn을 설치해도 됩니다. 실제로 저는 yarn을 설치했지만, npm이 조금 더 보편적인 설명에 해당한다고 생각해서 npm으로 설명했습니다.

2-3. 환경 변수 설정 ✍️

$ nano .env

nano를 통해 .env를 생성합니다. 우리가 로컬에서 백엔드를 실행했을 때 정상적으로 동작했던 이유는 로컬에 맞는 .env가 작성되어 있었기 때문입니다.

그런데 .env에는 각종 민감 정보가 들어있기 때문에 .gitignore 파일에 .env를 등록했습니다. Github Repository에는 .env 내용이 저장되지 않게 하기 위해서였죠.

그런데 내가 임대한 컴퓨터에서 해당 소스를 clone 하면 .env 내용이 없기 때문에 서버가 정상적으로 돌아가지 않을 것입니다. 그래서 실제 상용 서비스에 맞는 값들이 적용된 .env를 추가적으로 작성해야 하는 것입니다.

2-4. PM2 설치 및 프로세스 관리 ✍️

$ sudo npm i -g pm2

pm2는 Node.js를 위한 프로세스 매니저입니다. 터미널을 닫으면 서버가 종료됩니다. pm2를 통해 서버를 실행하면 Auto Restart를 통해 터미널을 닫아도 서버가 계속 실행됩니다.

3. 도메인 및 SSL 설정 🎯

3-1. Route 53 도메인 설정 ✍️

Route 53은 우리가 구매한 도메인을 IP와 매핑하기 위해 사용하는 것이라고, 프론트엔드 배포 글에서 다룬 바 있습니다. 이번에는 api.pullim.co.kr이라는 도메인을 EC2 IP에 직접 매핑합니다.

이전 글에서는 ELB를 EC2 앞단에 배치했습니다.

https://velog.io/@minkwan/Amazon-Route-53-HTTPS

ELB는 서버를 2대 이상 운용할 때, 트래픽을 적절히 분해하기 위해 사용하는 로드 밸런서입니다. ELB의 주요 기능이라고 할 수 있는 로드 밸런싱 기능이 아니라, 부가 기능인 SSL/TLS(HTTPS)를 적용하기 위해서 사용했었죠.

그런데 ELB는 한 달 운용 비용이 5달러~20달러 정도 부과됩니다. 로드 밸런싱 기능을 쓰는 것도 아니고 단순히 HTTPS를 적용하기 위해서 매달 5달러~20달러의 비용을 지불하는 것은 합리적이지 않다고 판단했습니다. 물론 이렇게 되면, 서버가 고장 나면 끝입니다. 최소한 모니터링은 해야겠다는 생각이 들었고, 이에 대해서는 차후에 별도로 정리할 생각입니다.

결국 위에서 제시한 문제를 해결하기 위해 Nginx와 Certbot을 도입하게 되었습니다.

3-2. Nginx 설치 및 구성 ✍️

Nginx는 ELB처럼 로드 밸런싱 기능을 제공하는 웹 서버입니다. 저는 Nginx의 리버스 프록시 기능을 사용할 것입니다. 리버스 프록시를 이해하기 위해서는 포워드 프록시에 대해 먼저 알아야 합니다.

프록시는 대리자입니다. 요청과 응답을 대리해서 처리하는 것이 본질이죠.

포워드 프록시는 인터넷 앞 단에 배치한 대리자입니다. 사용자가 인터넷에 '직접적으로' 방문하는 것을 막습니다.

정부, 학교, 기업 등과 같은 기관에서는 인터넷에 접근하기 전에 특정 웹 사이트에 접근하는 것을 막을 필요가 있습니다. 유해 사이트나 업무과 관련 없는 사이트에 접속하는 것을 방지하기 위해 사용하는 것이죠. 모니터링과 로깅을 위해서 사용하기도 합니다.

리버스 프록시는 인터넷 뒷 단에 배치하는 대리자입니다.

사용자는 실제 서버를 직접 호출하지 않고, 리버스 프록시 서버에게 요청을 보냅니다. 리버스 프록시는 그 요청을 적절한 서버에게 전달하고, 서버의 응답을 다시 사용자에게 반환합니다. 즉, 외부에서는 리버스 프록시만 보이고 내부 서버들은 숨겨진 상태가 됩니다.

내부 서버의 실제 IP와 구조를 감춰서 직접 공격을 막을 수 있고, HTTPS 암호화/복호화를 리버스 프록시에서 처리하기에 뒤쪽 애플리케이션 서버는 복호화 된 트래픽만 받으므로 부담이 줄어들다는 장점도 있습니다.

$ sudo apt update
$ sudo apt install nginx
$ sudo service nginx status

위 명령어를 통해 EC2에 Nginx를 설치합니다.

프록시에 대해 이해할 때 참고한 영상입니다.

https://www.youtube.com/watch?v=Rt-KdCpsmdc

3-3. Certbot 설치 + SSL 인증서 발급 ✍️

Certbot는 Let's Encrypt에서 제공하는 무료 SSL/TLS 인증서를 자동으로 발급·갱신해주는 오픈소스 도구입니다. 원래의 목표였던 HTTPS 적용을 위해 사용하는 툴입니다.

$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

위 명령어를 통해 Certbot을 설치합니다.

$ sudo certbot --nginx -d <도메인 주소>

# 예시
$ sudo certbot --nginx -d pullim.io.kr

Nginx 설정 파일을 자동으로 수정해 HTTPS를 내가 원하는 도메인에 적용합니다.

3-4. 리버스 프록시 설정 + Nginx 재시작 ✍️

$ sudo nano /etc/nginx/sites-available/default
...
server {
	...

	server_name pullim.io.kr;
	
	location / {
		try_files $uri $uri/ =404;
		proxy_pass http://localhost:3000/;
	}
	...
}

Nginx 설정 파일 경로의 sites-available에서 proxy_pass를 등록합니다. 우리가 배치한 대리자가 요청을 3000번 포트로 전달하도록 설정한 것입니다.

$ sudo service nginx restart

마지막으로 Nginx를 재시작해서 변경 사항을 완벽하게 반영합니다.

4. 데이터베이스 구성 🎯

4-1. RDS PostgreSQL 생성 ✍️

Amazon RDS는 AWS에서 빌리는 데이터베이스 서비스입니다.

로컬 환경에서 개발할 때는 로컬 환경에 설치된 PostgreSQL과 같은 DB를 연결해서 사용하지만, 서버를 배포하고 나면 서버가 내 컴퓨터에 설치된 PostgreSQL과 연결을 할 수 없습니다. 그래서, DB도 외부 인터넷에서 접근할 수 있게 AWS RDS라는 데이터베이스를 빌려서 사용하는 것입니다.

EC2에 PostgreSQL를 직접 설치할 수도 있지만, 그렇다면 EC2와 운명을 함께하게 되겠죠. EC2가 고장 나면 DB도 사용할 수 없게 되기에 외부 서비스로 분리하는 것이 더 안정적입니다.

4-2. RDS 보안 그룹 설정 ✍️

보안 그룹 설정 시 Source를 EC2 보안 그룹으로 제한해야 합니다. 추가적으로, 인바운드 규칙을 정해야 합니다. 유형 드롭박스를 보면, 본인이 사용하는 DB 유형이 있을 것입니다. 해당하는 유형을 선택하면 됩니다.

4-3. EC2와 RDS 연결 ✍️

$ sudo apt update
$ sudo apt install postgresql-client -y
$ psql -h <RDS엔드포인트> -p 5432 -U <DB사용자이름> -d <DB이름>

이전에 EC2에서 '사용자 지정 TCP - 5432'를 제대로 설정했어야 잘 동작합니다.

5. CI/CD 자동화 🎯

5-1. Github Actions 사용 이유 ✍️

프론트엔드 CI/CD 적용 목적과 궤를 같이합니다. 백엔드 배포의 과정을 자동화하기 위해 Github Actions를 사용합니다.

https://velog.io/@minkwan/React-%EB%B0%B0%ED%8F%AC-%EC%A0%84-%EA%B3%BC%EC%A0%95-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0

5-2. GitHub Actions 설정 ✍️

# 워크플로우 이름
name: Deploy To EC2

# main 브랜치에 push 이벤트가 발생했을 때 실행
on:
  push:
    branches:
      - main

jobs:
  deploy:
    # 작업이 실행될 환경 
    runs-on: ubuntu-latest

    # 작업의 단계(step) 정의
    steps:
      - name: SSH to EC2 and Deploy
        # SSH 접속 및 스크립트 실행을 위한 Action
        uses: appleboy/ssh-action@v1.0.3
        # with 블록에 사용할 환경 변수들을 GitHub Secrets에서 가져옴
        with:
       	  # EC2 인스턴스의 public IP 또는 도메인
          host: ${{ secrets.EC2_HOST }}   
          # EC2 접속 유저 이름 (예: ubuntu, ec2-user)
          username: ${{ secrets.EC2_USERNAME }} 
          # EC2 접속을 위한 pem 키
          key: ${{ secrets.EC2_PRIVATE_KEY }} 
		  # EC2 접속 포트 (기본값 22)      
          port: ${{ secrets.EC2_SSH_PORT || 22 }} 
          # 스크립트 실행 중 오류 발생 시 즉시 중단
          script_stop: true                       
          script: |
                    
            cd /home/ubuntu/react-nest/server

            rm -f .env

            git pull origin main

            echo "${{ secrets.ENV }}" > .env

            npm install

            npm run build

            pm2 kill

            pm2 start dist/main.js --name "pullim-server"

위 플로우와 정확히 일치하지는 않을 수 있습니다. 본인의 상황에 맞게 변형하여 적용하면 됩니다.

6. 요약 🎯

  1. EC2 기본 설정(이름 / OS / 인스턴스 유형)
  2. EC2 보안 그룹 설정(SSH / HTTP / HTTPS / 사용자 지정 TCP(Database))
  3. EC2 스토리지(Elastic Block Storage) 설정
  4. 탄력적 IP 할당
  5. EC2에 Node.js 설치
  6. EC2에서 git repository 클론 후 패키지 설치
  7. nano 편집기로 프로덕션 버전 .env 추가
  8. PM2 설치
  9. Route 53에서 EC2의 IP를 우리가 구매한 도메인에 매핑
  10. Nginx 설치
  11. Certbot 설치
  12. Certbot으로 무료 SSL/TLS 인증서 발급
  13. 인증서를 Nginx에 등록
  14. Nginx 재시작
  15. RDS 생성 + 보안 그룹 설정
  16. EC2에 RDS 연결
  17. Github Actions로 백엔드 배포 과정 자동화
profile
Write a little every day, without hope, without despair ✍️

0개의 댓글