왜 GitLab인가? (GitHub vs GitLab)

홈서버의 배포 자동화를 위해 가장 먼저 고민한 것은 “어떤 플랫폼을 쓸 것인가?“였습니다. 흔히 쓰이는 GitHub Actions 대신 GitLab CI/CD를 선택한 이유는 다음과 같습니다.

비교 항목 GitHub (Free) GitLab (Free) 비고
Registry 용량 500MB 5GB 홈서버 이미지 적재 시 유리
Self-hosted Runner 가능 매우 간편함 GitLab Runner의 아키텍처가 더 직관적
CI/CD 기능 유연함 (Actions) 강력한 통합 내장된 배포 관리 기능이 풍부함

특히 홈서버는 리소스가 한정적이기 때문에, Container Registry 용량이 넉넉하고 Runner 설치 및 관리가 압도적으로 편한 GitLab이 더 매력적인 선택지였습니다.


GitLab Shell Runner 등록하기

보통 CI/CD는 격리된 컨테이너 환경(Docker Runner)에서 수행하지만, 홈서버 배포에서는 Shell Runner를 선택했습니다.

왜 Shell Runner인가?

  1. 권한 관리: docker-compose 명령어를 통해 호스트의 컨테이너를 직접 제어하기 가장 쉬운 방식입니다.
  2. 퍼포먼스: Docker-in-Docker(DinD) 환경을 구성할 필요 없이 호스트의 도커 엔진을 즉시 사용하므로 빌드/배포 속도가 빠릅니다.
  3. 간편함: 호스트에 설치된 도구들(docker, git 등)을 환경 설정 없이 그대로 파이프라인에서 쓸 수 있습니다.

⚠️ 주의사항 및 단점

다만, Shell Runner는 다음과 같은 리스크가 있으므로 주의해서 사용해야 합니다.

  • 보안 위험: 파이프라인 스크립트가 호스트 머신의 권한으로 직접 실행되므로, 잘못된 스크립트가 시스템 전체를 망가뜨릴 수 있습니다. (신뢰할 수 있는 레포지토리에서만 사용 권장)
  • 환경 오염: 이전 빌드에서 생성된 파일이나 설정이 다음 빌드에 영향을 줄 수 있습니다. (GIT_CLEAN_FLAGS 등을 통한 관리가 필요합니다.)
  • 동시성 문제: 여러 빌드가 동시에 돌 때 호스트 리소스를 공유하므로 예측 불가능한 오류가 생길 수 있습니다.

그럼에도 Shell Runner인 이유

위에서 언급한 여러 리스크에도 불구하고 제가 Shell Runner를 선택한 이유는 명확합니다. 이 홈서버는 팀 단위의 상용 서비스가 아니라, 오직 저 혼자만의 편의를 위해 개발하고 배포하는 환경이기 때문입니다.

1인 운영 환경에서는 복잡한 격리나 엄격한 보안보다는, 직관적인 배포 프로세스신속한 배포에 우선순위를 두었고, 그런 관점에서 Shell Runner와 Docker Compose의 조합을 택했습니다.

설치 과정

GitLab Runner를 홈서버(Ubuntu)에 설치하는 과정입니다. 공식 리포지토리를 추가하고 패키지를 설치합니다.

# 1. 패키지 리포지토리 추가 스크립트 실행
curl -L "https://packages.gitlab.com/install/repositories/gitlab/gitlab-runner/script.deb.sh" | sudo bash

# 2. GitLab Runner 설치
sudo apt-get install gitlab-runner

등록 과정

설치가 완료되었다면, 이제 GitLab 서버와 홈서버를 연결하는 등록 과정을 진행합니다.

  1. 명령어 실행: sudo gitlab-runner register
  2. GitLab 프로젝트/그룹 설정의 CI/CD > Runners 페이지에 있는 URLRegistration Token 입력
  3. Executor 선택: shell
  4. Tag 입력: home (파이프라인 실행 시 특정 러너를 지정하기 위함)

파이프라인 설정 예시

실제 배포에 사용되는 .gitlab-ci.yml 파일입니다. main 브랜치에 푸시가 발생하면 홈서버의 러너가 이를 감지하여 배포를 수행합니다.

1. .gitlab-ci.yml

상세한 문법은 GitLab CI/CD YAML reference를 참고하세요.

stages:
  - deploy

deploy-to-home:
  stage: deploy
  tags:
    - home  # 등록한 홈서버 Runner의 태그
  script:
    # 기존 컨테이너 가동 중지 및 최신 이미지 빌드 후 실행
    - docker compose down || true
    - docker compose up -d --build
  only:
    # main 브랜치에 푸시가 발생하면 배포 실행
    - main

2. docker-compose.yml

상세한 옵션은 Docker Compose file reference에서 확인할 수 있습니다.

services:
  backend:
    build:
      context: ./backend
    container_name: app-backend
    restart: always
    environment:
      - PORT=8080
      - TZ=Asia/Seoul
      # 환경 변수는 GitLab CI/CD Variable로 관리 가능

  frontend:
    build:
      context: ./frontend
    container_name: app-frontend
    restart: always
    ports:
      # 로컬 호스트의 특정 포트에 매핑하여 Cloudflare Tunnel과 연결
      - "127.0.0.1:3000:80"
    environment:
      - TZ=Asia/Seoul
    depends_on:
      - backend

상세 동작 원리: Shell Runner의 내부 작동

커밋 후 배포가 시작되면 GitLab Shell Runner는 다음과 같은 순서로 호스트 머신(홈서버)에서 동작합니다.

  1. Work Directory 할당: 러너는 /home/gitlab-runner/builds 디렉토리 하위에 프로젝트별 고유 경로를 생성합니다. (예: /home/gitlab-runner/builds/ABCxyz/0/group/project)
  2. Repo 클론/Fetch: 해당 경로에 소스 코드를 최신화합니다.
  3. 스크립트 실행: 우리가 .gitlab-ci.yml에 정의한 docker-compose 명령어를 해당 경로에서 직접 실행합니다.

마치며: “너무 단순한 배포 방식 아닌가?”

쿠버네티스 홈 클러스터를 구축해서 대규모 파이프라인을 운영하는 분들에 비하면, 이 방식은 매우 단순해 보일 수 있습니다. 하지만 저는 홈서버의 본질에 집중하고자 했습니다.

제가 개발한 자동 매매 봇, 관심 있는 뉴스나 소재를 LLM으로 요약해 정해진 시간에 알림을 주는 뉴스 요약 봇, 그리고 기존 모바일 가계부 앱들의 사용성이 아쉬워 직접 개발한 PWA 가계부 같은 서비스들을 빠르게 배포하고 사용하는 것이 제 주된 목적 이었습니다.

특히 제 홈서버 사양이 Intel N100에 16GB 메모리이다 보니, kindk3s 같은 경량 쿠버네티스조차 클러스터를 유지하기 위한 리소스 소모가 아깝게 느껴졌습니다.

개인 프로젝트의 목적이 ‘운영 그 자체의 재미’라면 오버엔지니어링도 좋은 세팅이 될 수 있겠지만, 저처럼 ‘내가 만든 서비스를 직접 배포하고 사용하는 것’이 우선이라면 이 조합도 좋은 선택지라고 생각합니다.