소스 : https://github.com/braverokmc79/nestjs-netflix
1. AWS Elastic Beanstalk 배포
.gihub/workflows/aws-deploy.yml
name: Deploy to AWS Elastic Beanstalk on: push: branches: - main jobs: build-and-deploy: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 steps: - name: Checkout Code uses: actions/checkout@v3 - name: Set Up NodeJS uses: actions/setup-node@v3 with: node-version: '18' - name: Create Env File env: ENV: ${{secrets.ENV}} DB_TYPE: ${{secrets.DB_TYPE}} DB_HOST: ${{secrets.DB_HOST}} DB_PORT: ${{secrets.DB_PORT}} DB_USERNAME: ${{secrets.DB_USERNAME}} DB_PASSWORD: ${{secrets.DB_PASSWORD}} DB_DATABASE: ${{secrets.DB_DATABASE}} HASH_ROUNDS: ${{secrets.HASH_ROUNDS}} ACCESS_TOKEN_SECRET: ${{secrets.ACCESS_TOKEN_SECRET}} REFRESH_TOKEN_SECRET: ${{secrets.REFRESH_TOKEN_SECRET}} AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} AWS_REGION: ${{secrets.AWS_REGION}} BUCKET_NAME: ${{secrets.BUCKET_NAME}} run: | touch test.env echo ENV="test" >> test.env echo DB_TYPE="$DB_TYPE" >> test.env echo DB_HOST="localhost" >> test.env echo DB_PORT="$DB_PORT" >> test.env echo DB_USERNAME="$DB_USERNAME" >> test.env echo DB_PASSWORD="$DB_PASSWORD" >> test.env echo DB_DATABASE="$DB_DATABASE" >> test.env echo HASH_ROUNDS="$HASH_ROUNDS" >> test.env echo ACCESS_TOKEN_SECRET="$ACCESS_TOKEN_SECRET" >> test.env echo REFRESH_TOKEN_SECRET="$REFRESH_TOKEN_SECRET" >> test.env echo AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" >> test.env echo AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" >> test.env echo AWS_REGION="$AWS_REGION" >> test.env echo BUCKET_NAME="$BUCKET_NAME" >> test.env echo "test.env created" cat test.env touch env.env echo ENV="$ENV" >> .env echo DB_TYPE="$DB_TYPE" >> .env echo DB_HOST="$DB_HOST" >> .env echo DB_PORT="$DB_PORT" >> .env echo DB_USERNAME="$DB_USERNAME" >> .env echo DB_PASSWORD="$DB_PASSWORD" >> .env echo DB_DATABASE="$DB_DATABASE" >> .env echo HASH_ROUNDS="$HASH_ROUNDS" >> .env echo ACCESS_TOKEN_SECRET="$ACCESS_TOKEN_SECRET" >> .env echo REFRESH_TOKEN_SECRET="$REFRESH_TOKEN_SECRET" >> .env echo AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" >> .env echo AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" >> .env echo AWS_REGION="$AWS_REGION" >> .env echo BUCKET_NAME="$BUCKET_NAME" >> .env echo ".env created" cat .env - name: Create Folders run: | mkdir -p ./public/movie mkdir -p ./public/temp - name: Install Depencies run: npm i - name: Build Project run: npm run build - name: Run Test run: npm run test - name: Install Typeorm run: npm i -g typeorm - name: Run Migration run: typeorm migration:run -d ./dist/database/data-source.js - name: Zip Artfact For Deployment run: zip -r deploy.zip . - name: Upload To S3 env: AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} AWS_REGION: ${{secrets.AWS_REGION}} run: | aws configure set region $AWS_REGION aws s3 cp deploy.zip s3://nestjs-netflix-bucket/deploy.zip - name: Deploy To AWS Elastic Beanstalk env: AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} AWS_REGION: ${{secrets.AWS_REGION}} run: | aws elasticbeanstalk create-application-version \ --application-name "nestjs-netflix" \ --version-label $GITHUB_SHA \ --source-bundle S3Bucket="nestjs-netflix-bucket",S3Key="deploy.zip" aws elasticbeanstalk update-environment \ --application-name "nestjs-netflix" \ --environment-name "Nestjs-netflix-env" \ --version-label $GITHUB_SHA
1)이름과 트리거 조건
name: Deploy to AWS Elastic Beanstalk on: push: branches: - main
이름: Deploy to AWS Elastic Beanstalk
트리거 조건: main 브랜치에 push가 발생할 때 워크플로 실행
2)jobs: build-and-deploy
실행 환경
runs-on: ubuntu-latest
Ubuntu 최신 버전에서 워크플로 실행
3) PostgreSQL 서비스 (CI 테스트용)
services: postgres: image: postgres:16 ...
PostgreSQL 16 버전을 도커로 실행
포트: 5432 사용
pg_isready를 통해 DB 헬스 체크 수행
4)1. 코드 체크아웃
- name: Checkout Code uses: actions/checkout@v3
현재 커밋된 코드를 워크플로에 가져옴
5) 2. Node.js 환경 설정
- name: Set Up NodeJS uses: actions/setup-node@v3 with: node-version: '18'
Node.js 18버전 설치
6) 3. .env 파일 생성
- name: Create Env File
CI/CD에 필요한 .env 파일과 test.env 파일을 생성
GitHub Secrets에서 민감한 정보들을 주입
localhost DB로 지정한 것은 CI 환경에서 테스트용 PostgreSQL을 사용하기 위함
7)폴더 생성
- name: Create Folders
./public/movie, ./public/temp 디렉토리 생성
8) 의존성 설치
- name: Install Dependencies run: npm i
9) 프로젝트 빌드
- name: Build Project run: npm run build
프로젝트 빌드 (예: NestJS 빌드)
10) 테스트 실행
- name: Run Test run: npm run test
테스트 코드 실행
11) TypeORM CLI 설치
- name: Install Typeorm run: npm i -g typeorm
전역으로 TypeORM CLI 설치 (마이그레이션 실행을 위해)
12) 마이그레이션 실행
- name: Run Migration run: typeorm migration:run -d ./dist/database/data-source.js
빌드된 data-source.js를 기반으로 DB 마이그레이션 실행
13) 배포용 압축파일 생성
- name: Zip Artifact For Deployment run: zip -r deploy.zip .
현재 프로젝트 전체를 deploy.zip으로 압축
14) S3 업로드
- name: Upload To S3 run: | aws configure set region $AWS_REGION aws s3 cp deploy.zip s3://nestjs-netflix-bucket/deploy.zip
deploy.zip을 S3 버킷에 업로드 (nestjs-netflix-bucket)
15) Elastic Beanstalk 배포
- name: Deploy To AWS Elastic Beanstalk run: | aws elasticbeanstalk create-application-version ... aws elasticbeanstalk update-environment ...
새 어플리케이션 버전 생성 (커밋 SHA를 버전 라벨로 사용)
해당 버전으로 Elastic Beanstalk 환경 업데이트
(환경명: Nestjs-netflix-env, 앱명: nestjs-netflix)
2. 우분트 22 SSH 배포
다음 참조
✅Spring Boot 3.4 애플리케이션을 우분투 22 서버에 GitHub Actions를 사용해 무중단 배포
1) SSH 키 기반 인증 준비
SSH Key 생성 및 서버 등록
1. 로컬에서 SSH 키 생성
여기서 github-actions@our-sample 는 임의 명이다.
github-deploy-key 또한 원하는 이름으로 변경하면된다.
중요한것은 깃허브에서 actions 에서 우분트 22 에 접속할수 있는 개인 키 등록과 우부트 22에 공개 키 설정이다.
PowerShell 사용 시 (경로를 명시적으로 써야 함)
ssh-keygen -t rsa -b 4096 -C "github-actions@our-sample" -f "$env:USERPROFILE\.ssh\github-deploy-key"
Git Bash 사용 시 (추천).
ssh-keygen -t rsa -b 4096 -C "github-actions@our-sample" -f ~/.ssh/github-deploy-key
2. GitHub에 개인 키 등록
github-deploy-key 내용을 복사
GitHub 저장소> 깃허브 해당 프로젝트에서 > Settings > Secrets and variables > Actions > New secret 이름은 예: DEPLOY_KEY 값을 복붙
3.서버에 공개 키 등록
권한설정 오류 방지하기 위해 root 디렉토리에 설정
서버에 .ssh 디렉토리 만들기
mkdir -p ~/.ssh chmod 700 ~/.ssh
공개 키 붙여넣기:
로컬에서 github-deploy-key.pub 파일 내용을 복사한 뒤, 서버에서:
중요한 오해 방지 서버에 ~/.ssh/github-deploy-key 추가하는 게 아닙니다.
서버는 공개 키만 필요하며, 개인 키는 절대 서버에 두지 않습니다.
개인 키: GitHub Actions에서만 사용
공개 키: 서버의 ~/.ssh/authorized_keys에만
서버 측 ~/.ssh/authorized_keys 확인
우분투 서버에 다음 내용이 등록되어야 합니다:
~/.ssh/authorized_keys 파일에 개인키에 대응되는 공개키가 포함되어야 합니다.
cat ~/.ssh/github-deploy-key.pub >> ~/.ssh/authorized_keys
루트에 설정해서 루트 권한을 가지게 됩니다
cat ~/.ssh/github-deploy-key.pub >> ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
비밀번호 인증이 막혀 있음 (sshd_config)
원인: SSH 서버에서 PasswordAuthentication no로 설정되어 있으면, 비밀번호 로그인이 불가능합니다.
해결 방법:
서버에서 다음 명령어로 설정 파일 수정:
sudo vi /etc/ssh/sshd_config
다음 항목을 찾아서 아래처럼 수정:
PermitRootLogin yes PasswordAuthentication yes
sudo systemctl restart ssh
설치
sudo apt install nodejs npm npm install -g pm2
2) GitHub에 Secrets 등록
GitHub > Settings > Secrets and variables > Actions > New repository secret
아래 항목 등록:
권한 설정 문제 :
a) DEPLOY_KEY: 개인 키 (~/.ssh/github-deploy-key 내용) b) HOST: sample.com c) USER: nextjs d) TARGET_DIR: /home/nextjs/app e) SERVICE_NAME: nestjs.service f) PORT: 1234 g) ENV : .env 전체 내용을 그대로 붙여넣기
디렉토리 생성 : 샘플
mkdir -p /home/nextjs/app
.env 샘플
#dev = 개발 환경 #prod = 배포 환경 NESTJS_ENV="prod" TARGET_DIR="/home/nextjs/app" #DB DB_TYPE = "postgres" DB_HOST="192.168.0.19" DB_PORT="5432" DB_USERNAME="sample" DB_PASSWORD="sample" DB_DATABASE="sample" # Hash HASH_ROUNDS=10 # JWT $ openssl rand -base64 64 ACCESS_TOKEN_SECRET="sample" REFRESH_TOKEN_SECRET="sample" JWT_SECRET="sample"
3) ssh-deploy.yml
GitHub에 등록한 self-hosted runner가 있다면, runs-on:에는 해당 runner를 구분할 수 있는 라벨(label) 을 지정해야 합니다
runs-on: [self-hosted, nextjs-host]
라벨 지정 방법
1)
2)
서버 실행시 확인
3) sudo systemctl status github-runner
1. runs-on은 runner의 라벨 이름 GitHub Actions에서 runs-on:은 "어떤 runner에서 이 job을 실행할지"를 지정합니다. GitHub에서 직접 제공하는 runner는 예: ubuntu-latest, windows-latest, macos-latest Self-hosted runner는 등록할 때 **라벨(label)**을 하나 이상 설정하게 되어 있음 예시:
만약에 등록한 값이 없다면, 깃허브에서 기본적으로 제공하는 ubuntu-latest 를 적어 준다.
ssh-deploy.yml
name: CI/CD for NestJS on: push: branches: [main] jobs: deploy: runs-on: [self-hosted, nextjs-host] steps: - name: ✅ Checkout repository uses: actions/checkout@v3 - name: ✅ Set up Node.js 18 uses: actions/setup-node@v3 with: node-version: '18' - name: ✅ List directory contents run: | pwd ls -alR - name: ✅ Create .env file from secrets run: | mkdir -p backend echo "${{ secrets.ENV }}" > backend/.env echo ".env created in backend/" - name: ✅ Install backend dependencies working-directory: ./backend run: npm install - name: ✅ Build backend project working-directory: ./backend run: npm run build - name: ✅ Setup SSH private key and known_hosts run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -p ${{ secrets.PORT }} -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts - name: ✅ Compress and transfer build to remote server run: | tar -czf backend.tar.gz backend scp -F /dev/null -P ${{ secrets.PORT }} backend.tar.gz ${{ secrets.USER }}@${{ secrets.HOST }}:${{ secrets.TARGET_DIR }}/backend-latest.tar.gz - name: ✅ Restart service on remote server env: TARGET_DIR: ${{ secrets.TARGET_DIR }} HOST: ${{ secrets.HOST }} USER: ${{ secrets.USER }} PORT: ${{ secrets.PORT }} run: | ssh -F /dev/null -p "$PORT" "$USER@$HOST" << 'EOF' set -e echo "✅ Connected to remote server" echo "✅ Extracting build..." cd "$TARGET_DIR" rm -rf backend_new mkdir backend_new tar -xzf backend-latest.tar.gz -C backend_new --strip-components=1 echo "✅ Fixing permissions (if needed)..." chown -R nextjs:nextjs "$TARGET_DIR/backend_new" echo "✅ Updating symlink..." ln -sfn "$TARGET_DIR/backend_new" "$TARGET_DIR/current" cd "$TARGET_DIR/current" echo "✅ Installing production dependencies..." npm install --omit=dev echo "✅ Restarting PM2 service..." pm2 restart app || pm2 start dist/main.js --name app echo "✅ 현재 실행 중인 모든 애플리케이션의 상태를 저장" pm2 save echo "✅ Deployment complete." EOF
※전송 시 scp로 권한 유지
cat ~/.ssh/github-deploy-key.pub >> ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
authorized_keys 를 루트에서 설정했기 때문에 루트 권한을 가집니다.
따라서 scp 자체는 파일을 원격에서 어떤 유저로 실행하는지가 중요합니다.
만약 scp 전송 명령이 root 권한으로 실행되고 있다면, GitHub Action의 self-hosted runner 권한을 nextjs로 제한하거나,
파일 전송 후 아래처럼 권한을 반드시 조정하세요:
chown nextjs:nextjs backend-latest.tar.gz
authorized_keys 를 루트에서 설정 했기 때문에 github action 에 등록 한 USER 가 일반 접속 유저일경우
항상
일반 유저 파일 권한 변경 추가 다음처럼 권한 변경을 추가해야 합니다.
# inside your SSH script sudo chown -R nextjs:nextjs /home/nextjs/app/backend-latest.tar.gz
4) 서버에 PM2 설치
서버에 SSH 접속해서 아래 명령어 실행:
# Node.js가 설치되어 있어야 합니다 npm install -g pm2
설치 확인:
pm2 -v
Restart service via SSH 설정 설명
- name: Restart service via SSH run: | ssh -p ${{ secrets.PORT }} ${{ secrets.USER }}@${{ secrets.HOST }} << 'EOF' set -e cd ${TARGET_DIR} tar -xzf backend-latest.tar.gz ln -sf ${TARGET_DIR}/backend ${TARGET_DIR}/current cd ${TARGET_DIR}/current npm install --omit=dev npm run build pm2 restart nestjs-app || pm2 start dist/main.js --name nestjs-app --env production EOF
위 명령은:
앱이 이미 실행 중이면 restart
앱이 없으면 새로 start
GitHub Actions에서 서버에 코드 배포
이미 설정하신 이 부분은 그대로 유지하시면 됩니다:
backend를 tar로 압축해서 서버로 전송
서버에서 압축 해제
ln -sf로 /home/ubuntu/app/current 심볼릭 링크 생성
즉, current 디렉토리에 항상 최신 NestJS 앱이 배포되게 설정돼 있어야 합니다.
PM2 프로세스 등록 (처음 1번만)
서버에서 다음 명령어로 PM2에 NestJS 앱을 등록합니다
cd /home/ubuntu/app/current # .env가 있는 상태여야 함 npm install --omit=dev npm run build # PM2에 등록 (앱 이름은 nestjs-app으로 설정) pm2 start dist/main.js --name nestjs-app --env production
.env는 GitHub Actions에서 이미 자동 생성되어 있으니 괜찮습니다.
5). PM2 로그 보기 (서버에서)
pm2 logs nestjs-app
✅ GitHub Actions에서 self-hosted 러너를 설정
기본 개념
GitHub Actions는 GitHub에서 제공하는 CI/CD(지속적 통합/배포) 도구입니다.
기본적으로 GitHub에서는 무료로 제공되는 호스팅 러너(ubuntu-latest 등)를 사용하지만,
필요에 따라 자체 서버(local, VM, 클라우드 등)를 러너로 등록하여 사용할 수 있습니다. 이것이 self-hosted runner입니다.
Self-Hosted 러너를 사용하는 이유
1)성능 제어 : CPU , 메모리, 스토리지 등 자원을 내가 원하는 수준으로 구성 가능
2) 커스텀 환경 : 특정 라이브러리, 툴체인, DB 등을 미리 설치해 둘 수 있음
3) 보안 : 외부에 노출되지 않은 사내 네트워크나 내부 시스템 접근 가능
4)비용 절감 : Actions 사용량이 많을 경우 GitHub의 무료 러너 한도를 초과함 → 자체 서버 사용 시 비용 절감
5) Windows/macOS 필요 시 : GitHub이 기본적으로 Linux 러너만 제공하므로 Windows/macOS 환경이 필요할 경우 직접 구성해야 함
✅ 설정 요약
OS 선택 (Linux, Windows, macOS)
스크립트 복사
자신의 서버에서 실행 (ex. WSL2에서 실행 가능)
깃허브 액션 러너 설정
관리 권한으로 하면 안된다. 또한 접속자 권한으로 실행시에도 다음과 같은 오류가 난다.
actions-runner 디렉터리 내 파일의 소유권을 현재 사용자로 변경하세요
sudo chown -R $USER:$USER /actions-runner
다시 실행 전에 권한을 안전하게 설정해줍니다:
chmod -R u+rwX /actions-runner
위 권한 수정이 완료되면 다시 설정 명령어를 실행하세요:
※ 작업은 sudo 없이 현재 사용자 계정으로 실행되어야 합니다. ./run.sh 역시 마찬가지입니다:
./run.sh
WSL(Windows Subsystem for Linux)로 리눅스 사용하기
✅ WSL이란?
Windows에서 리눅스 커널을 직접 실행할 수 있게 해주는 기능입니다. 일반적으로 Ubuntu 배포판을 가장 많이 사용합니다.
✅ 설치 순서 (Windows 10/11 기준)
PowerShell(관리자) 열기
wsl --install
자동으로 Ubuntu 설치됨 (Windows 11에서는 단순 명령어 하나로 끝)
설치 후 초기 설정
사용자 이름 및 비밀번호 설정
WSL2 설정 확인
wsl --set-default-version 2
실행
wsl
또는 시작 메뉴 → Ubuntu 검색
✅ GitHub Actions Self-Hosted Runner + WSL 사용 가능?
WSL2 환경에서도 self-hosted 러너를 등록할 수 있습니다.
# WSL2에서 러너 등록 mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.316.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.316.0/actions-runner-linux-x64-2.316.0.tar.gz tar xzf actions-runner-linux-x64-2.316.0.tar.gz # GitHub에서 발급받은 설정 명령어 붙여넣기 (token 포함) ./config.sh --url https://github.com/your-org/your-repo --token XXXXXX # 실행 ./run.sh
✅Ubuntu 시스템에서 부팅 시 /actions-runner/run.sh 자동 실행을 설정하는 방법
1. run.sh 권한 확인
실행 가능 여부를 먼저 확인합니다:
chmod +x /actions-runner/run.sh
2. systemd 서비스 파일 생성
sudo nano /etc/systemd/system/github-runner.service
[Unit] Description=GitHub Actions Runner After=network.target [Service] ExecStart=/actions-runner/run.sh WorkingDirectory=/actions-runner User=사용자유저 Restart=always [Install] WantedBy=multi-user.target
3. systemd 서비스 등록 및 활성화
# 권한 다시 로딩 sudo systemctl daemon-reexec sudo systemctl daemon-reload # 서비스 등록 sudo systemctl enable github-runner.service # 즉시 실행 sudo systemctl start github-runner.service # 상태 확인 sudo systemctl status github-runner.service
확인
시스템 재부팅 후에도 run.sh가 자동으로 실행됩니다.
journalctl -u github-runner.service 로 로그 확인 가능
✅ 부팅 시 정상 동작 확인 팁
시스템 재부팅
sudo reboot
2.재부팅 후 로그인 없이도 GitHub Runner가 자동 실행되는지 확인:
sudo systemctl status github-runner
Self-hosted Runner를 위한 네트워크 및 보안 체크리스트
✅ 1. GitHub 서버와의 통신 허용
GitHub에서 runner와 통신하기 위해 아웃바운드 포트가 열려 있어야 합니다.
기본적으로 허용해야 할 포트:
HTTPS (443) → GitHub와의 통신 (최우선)
HTTP (80) → 초기 리디렉션에 사용되기도 함
필수 도메인:
github.com, api.github.com, actions.githubusercontent.com 등
대부분의 경우 아웃바운드만 허용되면 충분합니다. 방화벽이 너무 제한적인 환경이라면 도메인 화이트리스트 등록도 필요할 수 있습니다.
✅ 2. 방화벽 설정
Runner는 GitHub 서버에서 직접 접속하지 않습니다. 즉, GitHub → runner로 인바운드 요청이 발생하지 않음.
따라서 인바운드 포트를 열 필요는 없음, 단:
CI 환경 내에서 테스트 대상 애플리케이션이 서버처럼 동작하고 내부적으로 포트를 사용하는 경우, 예: Django 서버 실행 (python manage.py runserver), 해당 포트를 허용해야 함.
# 로컬에서 테스트 시 8000번 포트를 사용할 수 있게 허용 sudo ufw allow 8000
✅ 3. Runner 보안
self-hosted runner는 외부 요청을 받아 실행하므로 다음을 고려해야 합니다:
별도의 사용자 계정으로 실행 (루트 권한 X)
실행 환경을 주기적으로 초기화하거나 컨테이너화 (예: Docker)하여 안전하게 유지
필요시 CI 서버를 내부망에서만 접근 가능하게 구성
예: Ubuntu + UFW 환경에서 최소 설정
# 아웃바운드 허용 (기본적으로 허용되어 있음) sudo ufw default allow outgoing # 인바운드는 필요할 때만 허용 (예: Django 서버 테스트용 8000) sudo ufw allow 8000 # UFW 상태 확인 sudo ufw status
댓글 ( 0)
댓글 남기기