홈서버에 CI/CD 구축하고 최적화하기

김유진·2026년 6월 8일

오늘 배운 거

목록 보기
3/3
post-thumbnail

개요

집에 안쓰는 구형 맥북이 있다.

MacBook Pro 2017
CPU : i5 어쩌구 인가? 기억 안남
RAM : 8GB
GPU : 모름 아무튼 내장 그래픽
SSD : 256GB 인가 아무튼 그쯤

이 정도 스펙인데 집에서 뒹굴거리길래 홈서버를 구축해서 요긴하게 써먹고 있다.

홈서버를 구축하면서

  • 집 공유기를 통해서 포트포워딩도 해보고
  • DDNS 설정도 해보고
  • 개인 프로젝트 배포도 해보고
  • CI/CD 구축도 해보고
    아무튼 잘 굴려먹고 있었다.

물론 CI/CD 구축할땐 그냥 AI로 짰다.
그냥 아하 이렇게 하는 거구나? 하는 느낌

그런데..

문제 발생

그런데 어느 순간부터 배포하려고 PR 날리면 Actions가 너무 오래 돌다가 timeout 나기 시작했다.

이것 저것 찔러보면서 해결하려고 애쓴 모습

AI로 코드를 짰으므로, 클로드 열심히 돌려가면서 원인을 찾으려고 애썼지만 문제는 지속됐다.

문제 현상

  1. Actions 실행 중 반응 없다가 time out
  2. 홈서버 접속해서 docker ps 로 상태를 확인하려고 하면 반응 없음
  3. 그때마다 colima 자체를 껐다가 다시 켜야하는 상황

그래서 찾은 문제의 원인은 바로 ...

💡 OOM Out Of Memory
메모리가 터진 것이였다.

8GB RAM 사용 현황:
  macOS:           ~3GB
  Colima VM:       ~1GB
  기존 컨테이너들:     ~1GB
  Maven 빌드:       ~2GB+
  ─────────────────────
  합계: 7GB+ → OOM → Colima VM 강제 종료 → docker ps 블로킹

내 맥북은 구형이기 때문에 Docker가 해당 os버전까지 지원해주지 않는다. (건방지게)
그래서 Colima라는 Linux 가상 머신을 띄워서 도커를 띄우고 있었다.
그게 리소스를 꽤 먹는데다가 컨테이너도 왕창 띄워놨었다.

그리고 결정적으로 빌드 자체를 홈서버에서 하고 있었다
그 커다란 maven 의존성 덩어리를 배포할때마다 빌드하고 있었으니 간당간당했던 내 맥북이 뻗어버린 것이다.

정리하자면

  • 구형 맥북에
  • Colima위에
  • 도커 띄우고
  • 컨테이너 왕창 띄우고
  • 게다가 빌드 자체를 홈서버에서 하고 있었는데다
  • 클로드 코드가 짜준 코드여서 근본적인 원인 탐색을 못하고 있었다.

그래서 이 참에 그냥 CI/CD 과정을 배워보기로 했다.

CI/CD란?

📝
CI (Continuous Integration) — 코드를 push할 때마다 자동으로 빌드·테스트
CD (Continuous Deployment) — CI 통과 후 자동으로 서버에 배포

git push 하나로 → 빌드 → 테스트 → 배포 전부 자동화

결국 푸시만 하면 알잘딱 빌드와 테스트, 배포까지 해주는 아주 똘똘한 방식이다.

지금까지의 방식

  1. Actions VM 실행
  2. github Repo에서 코드를 checkout 해옴
  3. VM 위에서 maven 빌드
  4. JAR 파일 생성
  5. SCP로 JAR 파일 홈서버로 전송 (JAR가 있어야 이미지를 만들 수 있기 때문)
  6. 홈서버에 SSH 접속
  7. git pull로 최신 코드 땡겨옴
  8. 그 상태에서 Docker 빌드 (이미지 생성) → 💥 주로 여기서 OOM 발생 💥
  9. 배포
steps:
  - uses: actions/checkout@v4
  - name: Set up JDK 21
    uses: actions/setup-java@v4
  - name: Build JAR
    run: mvn package -DskipTests -q && cp target/*.jar app.jar

  - name: SCP JAR to server          # JAR를 홈서버로 전송
    uses: appleboy/scp-action@v0.1.7
    with:
      source: "app.jar"
      target: ${{ secrets.DEPLOY_PATH }}

  - name: Deploy via SSH # ssh로 홈서버 접속
    uses: appleboy/ssh-action@v1.0.3
    with:
      script: |
        cd ${{ secrets.DEPLOY_PATH }}
        git pull origin main
        docker compose up -d --build   # 💥 홈서버에서 Docker 빌드 → OOM

이 방식에 문제점이 있다면
1. 서버에서 빌드하기 때문에 서버에 부하가 크다 (얼마나 부하가 큰지는 측정을 못해봤음 ㅠ)

어쨌든 PR 날릴 때마다 서버가 통으로 멈추는건 아주 크나큰 문제였다.

사실 최초의 코드는 더욱 심각했다.

name: Deploy to Home Server

  on:
    push:
      branches: [main]

  jobs:
    deploy:
      runs-on: ubuntu-latest
      steps:
        - name: Deploy via SSH
          uses: appleboy/ssh-action@v1.0.3
          with:
            host: ${{ secrets.SSH_HOST }}
            port: ${{ secrets.SSH_PORT }}
            username: ${{ secrets.SSH_USER }}
            key: ${{ secrets.SSH_PRIVATE_KEY }}
            script: |
              set -e
              cd ${{ secrets.DEPLOY_PATH }}
              git pull origin main
              ./mvnw package -DskipTests      # 홈서버에서 Maven 빌드
              docker compose up -d --build    # 홈서버에서 Docker 빌드
              echo "✅ 배포 완료"
  • CI 과정은 아예 생략,
  • 곧바로 ssh로 서버 접속해서 pull받고,
  • maven build, docker build를 둘 다 서버에서 했다.
  • 결국, 부하가 두배로 드는 최악의 코드였다.

해결책

Github Container Registry (ghcr.io)를 도입하기로 했다.

ghcr.io란? github에서 운영하는 Docker 이미지 저장소

Github Container Registry를 이용해서 도커 이미지를 pull받아 이미지만 실행시키는 방식을 사용하도록 했다.
1. Actions VM 실행!
2. github Repo에서 코드 checkout
3. maven 빌드
4. 생성된 jar를 토대로 docker 빌드
5. 생성된 이미지를 ghcr.io에 업로드
6. SSH로 홈서버 접속
7. git pull로 최신 코드(docker-compose.yml) 땡겨옴
8. docker compose pull로 이미지 pull 받아오기
9. 그 상태에서 Docker Run
10. 배포

steps:
  - uses: actions/checkout@v4
  - name: Set up JDK 21
    uses: actions/setup-java@v4
  - name: Build JAR # maven 빌드
    run: mvn package -DskipTests -q && cp target/*.jar app.jar
  - name: Set up Docker Buildx # docker build 도구
    uses: docker/setup-buildx-action@v3
  - name: Log in to ghcr.io # ghcr.io 로그인
    uses: docker/login-action@v3
  - name: Build and push Docker image   # VM에서 이미지 빌드 + ghcr.io push
    uses: docker/build-push-action@v5
    with:
      context: .
      push: true
      tags: ghcr.io/0xdf0101/mock_invest:latest

  - name: Deploy via SSH
    uses: appleboy/ssh-action@v1.0.3
    with:
      script: |
        git pull origin main
        docker compose pull             # ghcr.io에서 이미지 pull
        docker compose up -d            # 실행만 (빌드 없음) ✅

이렇게 했을 시 장점

  1. VM에서 maven과 image 빌드를 다 하고, 서버에서는 이미지 실행만 하기 때문에 서버의 부하가 줄어든다!
  2. ghcr.io에 push 했기 때문에 같은 이미지를 다른 환경에서도 그대로 실행 가능
  3. 이미지가 환경 자체를 포함하기 때문에 일관성 증가

결론


ghcr.io를 도입함으로써 서버의 부하를 줄이고, OOM을 예방할 수 있었다.
(두 방식에 대한 메모리 변화량도 기록하고 싶었으나 Grafana를 띄우니까 또 뻗었다.)

배운 점

  • '한정된 자원'이라는 것을 처음 경험해봤다. 지금까지는 제공되는 빵빵한 서버에 빵빵한 리소스 위에 배포를 했다면, 이제는 정말 '한정된 자원'에 최적화 하는 경험을 해본 것 같다.
  • 그저 '동작만 하는 코드'를 만들어내는게 중요한 것이 아니라 구조를 이해하고 설계하는 능력이 필요함을 느꼈다.
  • 사실 이제 막 배워가는 단계라 '최적화'인지는 잘 모르겠슈
profile
제가 공부한 내용을 적은 것이기 때문에 틀릴 수 있습니다.

0개의 댓글