Krong Dev.
DevOps AWS EC2

NestJS 백엔드를 AWS EC2에 올리기 — Vercel 쓰던 금쪽이의 시련

AWS와 EC2의 기초 개념부터 인스턴스 생성, 보안 그룹, SSH 접속, RDS 대신 Docker로 DB를 올린 이유까지 정리합니다.

NestJS 백엔드를 AWS EC2에 올리기 — Vercel 쓰던 금쪽이의 시련

NestJS로 서버를 만들었습니다. 로컬에서 pnpm dev를 치면 잘 돌아갑니다. 그런데 이 서버에 외부에서 접속하려면 어떻게 해야 할까요?
제 노트북은 항상 켜져 있지 않습니다.

서버를 ‘항상 켜진 컴퓨터’에 올리려면 무엇이 필요할까요?

AWS EC2가 무엇이고, 왜 이걸 선택했을까요?

이 글에서는 클라우드와 EC2의 기초 개념부터, 실제 인스턴스를 생성하고 NestJS 서버를 올리기까지의 과정을 정리합니다. 나중에 같은 스택으로 다시 배포할 때 처음부터 찾아보지 않아도 되도록, 설명서 수준으로 기록합니다.


왜 클라우드인가

”내 노트북을 서버로 쓰면 안 되나요?”

이론적으로는 됩니다. 노트북에 공인 IP를 할당받고, 방화벽을 열고, 24시간 켜두면 됩니다. 하지만 현실에서는 이런 문제가 생깁니다.

  • 노트북을 닫으면 서버가 꺼집니다.
  • ISP가 유동 IP를 제공하면 재접속할 때마다 주소가 바뀝니다.
  • 하드웨어가 고장나면 복구 방법이 없습니다.
  • 개발 환경과 서버 환경이 뒤섞입니다.

클라우드는 이 문제를 해결하기 위해 등장했습니다.
내가 서버 하드웨어를 직접 사거나 관리하지 않고, 데이터센터에 있는 컴퓨터를 빌려서 씁니다.
필요할 때 만들고, 필요 없으면 사용하지 않고, 사용한 만큼만 비용을 냅니다.


왜 AWS인가

AWS 공식 이미지
출처: AWS

AWS(Amazon Web Services)는 현재 가장 많이 쓰이는 클라우드 플랫폼입니다.

점유율이 높다는 것은 레퍼런스가 많다는 뜻입니다.
문제가 생겼을 때도 검색으로 해결책을 찾을 확률이 높습니다.
취업 시장에서도 AWS 경험이 가장 많이 요구되는 것으로 보입니다.

이번 프로젝트에서는 학습 목적으로 AWS 프리티어(Free Tier)를 사용했습니다.
계정 생성 후 12개월 동안 특정 서비스를 무료로 사용할 수 있는 플랜입니다.


EC2란 무엇인가

EC2의 핵심 개념들

EC2(Elastic Compute Cloud)는 AWS에서 제공하는 가상 서버 서비스입니다.

물리 서버를 데이터센터에 두고, 그 위에 가상화 기술로 여러 개의 서버를 나눠서 제공합니다.

“Elastic(탄력적)“이라는 이름처럼 필요에 따라 서버 사양을 올리거나 내릴 수 있습니다.

인스턴스 (Instance)

EC2에서 하나의 가상 서버를 인스턴스라고 부릅니다.
인스턴스를 생성할 때 CPU, 메모리, 스토리지 용량을 결정합니다.
인스턴스는 언제든 시작(start), 중지(stop), 종료(terminate)할 수 있습니다.

중지는 전원을 끄는 것으로, 데이터는 유지됩니다. 종료는 삭제하는 것으로, 인스턴스가 사라집니다.


AMI (Amazon Machine Image)

인스턴스를 만들 때 운영체제와 기본 소프트웨어를 담은 틀을 선택합니다.
이것이 AMI입니다.

Ubuntu, Amazon Linux, Windows 등 다양한 AMI가 있습니다.
이번 프로젝트에서는 Ubuntu 24.04 LTS를 선택했습니다.
가장 널리 쓰이는 Linux 배포판이고, Docker 설치 레퍼런스가 풍부합니다.


인스턴스 유형 (Instance Type)

인스턴스의 CPU·메모리 사양을 결정하는 것이 인스턴스 유형입니다.
t4g.small처럼 문자와 숫자로 표현됩니다.

t  4  g  .  micro
│  │  │     └─ 크기 (nano < micro < small < medium < large < ...)
│  │  └─ 옵션 (g = AWS Graviton, ARM 아키텍처)
│  └─ 세대 (숫자가 높을수록 최신)
└─ 패밀리 (t = 범용, c = 컴퓨팅 최적화, r = 메모리 최적화...)

이번 프로젝트에서는 t4g.small를 선택했습니다.
AWS Graviton2 프로세서 기반으로 ARM64(aarch64) 아키텍처입니다.
프리티어 대상이면서 동급 Intel 인스턴스보다 가성비가 높습니다.

ARM64 아키텍처를 선택했다는 사실이 Docker 배포에서 중요한 변수가 됩니다. 이 내용은 다음 글에서 이어집니다.


키 페어 (Key Pair)

키 페어 생성 화면

EC2 인스턴스에 SSH로 접속하려면 키 페어가 필요합니다.
공개 키는 인스턴스에 저장되고, 비공개 키(.pem 파일)는 내 컴퓨터에 보관합니다.
접속 시 비공개 키로 신원을 증명합니다.

.pem 파일은 인스턴스 생성 시 한 번만 발급됩니다.
분실하면 해당 인스턴스에 SSH 접속이 영구적으로 불가능합니다.
반드시 안전한 곳에 보관해야 합니다.


보안 그룹 (Security Group)

EC2 인스턴스에 드나드는 트래픽을 제어하는 방화벽이 보안 그룹입니다.
인바운드 규칙(들어오는 트래픽)과 아웃바운드 규칙(나가는 트래픽)으로 나뉩니다.

기본적으로 모든 인바운드는 차단됩니다.
필요한 포트만 명시적으로 열어야 합니다.

이번 프로젝트에서 설정한 인바운드 규칙입니다.

유형포트소스용도
SSH22내 IP서버 직접 접속
HTTP800.0.0.0/0웹 트래픽 (추후 사용)
사용자 정의 TCP80800.0.0.0/0NestJS API 서버

SSH 포트(22)는 모든 IP(0.0.0.0/0)에 열어두지 않는 것이 좋습니다. 내 IP만 허용하거나 필요할 때만 임시로 열고 닫는 방식이 안전합니다.


탄력적 IP (Elastic IP)

EC2 인스턴스를 중지하고 다시 시작하면 공인 IP가 바뀝니다.
매번 바뀌는 IP로는 도메인을 연결하기 어렵고, 외부에서 일관된 주소로 접근할 수 없습니다.

탄력적 IP는 고정 공인 IP를 발급받아 인스턴스에 연결하는 기능입니다.
인스턴스를 재시작해도 IP가 유지됩니다.

탄력적 IP는 인스턴스에 연결되어 있는 동안은 무료입니다.
인스턴스에 연결되지 않은 상태로 방치하면 비용이 발생합니다.


EC2 인스턴스 생성하기

인스턴스 생성 순서

AWS 콘솔에 로그인한 뒤 EC2 대시보드로 이동합니다.
인스턴스 시작 버튼을 클릭합니다.

1단계 — 이름 및 AMI 선택

AMI 선택 화면

인스턴스 이름을 입력합니다. (예: todolist-server)
AMI는 Ubuntu Server 24.04 LTS (HVM), SSD Volume Type을 선택합니다.
아키텍처는 반드시 64비트(Arm)를 선택합니다.


2단계 — 인스턴스 유형 선택

인스턴스 유형 선택 화면

t4g.small을 선택합니다.
vCPU 2개, 메모리 1GB 사양의 프리티어 대상 인스턴스입니다.


3단계 — 키 페어 생성

키 페어 생성 화면

새 키 페어 생성을 클릭합니다.
이름을 입력하고 RSA, .pem 형식으로 생성합니다.
자동으로 다운로드되는 .pem 파일을 안전한 위치(예: ~/.ssh/)에 보관합니다.

# 키 파일은 소유자만 읽을 수 있도록 권한을 설정해야 SSH가 연결됨
chmod 400 ~/.ssh/my-key.pem

4단계 — 보안 그룹 설정

보안 그룹 설정 화면

새 보안 그룹 생성을 선택합니다.
인바운드 규칙을 추가합니다.

  • SSH (22): 내 IP
  • HTTP (80): 위치 무관
  • 사용자 정의 TCP 8080: 위치 무관

5단계 — 스토리지 설정

스토리지 설정 화면

스토리지는 30 GiB gp3로 설정합니다. 프리티어 한도(30 GiB) 안이면 추가 비용 없고,
Docker·PostgreSQL을 같은 인스턴스에 올릴 때 디스크 부족을 피하기 좋습니다.


6단계 — 탄력적 IP (선택)

탄력적 IP 연결

운영 환경에서는 탄력적 IP로 공인 IP를 고정하는 것이 정석입니다.
인스턴스가 실행 중이 된 뒤 왼쪽 메뉴 탄력적 IP 주소탄력적 IP 주소 할당할당으로 IP를 발급하고,
발급된 IP를 선택해 탄력적 IP 주소 연결로 방금 만든 인스턴스에 붙입니다.

저는 이 단계를 건너뛰었습니다.
탄력적 IP는 인스턴스에 연결된 동안은 무료지만, 인스턴스를 자주 중지·해제하는 학습 단계에서는 연결이 끊긴 IP를 방치하면 비용이 나갈 수 있습니다.

비용을 최소화하려고 고정 IP 없이 진행했고, 도메인을 붙이거나 24시간 서비스를 유지하는 단계가 아니라 IP가 바뀌어도 콘솔에서 공인 IPv4만 다시 확인하면 되는 수준이라 실제로 큰 불편은 없었습니다.


SSH로 서버 접속하기

인스턴스가 실행 중인 상태에서 SSH로 접속합니다.

SSH 접속 성공

EC2 콘솔의 인스턴스 상세 화면에서 공인 IPv4 주소를 확인한 뒤 접속합니다.

ssh -i ~/.ssh/my-key.pem ubuntu@[공인-IPv4]

Ubuntu AMI의 기본 사용자명은 ubuntu입니다.
Amazon Linux라면 ec2-user, CentOS라면 centos입니다.

접속에 성공하면 다음과 같은 프롬프트가 나타납니다.

ubuntu@ip-172-xx-xx-xx:~$

처음 접속 후 시스템 패키지를 최신 상태로 업데이트합니다.

sudo apt update && sudo apt upgrade -y

자주 쓰는 SSH 연결은 ~/.ssh/config에 별칭을 등록하면 편합니다.

# ~/.ssh/config
Host todo-ec2
  HostName [공인-IPv4]
  User ubuntu
  IdentityFile ~/.ssh/my-key.pem

등록 후에는 ssh todo-ec2만 입력하면 됩니다.


RDS 대신 Docker를 선택한 이유

프리티어(Free Tier)의 함정과 선택

처음 계획은 AWS RDS(Relational Database Service) 로 PostgreSQL을 올리는 것이었습니다.

그런데 RDS 인스턴스를 생성하려고 콘솔을 열었을 때 문제가 생겼습니다.
프리티어(Free Tier) 대상인 db.t3.microdb.t4g.micro 인스턴스 유형 선택이 되지 않았습니다.

RDS 인스턴스 유형 선택 불가

계정 리전 설정이나 프리티어 조건 문제일 수도 있었습니다.
당장 원인 파악에 시간을 쓰기보다, 다른 방법을 택하기로 했습니다.

해결 방법을 아시는 분은 도움 부탁드립니다… 🙏 메일 보내기

결론: EC2 인스턴스에 Docker로 PostgreSQL 컨테이너를 직접 띄운다.

이 방식의 장단점을 정리하면 다음과 같습니다.

항목EC2 + DockerRDS
추가 비용없음 (EC2 요금에 포함)별도 DB 인스턴스 요금 발생
관리백업·패치를 직접 처리AWS가 자동 관리
학습 측면Docker·컨테이너 실습 가능추상화된 서비스, 내부 숨겨짐
복잡도네트워크·볼륨 직접 설정 필요연결 문자열만 알면 됨

학습 목적의 프로젝트에서는 Docker로 직접 PostgreSQL을 올리는 것이 오히려 더 많은 것을 배울 수 있는 선택이었습니다.
Docker 네트워크, 볼륨, 컨테이너 생명주기까지 직접 다뤄볼 수 있습니다.

EC2에 Docker를 설치하고 PostgreSQL과 NestJS 서버를 컨테이너로 올리는 방법은 다음 글에서 이어집니다.


마치며

처음 AWS 콘솔을 열었을 때 수많은 서비스 목록에 압도됐습니다.
하지만 EC2로 서버를 올리기 위해 반드시 알아야 할 개념은 생각보다 한정적입니다.

  • 인스턴스는 빌려 쓰는 가상 서버입니다. 사양은 인스턴스 유형으로 결정합니다.
  • Graviton(ARM) 인스턴스는 가성비가 좋습니다. 단, 빌드 환경과 아키텍처를 맞춰야 합니다.
  • 키 페어는 SSH 접속의 열쇠입니다. 분실하면 접속이 영구 불가능하므로 절대 안전하게 보관합니다.
  • 보안 그룹이 방화벽입니다. 필요한 포트만 열고, SSH는 내 IP만 허용하는 것이 안전합니다.
  • 탄력적 IP는 도메인·운영 단계에서 IP를 고정하는 정석입니다. 학습용으로 인스턴스를 자주 끄고 켠다면 생략하고 공인 IPv4로 접속해도 됩니다.

다음 글에서는 EC2에 Docker를 설치하고, PostgreSQL과 NestJS 서버를 컨테이너로 띄우는 과정을 다룹니다.

다음 글 — Docker로 NestJS와 PostgreSQL 배포하기