Haru Utils

GITHUB ACTIONS

GitHub Actions Self-hosted Runner 안전 구성

승인된 Linux runner를 전용 계정·runner group·명시적 label로 등록하고 서비스 운영, 업데이트, 제거와 무데이터 롤백까지 안전하게 구성합니다.
GitHub Actions runnerself-hosted runner 설치actions runner 등록runner serviceephemeral runner
지원 환경Ubuntu Server 24.04 LTS · systemd · amd64/arm64
예상 시간55
난이도중급
검토일2026-09-04

BEFORE YOU START

시작 전에 준비하세요

01

콘솔 복구와 sudo, 전용 <RUNNER_USER>·고유한 <RUNNER_NAME>

02

승인된 repository 또는 organization URL과 제한된 runner group

03

릴리스 자산의 정확한 version·architecture·공식 SHA-256

04

등록 직전에 발급하고 로그·history에 남기지 않을 1시간 유효 registration token

권장 대상 GitHub Actions 작업을 사설 인프라에서 실행하며 runner 격리·권한·수명주기를 책임지는 운영자

FOLLOW THE RECIPE

8단계 구성·점검 레시피

1
확인시스템을 변경하지 않는 점검 단계

범위·호스트·신뢰 경계 확인

runner가 접근할 repository와 secret, 네트워크, 기존 CI process를 읽기 전용으로 확인합니다.

OS·아키텍처
cat /etc/os-release
uname -m
계정·자원
getent passwd <RUNNER_USER>
id <RUNNER_USER>
free -h
df -hT / /srv
기존 runner
systemctl list-unit-files 'actions.runner.*' --no-pager
ps -eo pid,user,etime,comm,args | grep '[R]unner.Listener'
  • repository/organization 관리자 승인과 runner group 범위를 기록했습니다.
  • 비신뢰 fork PR은 이 지속형 runner에서 실행하지 않습니다.
  • host가 다른 업무·credential과 공유되지 않습니다.
결과 읽기

공유 host나 광범위한 secret이 보이면 전용 VM 또는 일회성 runner 설계가 먼저입니다.

다음 판단

공식 릴리스·네트워크·기존 등록 충돌을 확인합니다.

2
확인시스템을 변경하지 않는 점검 단계

릴리스·네트워크·등록 충돌 사전 점검

GitHub 설정 화면이 제시한 릴리스와 checksum을 변경 기록에 고정하고 outbound 연결과 기존 service를 확인합니다.

서비스 상태
systemctl status 'actions.runner.<SCOPE>.<RUNNER_NAME>.service' --no-pager -l || true
runner 경로
sudo test -d /srv/actions-runner && sudo find /srv/actions-runner -maxdepth 1 -type f -printf '%m %u:%g %f
' || true
기존 설치만 연결 진단
if sudo test -x /srv/actions-runner/config.sh; then sudo -u <RUNNER_USER> -H bash -lc 'cd /srv/actions-runner && read -rsp "Diagnostic token: " CHECK_PAT && printf "\n" && ./config.sh --check --url https://github.com/<OWNER>/<REPOSITORY> --pat "$CHECK_PAT" && unset CHECK_PAT'; fi
신규 설치는 package 설치 뒤 동일한 진단을 수행합니다. 권한을 최소화한 단기 자격증명을 history·출력에 남기지 않습니다.
  • GitHub 설정 화면의 권장 runner version과 progressive rollout 상태를 확인했습니다.
  • 동일 name·work directory·service 등록이 없습니다.
  • 필수 GitHub endpoint만 outbound allowlist에 포함됩니다.
결과 읽기

토큰을 명령행·환경 덤프로 노출했거나 기존 등록 소유자가 불명확하면 중단합니다.

다음 판단

0700 작업공간에서 검증한 package를 설치합니다.

3
변경패키지·설정·서비스 상태가 달라지는 단계

고정 runner package 검증·설치

공식 릴리스 자산을 임시 전용 경로에 내려받아 SHA-256을 대조한 뒤 runner 전용 계정 소유 경로에 풉니다.

변경 단계입니다. 대상 서버, 백업 파일, 서비스 중단 영향과 바로 이전 상태로 돌아가는 방법을 다시 확인하세요.
전용 nologin 계정·group
getent group <RUNNER_GROUP> >/dev/null || sudo groupadd --system <RUNNER_GROUP>
getent passwd <RUNNER_USER> >/dev/null || sudo useradd --system --create-home --home-dir /home/<RUNNER_USER> --shell /usr/sbin/nologin --gid <RUNNER_GROUP> <RUNNER_USER>
id <RUNNER_USER>
test "$(id -gn <RUNNER_USER>)" = '<RUNNER_GROUP>'
getent passwd <RUNNER_USER>
getent group <RUNNER_GROUP>
기존 계정도 primary group이 <RUNNER_GROUP>인지 fail-fast 검증합니다. nologin이므로 sudo -u <RUNNER_USER> -H bash -lc를 사용합니다.
0700 작업공간
umask 077
mktemp -d -p /var/tmp haru-actions-runner.XXXXXXXX
출력 경로를 <RUNNER_WORK_DIR>에 그대로 대입합니다.
download·checksum
stat -Lc '%a %U:%G %n' <RUNNER_WORK_DIR>
curl -fL -o <RUNNER_WORK_DIR>/runner.tar.gz https://github.com/actions/runner/releases/download/v<APPROVED_RUNNER_VERSION>/actions-runner-linux-<RUNNER_ARCH>-<APPROVED_RUNNER_VERSION>.tar.gz
printf '%s  %s
' '<APPROVED_RUNNER_SHA256>' '<RUNNER_WORK_DIR>/runner.tar.gz' | sha256sum --check --strict
설치
sudo install -d -o <RUNNER_USER> -g <RUNNER_GROUP> -m 0750 /srv/actions-runner
sudo tar --no-same-owner -xzf <RUNNER_WORK_DIR>/runner.tar.gz -C /srv/actions-runner
sudo chown -R <RUNNER_USER>:<RUNNER_GROUP> /srv/actions-runner
sudo -u <RUNNER_USER> /srv/actions-runner/bin/Runner.Listener --version
  • version·architecture·SHA-256이 승인값과 일치합니다.
  • runner는 root가 아닌 전용 계정 소유입니다.
  • 다운로드 URL은 actions/runner 공식 릴리스입니다.
결과 읽기

checksum이 다르면 압축을 풀지 않고 작업공간을 격리합니다.

다음 판단

단기 registration token으로 제한된 group·label에 등록합니다.

4
변경패키지·설정·서비스 상태가 달라지는 단계

등록·group·label·service 구성

등록 토큰은 발급 후 1시간 안에 한 번만 사용하고 값은 붙여넣기 입력합니다. 조직 runner는 승인된 runner group으로 제한합니다.

변경 단계입니다. 대상 서버, 백업 파일, 서비스 중단 영향과 바로 이전 상태로 돌아가는 방법을 다시 확인하세요.
기존 등록 증거 보존
sudo install -d -o root -g root -m 0700 <APPROVED_BACKUP_DIR>
if sudo test -f /srv/actions-runner/.runner; then sudo cp --archive --no-clobber /srv/actions-runner/.runner <APPROVED_BACKUP_DIR>/.runner.<BACKUP_SUFFIX>; sudo chmod 0600 <APPROVED_BACKUP_DIR>/.runner.<BACKUP_SUFFIX>; fi
if sudo test -f /srv/actions-runner/.service; then sudo cp --archive --no-clobber /srv/actions-runner/.service <APPROVED_BACKUP_DIR>/.service.<BACKUP_SUFFIX>; sudo chmod 0600 <APPROVED_BACKUP_DIR>/.service.<BACKUP_SUFFIX>; fi
if sudo test -f /srv/actions-runner/.credentials; then sudo stat -Lc '%a %U:%G %s %n' /srv/actions-runner/.credentials; sudo sha256sum /srv/actions-runner/.credentials; fi
.credentials는 복사하지 않고 root 화면에 metadata·checksum만 증거로 기록합니다. .runner·.service 사본은 운영 경로로 복원하지 않습니다.
설치 후 연결 진단
sudo -u <RUNNER_USER> -H bash -lc 'cd /srv/actions-runner && read -rsp "Diagnostic token: " CHECK_PAT && printf "\n" && ./config.sh --check --url <APPROVED_RUNNER_SCOPE_URL> --pat "$CHECK_PAT" && unset CHECK_PAT'
A. repository runner 등록
sudo -u <RUNNER_USER> -H bash -lc 'cd /srv/actions-runner && read -rsp "Registration token: " RUNNER_TOKEN && printf "\n" && ./config.sh --unattended --url https://github.com/<OWNER>/<REPOSITORY> --token "$RUNNER_TOKEN" --name <RUNNER_NAME> --labels <APPROVED_LABELS> --work _work && unset RUNNER_TOKEN'
repository scope에서만 선택합니다. B와 동시에 실행하지 않습니다.
B. organization runner 등록
sudo -u <RUNNER_USER> -H bash -lc 'cd /srv/actions-runner && read -rsp "Registration token: " RUNNER_TOKEN && printf "\n" && ./config.sh --unattended --url https://github.com/<OWNER> --token "$RUNNER_TOKEN" --runnergroup <APPROVED_RUNNER_GROUP> --name <RUNNER_NAME> --labels <APPROVED_LABELS> --work _work && unset RUNNER_TOKEN'
organization scope에서만 선택합니다. A와 동시에 실행하지 않으며 repository runner에는 --runnergroup을 쓰지 않습니다.
등록 결과
sudo -u <RUNNER_USER> test -f /srv/actions-runner/.runner
sudo stat -Lc '%a %U:%G %n' /srv/actions-runner /srv/actions-runner/.runner
service 설치
sudo bash -lc 'cd /srv/actions-runner && ./svc.sh install <RUNNER_USER>'
systemctl cat 'actions.runner.<SCOPE>.<RUNNER_NAME>.service'
  • token 값을 history·journal·ticket에 남기지 않았습니다.
  • runner group은 선택한 repository만 허용합니다.
  • label은 민감 capability를 명시하며 workflow가 self-hosted만 넓게 선택하지 않습니다.
  • service User는 전용 비 root 계정입니다.
결과 읽기

예상 group·label·scope와 다르면 job을 받기 전에 제거 토큰으로 등록을 해제합니다.

다음 판단

service를 시작하고 GitHub UI의 Idle 상태를 확인합니다.

5
변경패키지·설정·서비스 상태가 달라지는 단계

service 시작·등록 상태 확인

service를 시작한 뒤 로컬 상태와 GitHub UI를 함께 확인하고 최소 권한 진단 workflow만 실행합니다.

변경 단계입니다. 대상 서버, 백업 파일, 서비스 중단 영향과 바로 이전 상태로 돌아가는 방법을 다시 확인하세요.
service 시작
sudo bash -lc 'cd /srv/actions-runner && ./svc.sh start && ./svc.sh status'
systemd
systemctl status 'actions.runner.<SCOPE>.<RUNNER_NAME>.service' --no-pager -l
systemctl show 'actions.runner.<SCOPE>.<RUNNER_NAME>.service' -p User -p ActiveState -p NRestarts
제한 로그
journalctl -u 'actions.runner.<SCOPE>.<RUNNER_NAME>.service' -n 100 --no-pager
repository 이름·경로·token·secret을 외부 공유 전에 마스킹합니다.
  • GitHub UI에서 runner가 Online·Idle입니다.
  • 서비스 계정과 unit이 승인값입니다.
  • 진단 workflow에 write token·production secret을 주지 않았습니다.
결과 읽기

Offline·restart loop이면 job을 queue하지 않고 네트워크·권한·runner diagnostic log를 확인합니다.

다음 판단

업데이트·격리·업무 smoke test를 검증합니다.

6
변경패키지·설정·서비스 상태가 달라지는 단계

업데이트 정책·업무 검증

runner 자동 업데이트 상태를 감시하고 조직 변경창에서 승인 버전을 검증합니다. 자동 업데이트를 끈 경우 GitHub의 30일 업데이트 요구를 운영 기준에 반영합니다.

변경 단계입니다. 대상 서버, 백업 파일, 서비스 중단 영향과 바로 이전 상태로 돌아가는 방법을 다시 확인하세요.
version·service
sudo -u <RUNNER_USER> /srv/actions-runner/bin/Runner.Listener --version
systemctl is-enabled 'actions.runner.<SCOPE>.<RUNNER_NAME>.service'
systemctl is-active 'actions.runner.<SCOPE>.<RUNNER_NAME>.service'
update 진단
sudo find /srv/actions-runner/_diag -maxdepth 1 -type f -name 'Runner_*' -printf '%TY-%Tm-%TdT%TH:%TM %m %u:%g %p
' | tail -20
로그 본문을 공유하기 전 secret·repository·내부 주소를 마스킹합니다.
격리 확인
sudo find /srv/actions-runner/_work -mindepth 1 -maxdepth 2 -printf '%m %u:%g %p
' | head -100
  • 승인 version과 GitHub가 요구하는 최소 version을 충족합니다.
  • ephemeral runner 로그는 host 밖의 승인된 로그 저장소로 전달됩니다.
  • job 뒤 workspace·credential·process 격리를 확인했습니다.
결과 읽기

지속형 runner는 작업 간 상태가 남을 수 있으므로 비신뢰 PR에는 사용하지 않습니다.

다음 판단

권한·secret·update·service hardening을 점검합니다.

7
확인시스템을 변경하지 않는 점검 단계

비신뢰 코드·secret·host 권한 점검

self-hosted runner에서 실행된 코드는 host와 후속 job을 손상할 수 있다고 가정하고 trust boundary를 분리합니다.

계정 권한
id <RUNNER_USER>
sudo -l -U <RUNNER_USER>
민감 경로
sudo namei -l /srv/actions-runner /srv/actions-runner/_work
listener·outbound
sudo ss -lntup
sudo ufw status verbose
  • fork PR과 외부 contributor 코드는 이 runner에 배정하지 않습니다.
  • GITHUB_TOKEN permissions는 workflow별 read 최소값이며 secret은 environment 승인으로 보호합니다.
  • runner 계정에 sudo·Docker socket·production SSH key를 주지 않습니다.
  • 일회성 runner는 한 job 뒤 폐기하고 외부 로그를 보존합니다.
  • <...> 자리표시자는 승인된 리터럴 값으로 직접 치환하며 외부 입력으로 shell 명령을 조립하거나 eval하지 않습니다.
결과 읽기

sudo나 Docker socket, 비신뢰 workflow, 공유 production credential이 있으면 운영 투입을 중단합니다.

다음 판단

등록 해제와 backup/신규 설치 롤백을 분리합니다.

8
변경패키지·설정·서비스 상태가 달라지는 단계

등록 해제·service 중지·신규 파일 격리

GitHub UI에서 job이 없음을 확인하고 단기 remove token을 대화형으로 입력해 먼저 등록을 해제합니다. package·work data를 삭제하지 않습니다.

변경 단계입니다. 대상 서버, 백업 파일, 서비스 중단 영향과 바로 이전 상태로 돌아가는 방법을 다시 확인하세요.
상태 보존
sudo bash -lc 'cd /srv/actions-runner && ./svc.sh status'
sudo find /srv/actions-runner/_diag -maxdepth 1 -type f -printf '%m %u:%g %s %p
' | tail -20
등록 해제
sudo bash -lc 'cd /srv/actions-runner && ./svc.sh stop && ./svc.sh uninstall'
sudo -u <RUNNER_USER> -H bash -lc 'cd /srv/actions-runner && read -rsp "Remove token: " REMOVE_TOKEN && printf "\n" && ./config.sh remove --token "$REMOVE_TOKEN" && unset REMOVE_TOKEN'
remove token도 1시간 유효한 단기 값이며 외부에 출력하지 않습니다.
A. 기존 설치 복구
sudo stat -Lc '%a %U:%G %n' <APPROVED_BACKUP_DIR>/.runner.<BACKUP_SUFFIX> <APPROVED_BACKUP_DIR>/.service.<BACKUP_SUFFIX> 2>/dev/null || true
기존 설치에서만 선택합니다. identity backup은 증거이며 운영 경로로 복원하지 않습니다. fresh removal token으로 잔여 등록을 정리하고 새 registration token으로 재등록한 뒤 이전 enabled/active 상태를 승인 기록대로 복원합니다. B와 동시에 실행하지 않습니다.
B. backup 없는 신규 설치 격리
sudo systemctl disable --now 'actions.runner.<SCOPE>.<RUNNER_NAME>.service' || true
sudo install -d -o root -g root -m 0700 <APPROVED_QUARANTINE_DIR>
if ! sudo test -f <APPROVED_BACKUP_DIR>/.runner.<BACKUP_SUFFIX> && ! sudo test -f <APPROVED_BACKUP_DIR>/.service.<BACKUP_SUFFIX> && sudo test -d /srv/actions-runner; then sudo mv --no-clobber /srv/actions-runner <APPROVED_QUARANTINE_DIR>/actions-runner.new; fi
변경 기록상 신규 설치이고 두 backup이 모두 없음을 확인할 때만 선택합니다. A와 동시에 실행하지 않으며 package·workspace·diagnostic log를 purge하지 않습니다.
  • GitHub UI에서 runner 등록이 제거됐습니다.
  • service는 inactive·disabled입니다.
  • 실패 artifact·work·diag는 0700 격리 경로에 보존됐습니다.
  • remove token은 폐기됐습니다.
결과 읽기

offline runner를 UI에서만 삭제하면 host service가 남을 수 있으므로 양쪽을 확인합니다.

다음 판단

원인·version·job 영향·격리 경로와 재등록 승인 조건을 기록합니다.

SECURITY CHECK

운영 전 마지막 보안 점검

  • runner group·repository scope·labels를 최소화합니다.
  • registration/remove token은 1시간 유효한 단기 값으로 대화형 입력하고 마스킹합니다.
  • 비신뢰 PR은 지속형 self-hosted runner에서 실행하지 않습니다.
  • 전용 비 root 계정이며 sudo·Docker socket·production key가 없습니다.
  • ephemeral runner의 진단 로그는 외부 승인 저장소에 보존합니다.
  • <...> 자리표시자는 승인된 리터럴 값으로 직접 치환하며 외부 입력으로 shell 명령을 조립하거나 eval하지 않습니다.

COMMON ERRORS

자주 막히는 지점

Runner가 Offline

증상
GitHub UI에서 Offline이고 service는 실행 중입니다.
가능한 원인
outbound DNS·TLS·proxy 또는 오래된 runner version 문제일 수 있습니다.
확인 순서
config.sh --check와 제한 로그로 확인하고 TLS 검증을 끄지 않습니다.
트러블슈팅으로 이어보기

job 뒤 상태가 남음

증상
후속 job이 이전 workspace·process를 봅니다.
가능한 원인
지속형 runner의 trust boundary가 충분하지 않습니다.
확인 순서
비신뢰 workload를 ephemeral 격리 환경으로 이전하고 host를 재이미징합니다.

등록 token 만료

증상
config.sh가 token 오류로 등록에 실패합니다.
가능한 원인
registration token 발급 후 1시간이 지났거나 scope가 다릅니다.
확인 순서
올바른 repository·organization 설정에서 새 token을 발급해 대화형으로 한 번만 입력합니다.

PRIMARY REFERENCES

공식 문서

설치 저장소와 지원 버전은 바뀔 수 있습니다. 검토일 이후에는 링크된 공식 문서와 현재 서버의 패키지 후보 버전을 함께 확인하세요.

도구 빠른 검색

최근 사용한 도구를 다시 열거나, 이름과 기능으로 검색하세요.

검색어와 도구의 입력·결과는 저장하지 않습니다.

↑↓ 이동 · Enter 열기 · Esc 닫기