파이썬 GitHub Actions CI/CD 가이드: 자동 테스트·린트 구축
GitHub Actions로 파이썬 코드 푸시와 풀 리퀘스트마다 Ruff 린트와 pytest 테스트를 자동 실행하는 CI/CD 워크플로 구성법을 정리합니다.

파이썬 프로젝트의 GitHub Actions CI/CD는 저장소에 코드를 푸시하거나 풀 리퀘스트를 열 때 가상 실행 환경에서 의존성을 설치하고, 린트와 테스트를 자동 수행하도록 만드는 방식입니다. 처음에는 배포까지 한꺼번에 연결하기보다 Ruff 검사 → pytest 테스트가 모두 통과해야 병합할 수 있는 CI부터 구축하는 것이 안전합니다.
GitHub 공식 Python CI 구성 문서 확인하기
핵심 요약
- 워크플로 파일은 저장소의
.github/workflows/에 YAML로 저장합니다. push와pull_request이벤트를 분리해 어떤 변경에서 검사가 실행될지 정합니다.actions/setup-python으로 Python 버전을 고정하고, 잠금 파일을 기준으로 의존성을 설치합니다.- 배포 자격 증명은 코드에 넣지 않고 GitHub Secrets 또는 OIDC를 사용하며 권한은 최소화합니다.
CI/CD 흐름을 먼저 이해하기
CI는 변경된 코드를 자동으로 검사하고 테스트해 문제를 병합 전에 찾는 과정입니다. CD는 검증을 통과한 결과물을 패키지 저장소나 서버에 전달하는 과정입니다. 둘은 같은 워크플로에 둘 수도 있지만, 운영 환경을 처음 구성한다면 CI와 배포 작업을 나누는 편이 실패 원인을 추적하기 쉽습니다.
| 단계 | 실행 시점 | 주요 작업 | 실패 시 조치 |
|---|---|---|---|
| CI | push·PR | 설치, 린트, 테스트 | 코드 수정 후 재실행 |
| 빌드 | 태그·릴리스 | 패키지·이미지 생성 | 의존성·빌드 로그 확인 |
| 배포 | 승인 또는 릴리스 | 운영 환경 반영 | 배포 중지·롤백 |
테스트 자체가 없다면 자동화는 단순히 명령이 끝났다는 사실만 확인합니다. 작은 함수부터 단위 테스트를 만들고 실패 조건을 명확히 정해야 CI가 품질 관문 역할을 합니다.
기본 GitHub Actions 워크플로 작성
아래 파일을 .github/workflows/python-ci.yml로 저장하면 main 브랜치 대상 push와 풀 리퀘스트에서 실행됩니다. 프로젝트가 pyproject.toml을 사용한다면 설치 명령만 프로젝트 방식에 맞게 바꾸면 됩니다.
name: Python CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint
run: ruff check .
- name: Test
run: pytest -q
on은 실행 조건, jobs는 독립 실행 단위, steps는 순서대로 수행할 명령입니다. runs-on은 GitHub가 제공하는 실행 환경을 정합니다. 액션 버전과 Python 버전을 명시하면 실행 환경 변화로 인한 예상 밖의 실패를 줄일 수 있습니다.
처음 커밋한 뒤에는 Actions 실행 화면에서 각 단계가 의도한 순서로 작동하는지 확인하세요. 워크플로가 아예 나타나지 않으면 파일 경로와 기본 브랜치, YAML 문법을 먼저 살펴봅니다. 실행은 되지만 조건이 예상과 다르면 on 아래의 브랜치 필터와 이벤트 종류를 확인해야 합니다. 테스트가 통과한 커밋에만 초록색 상태가 표시되도록 구성하면 변경 이력을 판단하기도 쉬워집니다.
pytest와 Ruff를 품질 관문으로 연결하기
린트는 문법 오류, 사용하지 않는 import, 스타일 문제를 빠르게 찾고 테스트는 실제 동작을 검증합니다. 두 명령은 로컬에서도 동일하게 통과해야 합니다. CI에서만 실패한다면 파일명 대소문자, 운영체제별 경로, 누락된 환경변수, 설치되지 않은 개발 의존성을 우선 확인하세요.
Ruff를 단지 보고용으로 둘지 실패 시 병합을 막을지도 정해야 합니다. 팀 규칙으로 확정했다면 continue-on-error를 사용하지 않는 편이 일관됩니다. 포매팅까지 검사하려면 ruff format --check .를 별도 단계로 추가할 수 있습니다.
개발 의존성을 빠뜨리지 않는 방법
pytest와 ruff가 운영용 requirements에 없다면 requirements-dev.txt나 프로젝트의 개발 의존성 그룹을 설치해야 합니다. 명령이 실행 환경마다 달라지지 않도록 잠금 파일을 저장하고, CI 설치 명령을 README에도 기록해 두는 것이 좋습니다.
여러 Python 버전과 의존성 캐시
라이브러리처럼 여러 Python 버전을 지원해야 한다면 매트릭스를 사용합니다. 반대로 사내 서비스가 Python 3.12 하나로 운영된다면 불필요한 매트릭스는 실행 시간만 늘릴 수 있습니다. 지원 범위를 먼저 정한 뒤 필요한 버전만 검사하세요.
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
setup-python의 pip 캐시는 의존성 다운로드 시간을 줄이지만 오래된 패키지를 임의로 재사용하는 기능은 아닙니다. requirements나 잠금 파일이 바뀌면 캐시 키도 달라집니다. 캐시를 켠 뒤에도 설치 단계는 생략하지 않습니다.
배포 자동화와 보안 원칙
CI가 안정화된 뒤에만 배포 job을 추가하세요. 테스트 job을 needs로 지정하면 검증이 성공한 경우에만 배포가 시작됩니다. 운영 배포에는 GitHub Environments의 승인 규칙과 배포 대상 브랜치 제한을 함께 적용하는 것이 좋습니다.
GITHUB_TOKEN권한은 기본적으로contents: read처럼 최소 범위로 선언합니다.- API 키와 비밀번호는 저장소·로그·YAML에 직접 쓰지 않습니다.
- 클라우드가 지원한다면 장기 키보다 OIDC 기반 단기 자격 증명을 우선 검토합니다.
- 외부 액션은 제공자와 소스를 확인하고, 민감한 배포에서는 커밋 SHA 고정을 고려합니다.
- 풀 리퀘스트 제목이나 브랜치명처럼 신뢰할 수 없는 값을 셸 명령에 직접 삽입하지 않습니다.
로컬 개발에서도 비밀값은 코드에서 분리해야 합니다. 다만 .env 파일을 GitHub에 커밋하는 것이 아니라, 개발 환경과 Actions Secrets를 각각 관리해야 합니다.
실패 로그 점검과 실수 방지 체크리스트
- Actions 탭에서 실패한 job과 최초 실패 step을 확인합니다.
- 같은 설치·린트·테스트 명령을 깨끗한 로컬 가상환경에서 실행합니다.
- Python 버전, 작업 디렉터리, 파일 경로와 환경변수 이름을 비교합니다.
- 일시적 네트워크 오류인지 코드 오류인지 로그로 구분한 뒤 필요한 단계만 재실행합니다.
- 필수 상태 검사를 브랜치 보호 규칙에 연결해 실패한 PR이 병합되지 않게 합니다.
- 워크플로 파일 위치가
.github/workflows인지 확인 - YAML 들여쓰기와 이벤트 브랜치 이름 확인
- 테스트·린트 도구가 의존성 파일에 포함됐는지 확인
- 필요한 Secrets 이름이 환경별로 정확한지 확인
- 배포 job이 테스트 성공을 의존하는지 확인
- 권한을 불필요하게
write-all로 주지 않았는지 확인
자주 묻는 질문
1. GitHub Actions는 무료인가요?
공개 저장소와 요금제에 따라 제공량이 다르며, 비공개 저장소의 실행 시간·저장공간에는 요금제별 한도가 있습니다. 현재 한도는 GitHub 공식 빌링 문서에서 확인해야 합니다.
2. 워크플로 파일은 어디에 저장하나요?
저장소 루트의 .github/workflows 디렉터리에 .yml 또는 .yaml 파일로 저장합니다.
3. push와 pull_request를 모두 넣어야 하나요?
PR 단계 검증과 main 반영 후 검증을 모두 원하면 함께 사용합니다. 중복 실행 비용이 부담되면 팀의 병합 방식에 맞춰 이벤트와 브랜치를 제한하세요.
4. 로컬에서는 통과하는데 CI에서만 실패하는 이유는 무엇인가요?
Python·패키지 버전 차이, Linux의 대소문자 구분, 누락된 환경변수, 테스트 순서 의존성이 흔한 원인입니다.
5. requirements.txt가 없으면 어떻게 하나요?
Poetry, uv, PDM 등 실제 프로젝트가 쓰는 도구의 설치·동기화 명령으로 바꾸고 해당 잠금 파일을 기준으로 캐시를 구성합니다.
6. 여러 Python 버전을 모두 테스트해야 하나요?
패키지는 공식 지원 버전을 매트릭스로 검사하는 것이 유용합니다. 단일 운영 서비스라면 운영 버전과 차기 업그레이드 후보 정도로 제한할 수 있습니다.
7. 실패한 테스트를 무시하고 배포해도 되나요?
권장하지 않습니다. 임시 예외가 필요하다면 이유와 종료 시점을 기록하고, 핵심 테스트가 실패하면 배포를 차단해야 합니다.
8. API 키는 YAML에 넣어도 되나요?
안 됩니다. GitHub Secrets 또는 OIDC를 사용하고 로그에 비밀값이 출력되지 않도록 명령과 디버그 출력을 점검하세요.
9. 캐시를 사용하면 pip install을 생략할 수 있나요?
아닙니다. 캐시는 다운로드를 빠르게 할 뿐이므로 설치 명령은 그대로 실행해야 재현 가능한 환경이 만들어집니다.
10. 자동 배포는 언제 추가하는 것이 좋나요?
린트와 테스트가 안정적으로 동작하고 롤백 절차가 준비된 뒤 추가하세요. 처음에는 수동 승인 환경을 거치게 하는 편이 안전합니다.
공식 출처
- GitHub Docs: Building and testing Python
- GitHub Docs: Workflow syntax
- GitHub Docs: Secure use reference
공식 문서 확인일: 2026년 9월 10일. GitHub Actions의 액션 버전, 러너 환경과 요금 정책은 변경될 수 있으므로 실제 적용 전 공식 문서를 다시 확인하세요.