GitHub Actions와 도커를 활용한 CI/CD 가장 빠르게 구축하기

@ahnsanghyeon· 10 min read

개발 외의 오버헤드를 줄이기

많은 해커톤과, 팀 프로젝트와, 때로는 외주를 거치며... 개발자 자신의 삶의 질을 위해서라도 반복되는 수동 작업을 최소화 해야함을 체감해왔다. 운영체제의 표현을 빌리자면, 개발의 오버헤드를 줄여야 한다!

앞으로 서버 구축, 백엔드 최적화, 운영 자동화 등의 개발 과정에서 마주한 문제들과 이를 기술적으로 해결한 과정들을 하나씩 기록해 나갈 계획이다. 누군가 한 명이라도 글을 보고 쉽게 따라할 수 있다면 둘도 없이 좋고, 나 또한 오랜만에 새로운 프로젝트를 열 때마다 버벅이는 시간을 줄이고 싶은 마음도 있다.

이번 글에서는 그 중 배포 과정의 비효율을 제거하기 위해 GitHub Actions와 Docker Compose를 활용하여 구축한 CI/CD(지속적 통합/배포) 파이프라인에 대해 다룬다. 특히, 코드를 업데이트할 때마다 서버에 SSH로 접속하여 원격에서 pull하고, 도커를 재시작하는 과정을 반복했다면 이 글이 많은 도움이 될 것이다.

본 글에서는 GitHub Actions를 활용하여 메인 브랜치에 코드가 푸시되는 즉시 클라우드 서버(NCP, AWS 등)에 자동으로 배포되는 파이프라인을 구축하는 과정을 기술한다. SSH 접속을 통해 명령어를 실행하는 방식(SSH Action)을 채택하여 구성과 과정을 최대한 컴팩트하게 단순화했다.

(단순 배포 뿐 아니라 자동화 테스팅이나 보안 등.. Github Actions는 많은 것을 가능하게 해주니 개발자가 다룰 줄 알면 너무나도 좋다. Jenkins에 비하면 적용 과정도 매우 쉽다!)

앞서 확인하기

이 가이드는 다음 환경을 전제로 한다.

  • 서버 환경: Ubuntu 기반의 리눅스 서버 (AWS, NCP 등)
  • 필수 도구: docker로 구동되며 docker-compose가 설치되어 있어야 한다.

서버 접속용 키 생성하기

1️⃣ 배포 전용 SSH 키 생성 및 설정

CI/CD 자동화를 위해 가장 먼저 선행되어야 할 작업은 GitHub Actions가 내 서버에 접근할 수 있도록 권한을 부여하는 것이다. 보안과 자동화의 편의성을 위해 기존 접속용 키를 사용하지 않고, 비밀번호(패스프레이즈)가 없는 키 쌍을 새로 생성하여 사용했다. (Passphrase가 설정된 키를 사용할 경우 SSH 자동화 스크립트 실행 시 비밀번호 입력 단계에서 막혀 배포에 실패하기 때문)

서버 터미널에 접속하여 다음 명령어를 실행한다. github_action_key는 원하는 파일명으로 변경해도 무방하다.

# -t 암호화 방식
# -b 키 길이
# -C 주석 (사용용도 표기용)
# -f 파일 저장 경로
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy" -f ~/.ssh/github_action_key

명령어 실행 중 Passphrase를 묻는 프롬프트가 나오면 입력하지 않고 Enter를 두 번 눌러 건너뛴다.

2️⃣ 공개키 등록 및 권한 설정

생성된 키 쌍 중 공개키(.pub)의 내용을 서버의 인증된 키 목록(authorized_keys)에 추가해야 한다. 이를 통해 해당 키 쌍을 가진 사용자의 접속을 허용하게 된다. (무작위로 생성한 n자리 난수를 도어락의 비밀번호로 설정하는 과정이라고 비유해볼 수 있겠다.)

# .pub 내용을 authorized_keys 파일 뒤에 덧붙임
cat ~/.ssh/github_action_key.pub >> ~/.ssh/authorized_keys

또한 SSH 접속은 권한 설정에 민감하다. authorized_keys 파일이 다른 사용자에게 노출되거나 수정 권한이 있으면 접속이 거부될 수 있기 때문에 리눅스 명령어로 아래와 같이 권한을 변경한다.

# .ssh 디렉토리 권한 = 사용자 본인만 읽기/쓰기/실행 가능
chmod 700 ~/.ssh

# authorized_keys 파일 권한 = 사용자 본인만 읽기/쓰기 가능
chmod 600 ~/.ssh/authorized_keys

3️⃣ Github에 개인키 등록

GitHub에 등록할 개인키의 내용을 출력하여 복사해 둔다.

cat ~/.ssh/github_action_key

-----BEGIN OPENSSH PRIVATE KEY----- 부터 -----END OPENSSH PRIVATE KEY----- 까지의 전체 내용을 복사한다.

다음으로, GitHub 저장소의 Settings > Secrets and variables > Actions 메뉴에 등록하여 환경변수처럼 사용 등록해야 한다. 입력 후 수정은 가능하나 조회는 불가능하므로 주의! (그렇기에 혹시 모르니 내가 접속하는 ssh pemkey와 CI/CD용 key를 분리하여 사용해야 한다)

image 1

다음 세 가지 항목을 Repository secrets로 등록한다.

  1. SERVER_HOST: 서버의 공인 IP 주소 (예: 211.xxx.xxx.xxx)

    • AWS나 NCP, GCP 등 클라우드 콘솔에서 찾을 수 있다
  2. SERVER_USER: 접속할 리눅스 계정명 (예: root, ubuntu, ec2-user)

    • 권한별로 사용자를 꼼꼼히 분리하고 있다면 해당 사용자명으로, 아니면 내가 쓰는 사용자명으로..
  3. SERVERSSHKEY: 앞서 복사한 개인키 전체 내용 붙여넣기

    • -----BEGIN OPENSSH PRIVATE KEY----------END OPENSSH PRIVATE KEY----- 를 포함한 개인키 전체

GitHub Actions 워크플로우 작성

이제 실제로 배포를 수행할 스크립트(yml)를 작성한다. 프로젝트 루트에서 .github/workflows/deploy.yml 파일을 생성하고 아래 내용을 작성한다.

deploy.yml 작성하기

name: Deploy to Server

# 트리거 조건
on:
  push:
    branches: [ "main" ]  # main 브랜치에 푸시가 발생하면

# 실행할 작업
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3  # appleboy/ssh-action 라이브러리 사용
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: 22
          # 접속 성공 시 실행할 쉘 스크립트 작성
          script: |
            # 프로젝트 디렉터리로 이동
            # !! 실제 프로젝트와 일치하는 경로로 변경해야 합니다 !!
            cd /root/my-project

            # 최신 커밋 가져옴
            git pull origin main

            # 도커 종료 후 다시 빌드
            docker-compose down
            docker-compose up -d --build

            # 사용하지 않는 이미지 정리
            docker image prune -f

주요 옵션 설명

  • uses: appleboy/ssh-action@v1.0.3: SSH 접속을 쉽게 처리해 주는 서드파티 액션이다. 직접 ssh 명령어를 쓰는 것보다 키 관리나 포트 설정이 간편하여 널리 사용된다.
  • script: 서버에 접속한 후 순차적으로 실행될 쉘 명령어들이다. | (파이프) 기호를 사용하여 여러 줄의 명령어를 작성할 수 있다.
  • docker-compose up -d --build: --build 옵션으로 코드가 변경되었을 때 기존 이미지를 그대로 쓰지 않고, 새로 빌드하여 컨테이너를 띄우도록 한다.

마치며

위 설정을 완료한 후 코드를 main 브랜치에 푸시하면, GitHub Actions 탭에서 워크플로우가 실행되는 것을 확인할 수 있다. 정상적으로 완료되면, 서버에 접속하지 않아도 최신 코드가 반영된 서비스가 구동된다.

체크 표시 → 정상 완료!
체크 표시 → 정상 완료!

이 방식은 별도의 CI 서버를 구축하거나 복잡한 이미지 레지스트리 설정을 하지 않고도, git pull 기반의 가벼운 배포 파이프라인을 구축할 수 있다는 점에서 소규모 프로젝트나 해커톤에서 효율적인 선택지가 된다.

@ahnsanghyeon
상상을 현실로 👨‍💻