개요
express.js로 토이 프로젝트를 진행하던 중 cicd를 구현하려는데 마땅한 글을 찾지 못해 직접 구현한 후 정리하게 되었다.
준비물
1. ssh 접속이 가능하고 docker, docker-compose가 정상 설치된 서버
2. docker hub 계정
3. 프로젝트 repository
Github Repository Secrets 등록
repository -> Settings -> Secrets and Variables -> Actions
1. SERVER_IP - 서버 ip
2. SERVER_USER - SSH 사용자명
3. SSH_PRIVATE_KEY - 서버 SSH 접속 시 사용하는 private key
4. DOCKER_USERNAME - docker hub 유저명
5. DOCKER_PASSWORD - docker hub 비밀번호
6. PROJECT_NAME - 프로젝트 명(소문자와 '-'으로만 구성 Ex. korea-quote)
프로젝트 디렉토리 구조
project
├── .github/workflows
├── docker-compose.yml
├── Dockerfile
├── package.json
└── server.js
서버 authorized_keys 설정
cd ~.ssh
vi authorized_keys ## 서버 SSH 접속 시 사용하는 public key 복붙 후 저장
chmod 600 authorized_keys
Github Action workflow 등록
name: CI/CD Pipeline
# 파이프라인이 실행될 트리거를 설정
on:
push:
branches:
- master # master 브랜치에 push 이벤트 발생 시 실행
pull_request:
branches:
- master # master 브랜치에 대한 pull request 이벤트 발생 시 실행
jobs:
build:
runs-on: ubuntu-latest # 빌드 작업을 실행할 환경 설정 (최신 우분투)
steps:
- name: Checkout repository # GitHub repository 체크아웃
uses: actions/checkout@v4
- name: Set up Docker Buildx # Docker Buildx 설정
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub # DockerHub 로그인
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }} # GitHub Secrets DockerHub 사용자명 사용
password: ${{ secrets.DOCKER_PASSWORD }} # GitHub Secrets DockerHub 비밀번호 사용
- name: Build Docker image # Docker 이미지 빌드
run: |
docker build . -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} # Docker 이미지 빌드 및 태그
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest # latest 태그 추가
- name: Push Docker image to Docker Hub # Docker hub에 이미지 push
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} # 빌드된 이미지 push
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest # latest 태그 이미지 push
deploy:
runs-on: ubuntu-latest # 배포 작업을 실행할 환경 설정 (최신 우분투)
needs: build # build 작업이 성공적으로 완료된 후에만 실행
steps:
- name: Checkout repository # GitHub repository 체크아웃
uses: actions/checkout@v4
- name: Deploy to server # 서버에 SSH로 접속하여 배포 작업 수행
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }} # GitHub Secrets 서버 IP 사용
username: ${{ secrets.SERVER_USER }} # GitHub Secrets 서버 사용자명 사용
key: ${{ secrets.SSH_PRIVATE_KEY }} # GitHub Secrets SSH 개인 키 사용
port: 22 # SSH 접속 포트 (기본: 22)
script: |
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} # 새로운 Docker 이미지 pull
sudo docker stop ${{ secrets.PROJECT_NAME }} || true # 기존 Docker 컨테이너 중지 (오류 무시)
sudo docker rm ${{ secrets.PROJECT_NAME }} || true # 기존 Docker 컨테이너 삭제 (오류 무시)
sudo docker run -d -p 3000:3000 --name ${{ secrets.PROJECT_NAME }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} # 새로운 Docker 컨테이너 실행
docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
environment:
- NODE_ENV=development
Dockerfile
# 베이스 이미지 설정
FROM node:20
# 작업 디렉토리 설정
WORKDIR /app
# 패키지 설치를 위해 package.json 및 package-lock.json 복사
COPY package*.json ./
# 패키지 설치
RUN npm install
# 어플리케이션 코드 복사
COPY . .
# 어플리케이션이 실행될 포트 설정
EXPOSE 3000
# 서버 실행
CMD ["node", "server.js"]
server.js (예시)
// server.js
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
마무리
정상적으로 진행 되었다면 http://ip:3000으로 접속 시 Hello World!라는 문구를 볼 수 있을 것입니다 :)