Post

Gitea와 Rootless Docker Runner를 활용한 안전한 자체 호스팅 CI/CD 환경 구축

GitHub Actions와 호환되는 경량 Git 플랫폼 Gitea를 배포하고, Authelia OIDC 단일 로그인 강제 및 루트리스(Rootless) Docker-in-Docker 러너를 결합하여 호스트 탈출 위협을 차단한 안전한 프라이빗 CI/CD 파이프라인을 구축합니다.

Gitea와 Rootless Docker Runner를 활용한 안전한 자체 호스팅 CI/CD 환경 구축

GitLab의 무거운 리소스 부담을 덜어내고 100MB 내외의 초경량으로 구동되는 Gitea를 배포하며, 자체 패스워드 로그인을 전면 차단하고 Rootless Docker-in-Docker 러너를 통해 호스트 권한 탈취 위협 없는 안전한 프라이빗 CI/CD 파이프라인을 완성합니다.


1. 홈서버에 자체 Git & CI/CD 인프라가 필요한 이유

개인 프로젝트나 홈랩 인프라 코드를 관리할 때 GitHub Public/Private 저장소를 주로 활용하지만, 다음과 같은 시나리오에서는 자체 호스팅 Git 플랫폼이 필수적입니다:

  • 홈랩 내부 접속용 시크릿 키나 사설 설정 파일의 안전한 로컬 보관
  • 기가비트 사설망 기반의 초고속 Git Push/Pull 및 대용량 패키지 레지스트리 활용
  • GitHub Actions의 분 단위 무료 사용량 제한 없는 무제한 자체 CI/CD 빌드

GitLab은 기능이 막강하지만 최소 4~8GB 이상의 메모리를 상시 점유하여 홈랩에 큰 부담이 됩니다. 반면 Gitea는 Go 언어로 작성되어 100MB 미만의 메모리로 동작하며, 최신 버전에서는 GitHub Actions 워크플로우 문법(.gitea/workflows/)을 그대로 재사용할 수 있는 Gitea Actions를 완벽하게 지원합니다.


2. 보안 아키텍처 및 Rootless 러너 격리 설계

일반적으로 CI/CD 러너에서 도커 빌드를 수행할 때 호스트의 도커 소켓(/var/run/docker.sock)을 마운트하거나 표준 docker:dind를 Privileged 모드로 띄우곤 합니다. 하지만 이는 워크플로우 스크립트에서 악의적인 명령어나 탈출 공격이 발생했을 때 호스트 머신의 root 권한이 완전히 탈취되는 치명적인 위험을 내포합니다.

홈랩 v2에서는 이 문제를 해결하기 위해 루트리스(Rootless) DinD 러너를 채택했습니다.

flowchart TD
    subgraph Ingress["웹 및 SSH 접속"]
        Dev["개발자"] -->|"HTTPS (git.namju.kim)"| NPM["Nginx Proxy Manager"]
        Dev -->|"SSH (Port 2222)"| Gitea
    end

    subgraph Development["Development 스택 (Docker Compose)"]
        NPM --> Gitea["Gitea Server<br/>(포트 3000 / DB: gitea)"]

        subgraph RunnerSandbox["격리된 CI/CD 러너: gitea-runner"]
            Runner["Gitea Act Runner"]
            RootlessDocker["Rootless dockerd<br/>(UID 1000 / slirp4netns)"]
            Runner --- RootlessDocker
        end
    end

    subgraph AuthTier["인증 및 영속성 계층"]
        Gitea -->|"OIDC 토큰 검증"| Auth["Authelia (SSO)"]
        Gitea -->|"데이터 저장"| PG["PostgreSQL"]
    end

    Gitea -->|"Job 큐 폴링 (gRPC)"| Runner
  • OIDC 로그인 강제: Gitea 자체 로컬 회원가입 및 패스워드 로그인 폼을 완전히 비활성화(ENABLE_PASSWORD_SIGNIN_FORM: false)하여, 오직 Authelia의 Single Sign-On을 통해서만 접근하도록 강제합니다.
  • Rootless DinD Sandbox: 컨테이너 내부의 dockerd가 비특권 사용자(UID 1000) 계정 네임스페이스에서 동작하므로, 워크플로우 내부에서 임의의 컨테이너를 생성하더라도 호스트 시스템 파일에는 접근할 수 없습니다.

3. Gitea & Runner Docker Compose 구성

development/docker-compose.yaml 전체 명세입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# development/docker-compose.yaml
name: development

services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    environment:
      TZ: Asia/Seoul

      # PostgreSQL 데이터베이스 연동
      GITEA__database__DB_TYPE: postgres
      GITEA__database__HOST: postgres:5432
      GITEA__database__NAME: gitea
      GITEA__database__USER: gitea_user
      GITEA__database__PASSWD: ${GITEA_DB_PASSWORD:?required}
      GITEA__database__SSL_MODE: disable

      # 서버 및 URL 기본 설정
      GITEA__server__DOMAIN: git.namju.kim
      GITEA__server__ROOT_URL: https://git.namju.kim/
      GITEA__server__PROTOCOL: http
      GITEA__server__HTTP_PORT: 3000

      # Git SSH 포트 (호스트 충돌 방지를 위해 2222 사용)
      GITEA__server__SSH_DOMAIN: git.namju.kim
      GITEA__server__SSH_PORT: 2222
      GITEA__server__SSH_LISTEN_PORT: 2222

      # 사용자 등록 및 로그인 보안 통제
      GITEA__service__DISABLE_REGISTRATION: "false"
      GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true"
      GITEA__service__SHOW_REGISTRATION_BUTTON: "false"
      GITEA__service__ENABLE_PASSWORD_SIGNIN_FORM: "false"
      GITEA__service__ENABLE_PASSKEY_AUTHENTICATION: "false"
      GITEA__service__REQUIRE_SIGNIN_VIEW: "true"

      # OIDC 자동 가입 매핑
      GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
      GITEA__oauth2_client__USERNAME: preferred_username

      # 패키지 레지스트리 및 Actions 활성화
      GITEA__packages__ENABLED: "true"
      GITEA__actions__ENABLED: "true"

    ports:
      - "${SERVER_IP:?required}:2222:2222"

    volumes:
      - gitea_data:/var/lib/gitea
      - gitea_config:/etc/gitea

    networks:
      - proxy_network
      - database_network
      - monitoring_network

  runner:
    image: gitea/runner:5-dind-rootless
    container_name: gitea-runner
    restart: unless-stopped
    privileged: true
    environment:
      TZ: Asia/Seoul
      GITEA_INSTANCE_URL: https://git.namju.kim
      GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_TOKEN:?required}
      GITEA_RUNNER_NAME: homelab-runner
      GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:22-bookworm,docker:docker://docker:dind"
      CONFIG_FILE: /config.yaml
      DOCKERD_ROOTLESS_ROOTLESSKIT_NET: slirp4netns
      DOCKERD_ROOTLESS_ROOTLESSKIT_MTU: "65520"
    volumes:
      - runner_data:/data
      - runner_docker:/home/rootless/.local/share/docker
      - ./gitea/config.yaml:/config.yaml:ro
    networks:
      - proxy_network
      - monitoring_network

volumes:
  gitea_data:
  gitea_config:
  runner_data:
  runner_docker:

networks:
  proxy_network:
    external: true
  database_network:
    external: true
  monitoring_network:
    external: true

4. Authelia OIDC 인증 연동 및 가입 설정

Gitea 관리자 콘솔(https://git.namju.kim/admin/auths)에서 신규 OAuth2 인증 소스를 등록합니다.

  • 인증 이름: Authelia
  • 클라이언트 ID: gitea
  • 클라이언트 시크릿: Authelia 설정 시 생성한 비밀키
  • OpenID Connect Discovery URL: https://auth.namju.kim/.well-known/openid-configuration

등록 후 사용자가 https://git.namju.kim에 접속하면 로컬 로그인 창 대신 “Authelia로 로그인” 버튼 하나만 노출되며, 클릭 시 Authelia SSO 인증을 거쳐 Gitea 계정이 즉시 자동 생성되고 로그인됩니다.


5. Actions Runner 등록 및 CI 파이프라인 검증

  1. Gitea 관리자 페이지(Site Administration -> Actions -> Runners)에서 Registration Token을 복사합니다.
  2. development/.env에 GITEA_RUNNER_TOKEN=<토큰>을 기입합니다.
  3. 러너 컨테이너를 기동합니다:
    1
    
    docker compose up -d runner
    

샘플 워크플로우 테스트 (.gitea/workflows/build-test.yaml)

저장소 루트에 워크플로우를 생성하여 Rootless 환경에서의 컨테이너 빌드를 검증합니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: Rootless Docker Build Test

on: [push]

jobs:
  test-build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Check User and Environment
        run: |
          whoami
          docker version
          id

Gitea Actions 탭에서 빌드가 성공적으로 실행되고, 호스트 시스템을 건드리지 않고도 온전한 컨테이너 기반 CI 파이프라인이 정상 완주되는 것을 확인할 수 있습니다.

This post is licensed under CC BY 4.0 by the author.