Skip to content

TIL -2025-06-02 [EC2 + Docker + GitHub Actions + GHCR + Slack Notification을 통한 CI/CD 구축] #79

Description

@soheeGit

📌 EC2 + Docker + GitHub Actions + GHCR + Slack Notification을 통한 CI/CD 구축

1. EC2 인스턴스 생성 및 환경 설정

  • OS: Ubuntu 22.04 LTS
    • 보안 그룹: 포트 22(SSH), 80(HTTP) 열기
Image Image Image Image
  • SSH 접속
chmod 400 "cicd.pem"
ssh -i "cicd.pem" ubuntu@ec2-52-78-25-156.ap-northeast-2.compute.amazonaws.com
  • Docker 설치
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  • 권한 및 테스트
docker --version
docker compose version
sudo docker run hello-world

sudo gpasswd -a $USER docker
sudo service docker restart
sudo su - 
su - ubuntu # 다시 일반 계정으로 돌아가기

2. GHCR 이미지 수동 실행 (테스트)

echo <GHCR_TOKEN> | docker login ghcr.io -u soheegit --password-stdin
docker run -d -p 80:8080 --name app ghcr.io/soheegit/boot-test-cicd:main

3. GitHub Actions Workflow (.github/workflows/docker-image-ci.yml)

  • 3-1. CI: 빌드 및 GHCR 이미지 푸시
name: docker-image-ci
on:
  push:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
          cache: gradle

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          cache-read-only: true

      - run: chmod +x ./gradlew
      - run: ./gradlew test --no-daemon

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker Metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and Push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Slack notify (CI)
        if: always()
        uses: act10ns/slack@v2.1.0
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          status: ${{ job.status }}
          message: |
            *CI Build* `${{ github.repository }}`
            • *Status:* `${{ job.status }}`
            • *Branch:* `${{ github.ref_name }}`
            • *Image:* `${{ steps.meta.outputs.tags }}`
            • <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Logs>
  • 3-2. CD: EC2로 SSH 접속 후 자동 배포
  deploy-prod:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Redeploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e
            LOWER_IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')
            IMAGE=${{ env.REGISTRY }}/$LOWER_IMAGE_NAME:main
            echo "${{ secrets.SOHEETOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
            docker pull $IMAGE
            docker rm -f app || true
            docker run -d --name app -p 80:8080 --restart unless-stopped $IMAGE

      - name: Slack notify (deploy)
        if: always()
        uses: act10ns/slack@v2.1.0
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          status: ${{ job.status }}
          message: |
            *Prod Deploy* `${{ github.repository }}`
            • *Status:* `${{ job.status }}`
            • *Server:* `${{ secrets.SSH_HOST }}`
            • *Image:* `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main`
            • <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Logs>

4. GitHub Secrets 설정

Image

5. 결과 확인

  • EC2 서버: 자동 pull → 기존 컨테이너 종료 → 새 이미지 run
  • Slack: CI/CD 성공 여부 및 로그 링크 자동 전송
  • GitHub Actions: main 브랜치 push 시 전체 CI/CD 실행
Image

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions