이전엔 내가 손으로 AWS에 코드를 배포했다. 하지만, 실제로 프로젝트를 진행하면 Infra 담당자가 전부 CI/CD 파이프라인을 구축해줬다. 내가 그 일을 언젠가 할 수도 있기에 코드를 `push`하면 자동으로 AWS에 반영되도록 CI/CD 파이프라인을 구축해보려고 한다.
1. 전체 아키텍처 및 데이터 흐름 (Architecture Overview)

1. git push
개발자가 로컬 컴퓨터(Local)에서 코드를 작성하고 Github 원격 저장소로 `git push`를 수행한다. 이 `git push`가 Github Actions의 Webhook을 통해 배포 파이프라인의 트리거 역할을 한다.
2. build docker image
Github Actions가 임시 가상 머신(Runner)을 할당받아 작업을 시작한다. Runner는 소스 코드를 다운로드하고, `Dockerfile`을 기반으로 애플리케이션을 실행 가능한 상태인 docker image로 빌드한다.
💡Runner란?
Github가 빌려주는 일회성 가상 컴퓨터이다.
우리가 `git push`를 하면 Github는 Microsoft Azure에서 우분투(Linux)가 깔린 컴퓨터 하나를 할당해준다. 이게 바로 Runner이다.
Runner는 2가지 특징을 가지고 있다.
1. 일회용
Runner는 `deploy.yml` 작업이 끝나면 즉시 삭제된다. 그래서 다음 배포 때는 또 다른 완전히 깨끗한 새 컴퓨터가 배정된다. 따라서, 항상 순수한 격리 환경에서 빌드가 수행됨을 보장한다 . (`deploy.yml`은 Runner가 실행해야할 명령어가 작성된 파일이다.)
2. 미리 세팅된 환경
이 컴퓨터에는 Docker, Java, Node.js, AWS CLI 같은 개발 필수 도구들이 미리 다 설치되어 있다. 그래서 우리가 따로 `install docker`를 하지 않아도 바로 빌드가 가능하다.
3. push image
2단계에서 빌드한 이미지를 Docker Hub로 전송한다. 이미지를 EC2로 바로 보내지 않고 Docker Hub를 거치는 이유는 버전 관리와 안정성 때문이다. 언제든 문제가 생기면 이전 버전의 이미지를 다시 가져올 수 있다.
4. SSH Connect & Command
Github Actions Runner가 `SSH Key(.pem)`를 사용하여 AWS EC2 인스턴스에 원격 접속한다. 직접 파일을 전송하는 것이 아니라, "명령어(Script)"만 전달한다.
5. pull latest image
명령을 받은 EC2는 Docker Hub에 접속하여 방금 업로드된 최신 버전(`latest`)의 이미지를 다운로드(Pull)한다. 필요한 레이어만 다운로드하므로 전송 속도가 빠르고 효율적이다.
6. stop old
기존에 포트를 점유하고 있던 구버전 컨테이너를 중지하고 삭제한다. (포트 충돌 방지)
7. run new container
다운로드한 새 이미지를 기반으로 컨테이너를 실행한다. 이때 `-p 80:80`옵션을 통해 외부 트래픽을 새 컨테이너로 연결한다.
2. Github Actions을 통한 CI/CD 파이프라인 만들기
과정은 크게 필수 파일 작성, 서버(EC2) 환경 설정, 보안 키 등록, 배포 순으로 진행된다.
1. 필수 파일 작성 (`Dockerfile` & `deploy.yml`)
Github Actions가 작동하려면 정해진 규칙에 따라 파일 위치와 내용을 작성해야 한다.

`.github/workflows` 경로는 Github이 정해놓은 규칙으로 Github Actions는 오직 이 폴더 안에 있는 파일만 읽는다. `deploy.yml`은 Runner에게 시킬 작업 내용을 적은 지시서이다. 크게 CI(빌드 및 푸시)와 CD(EC2 접속 및 배포) 두 단계로 나뉜다.
name: CI/CD Pipeline
on:
push:
branches: [ "main" ] # main 브랜치에 push가 발생하면 실행
jobs:
# [CI 단계] 이미지를 빌드하고 Docker Hub에 올리는 작업
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/platform-study:latest .
docker push ${{ secrets.DOCKER_USERNAME }}/platform-study:latest
# [CD 단계] AWS EC2에 접속해서 새 버전을 배포하는 작업
deploy:
needs: build-and-push # 위의 빌드 작업이 성공해야만 실행됨
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script: |
# 1. 도커 허브에서 최신 이미지 가져오기
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/platform-study:latest
# 2. 기존 컨테이너 중지 및 삭제 (에러 무시)
sudo docker stop my-server || true
sudo docker rm my-server || true
# 3. 새 컨테이너 실행 (80번 포트 연결)
sudo docker run -d --name my-server -p 80:80 ${{ secrets.DOCKER_USERNAME }}/platform-study:latest
# 4. 미사용 이미지 정리
sudo docker image prune -f
`Dockerfile`과 `index.html`은 https://ukj0ng.tistory.com/61에서 사용한 것을 그대로 사용하겠다.
[Infra] Docker란? (Docker에서 AWS EC2까지-1)
시작하기 전에그동안 프로젝트를 진행하며 docker-compose.yml은 AI를 사용해서 작성하거나 인프라 팀에게 받아쓰기만 했다. 인프라쪽을 잘 몰랐기 때문에, 팀을 만들 때도 이런 부분을 채워줄 수 있
ukj0ng.tistory.com
2. AWS EC2 서버 준비
서버 준비 역시 https://ukj0ng.tistory.com/62을 따라서 진행하면 된다.
[Infra] AWS 클라우드 배포 (Docker에서 AWS EC2까지-2)
이전 글에서 만든 `docker-compose.yml`을 AWS 클라우드 환경에서 배포를 진행해보려 한다. 1. EC2 인스턴스 생성EC2를 검색해서 인스턴스 시작 버튼을 눌러보자.이름은 원하는 걸 적고, OS 이미지는 우분
ukj0ng.tistory.com
3. Github Secrets 등록
`deploy.yml` 파일에 비밀번호나 비밀키를 그대로 적으면 해킹의 위험이 있다. 따라서 Github 저장소의 Secrets 기능을 이용해 변수로 처리한다.
해당 repository의 'Settings'에 들어간다.

왼쪽 메뉴에서 'Secrets and variable'의 'Actions'를 누르자.

변수를 등록하려면 'New repository secret'에서 등록해야 한다.


- `DOCKER_USERNAME`: 도커 허브 아이디
- `DOCKER_PASSWORD`: 도커 허브 비밀번호 (또는 토큰)
- `EC2_HOST`: EC2 인스턴스의 퍼블릭 IP 주소
- `EC2_USERNAME`: ubuntu
- `EC2_PRIVATE_KEY`: .pem 키 파일의 내용 전체 (`-----BEGIN...` 부터 `...END-----` 까지)
4. 최종 테스트


기존 html은 다음과 같다. html의 내용을 변경하고 `commit`, `push`를 진행하니

Actions에서 `build-and-push`와 `deploy`가 잘 된것을 확인할 수 있고, 실시간으로 변경된 것도 확인했다.


`build-and-push`의 과정

- Set up job: Runner가 준비됨
- Checkout code: 깃허브에서 코드를 다운로드함
- Login to Docker Hub: `Secrets`에 등록한 아이디/비밀번호로 로그인 성공
- Build and push: 도커 이미지를 만들고 Hub로 전송 완료
- Post Cleanup: 워크플로우 실행 중 사용했던 Docker Hub 로그인 정보나 Checkout 토큰 등 민감한 데이터를 러너에서 안전하게 삭제하고, 작업을 종료하는 뒷정리 단계
`deploy`의 과정

- Set up job: Runner가 준비됨
- Build appleboy/ssh-action@v1.0.0: EC2 접속에 필요한 외부 액션(Docker Container)을 다운로드하고 실행 준비를 마
- Deploy to EC2: Runner가 AWS EC2 서버에 SSH로 접속, 작성해둔 스크립트를 순서대로 실행
이로써, Github Actions을 통해 CI/CD 파이프라인을 구축해보았다. 매번 수동으로 하던 배포 과정을 자동화하면서, 코드가 서버에 반영되는 전체적인 데이터 흐름을 명확하게 이해할 수 있었다.
물론 아쉬운 점도 있다. 이번 실습에서는 편의상 latest 이미지만을 배포하도록 설정했지만, 실제 운영 환경이라면 특정 버전의 이미지를 배포하거나 문제 발생 시 이전 버전으로 롤백(Rollback)하는 전략이 반드시 추가되어야 할 것이다.
이제 배포의 자동화는 해결했다. 하지만 배포가 끝났다고 운영이 끝난 것은 아니다. 문득 '지금 내 서버가 건강한 상태인가?'라는 의문이 들었다. 현재로서는 서버가 느려지거나 죽어도 내가 직접 들어가 보지 않는 이상 알 방법이 없다. 따라서 다음 단계로는 서버의 리소스 상태를 실시간으로 모니터링하고, 임계치를 넘으면 알림을 보내주는 시스템을 구축해볼 예정이다. (근데 알림을 어디로 보내지?...)
'Infra' 카테고리의 다른 글
| [Infra] AWS 클라우드 배포 (Docker에서 AWS EC2까지-2) (0) | 2025.12.09 |
|---|---|
| [Infra] Docker란? (Docker에서 AWS EC2까지-1) (1) | 2025.12.05 |