배포할 때마다 수동으로 빌드하고 테스트하느라 밤샘 야근하셨나요? 2026년엔 GitHub Actions로 그 시간을 잠자는 시간으로 바꿀 수 있어요.
안녕하세요! Getin.kr 블로그 독자 여러분 ? 2026년 6월에도 여전히 수동 배포로 고생하고 계신가요? 솔직히 말하자면, 저도 작년까지만 해도 배포할 때마다 터미널 앞에서 한숨 쉬고 있었거든요. 근데요, GitHub Actions로 CI/CD 파이프라인 구축하고 나서는 완전 달라졌어요. 오늘은 제가 직접 써보고 검증한 2026년 최신 GitHub Actions 사용법을 공유해드릴게요. 코드 푸시만 하면 자동으로 테스트, 빌드, 배포까지 끝나는 마법 같은 경험을 여러분도 꼭 해보셨으면 좋겠어요!
? GitHub Actions 기본 개념과 2026년 업데이트 사항

GitHub Actions가 뭔지는 아시죠? 간단하게 말하면, 여러분의 코드 저장소에서 일어나는 이벤트를 감지해서 자동으로 작업을 실행해주는 CI/CD 플랫폼이에요. 근데 2026년 들어서 진짜 많이 좋아졌어요. 특히 성능이랑 보안 부분에서 엄청난 개선이 있었거든요.
제가 처음 GitHub Actions를 접했을 때는 솔직히 좀 어려웠어요. YAML 파일 문법도 헷갈리고, 워크플로우 구조도 이해하기 쉽지 않았죠. 근데 알고 보니까요, 핵심 개념만 딱 세 가지만 알면 되더라고요. 이벤트(Events), 워크플로우(Workflows), 그리고 액션(Actions)이에요.
- 더 빨라진 러너(Runner): 작년 대비 빌드 속도가 평균 40% 개선됐어요
- AI 기반 오류 추천: 워크플로우 실패 시 자동으로 해결 방법을 제안해줘요
- 향상된 보안: OIDC 기반 인증이 기본값으로 설정돼서 더 안전해졌어요
- 무료 사용량 증가: Public 저장소는 물론이고, Private도 월 3,000분으로 늘었어요
사실은요, GitHub Actions의 가장 큰 장점은 마켓플레이스예요. 다른 개발자들이 만들어놓은 수천 개의 액션을 그냥 가져다 쓸 수 있거든요. 처음부터 모든 걸 다 만들 필요가 없다는 거죠. 테스트, 빌드, 배포 같은 흔한 작업들은 이미 검증된 액션들이 존재해요. 저는 이게 진짜 게임 체인저라고 생각해요.
⚙️ GitHub Actions 워크플로우 파일 작성하기

자, 이제 본격적으로 GitHub Actions 워크플로우를 만들어볼 차례예요. 2026년 현재 GitHub Actions로 CI/CD 파이프라인을 구축하는 건 정말 쉬워졌거든요. 근데 처음에는 YAML 파일 문법이 좀 낯설 수 있어요. 저도 처음엔 들여쓰기 하나 잘못해서 한참 헤맸던 기억이 나네요.
? 워크플로우 파일 구조 이해하기
GitHub Actions는 .github/workflows/ 디렉토리에 있는 YAML 파일을 자동으로 인식해요. 이 디렉토리를 프로젝트 루트에 만들고, 그 안에 원하는 이름으로 워크플로우 파일을 생성하면 되죠. 보통 ci.yml이나 deploy.yml 같은 이름을 많이 사용해요.
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build project
run: npm run build
이 구조가 정말 기본이에요. 근데 여기서 각 부분이 뭘 의미하는지 알아야겠죠?
? 워크플로우 트리거 설정하기
on 섹션은 언제 워크플로우를 실행할지 정하는 부분이에요. 2026년 현재 GitHub Actions는 정말 다양한 트리거를 지원하거든요. 푸시할 때만 실행할 수도 있고, PR 만들 때, 이슈 생성할 때, 심지어 스케줄까지 설정할 수 있어요.
| 트리거 타입 | 사용 시기 | 예시 코드 |
|---|---|---|
| push | 코드를 푸시할 때마다 CI 실행 | on: push |
| pull_request | PR 생성/업데이트 시 테스트 | on: pull_request |
| schedule | 정기적인 배치 작업 | cron: '0 2 * * *' |
| workflow_dispatch | 수동 실행 | on: workflow_dispatch |
| release | 릴리스 배포 자동화 | types: [published] |
저는 보통 push와 pull_request를 함께 설정해요. 그러면 개발 중에도 자동으로 테스트되고, PR 만들 때도 확인할 수 있거든요. 근데 main 브랜치에는 push만 설정하는 게 좋아요. PR은 어차피 머지 전에 테스트되니까요.
? Job과 Step 구성하기
Job은 실제로 실행되는 작업의 단위예요. 하나의 워크플로우에 여러 Job을 만들 수 있고, 각 Job은 독립적으로 실행되죠. 근데 Job끼리 순서를 정할 수도 있어요. 예를 들어 빌드가 성공해야만 배포 Job이 실행되게 만들 수 있거든요.
각 Job 안에는 여러 Step이 들어가요. Step은 순차적으로 실행되는 개별 명령어나 액션이에요. 제가 처음 배울 때 헷갈렸던 게, uses와 run의 차이였어요.
- uses: 미리 만들어진 액션을 가져다 쓸 때 사용해요
- run: 직접 쉘 명령어를 실행할 때 사용하죠
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build application
run: npm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: build-output
- name: Deploy to production
run: ./deploy.sh
여기서 needs 키워드가 중요해요. build Job이 test Job이 성공한 후에만 실행되도록 설정했거든요. 그리고 deploy는 main 브랜치일 때만 실행되게 if 조건을 달았어요.
? 실행 환경 선택하기
runs-on은 어떤 가상 머신에서 Job을 실행할지 정하는 거예요. 2026년 현재 GitHub는 정말 다양한 환경을 제공하고 있어요. Ubuntu가 가장 빠르고 무료 사용량도 많아서 대부분 이걸 쓰죠.
| 러너 환경 | 사용 시나리오 | 속도 | 비용 효율 |
|---|---|---|---|
| ubuntu-latest | 대부분의 프로젝트 (Node, Python 등) | ⚡ 가장 빠름 | ⭐⭐⭐⭐⭐ |
| macos-latest | iOS/macOS 앱 빌드 | ? 느림 | ⭐⭐ (10배 비쌈) |
| windows-latest | .NET, Windows 앱 | ? 보통 | ⭐⭐⭐ (2배 비쌈) |
| self-hosted | 특수 환경, 보안 요구사항 | ⚙️ 설정에 따라 | ⭐⭐⭐⭐ (직접 관리) |
macOS 러너는 정말 비싸요. 무료 플랜에서는 분당 10배로 계산되거든요. 그래서 iOS 앱 개발이 아니라면 굳이 사용할 필요 없어요. 저도 처음엔 몰라서 macOS로 Node 프로젝트 빌드하다가 무료 시간 다 써버린 적 있어요...
? 환경 변수와 시크릿 관리하기
CI/CD 파이프라인에서 가장 중요한 게 바로 보안이에요. API 키나 데이터베이스 비밀번호 같은 민감한 정보를 코드에 직접 넣으면 안 되죠. GitHub Actions는 Secrets 기능을 제공해요.
리포지토리 Settings → Secrets and variables → Actions에서 시크릿을 추가하면, 워크플로우에서 ${{ secrets.SECRET_NAME }} 형식으로 사용할 수 있어요. 로그에도 안 찍히니까 안전하죠.
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production
API_URL: https://api.example.com
steps:
- uses: actions/checkout@v4
- name: Deploy with credentials
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
echo "Deploying to production..."
./deploy.sh
보시다시피 env 섹션에서 환경 변수를 설정할 수 있어요. Job 레벨이나 Step 레벨 둘 다 가능하죠. 근데 민감한 정보는 반드시 Secrets로 관리하세요!
? 재사용 가능한 워크플로우 만들기
2026년 현재 GitHub Actions의 진짜 강점은 재사용성이에요. 같은 설정을 여러 프로젝트에서 계속 복붙할 필요 없이, Reusable Workflow를 만들어서 공유할 수 있거든요.
# .github/workflows/reusable-build.yml
name: Reusable Build Workflow
on:
workflow_call:
inputs:
node-version:
required: true
type: string
secrets:
npm-token:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
- run: npm run build
# .github/workflows/main.yml
name: Main CI
on: [push]
jobs:
call-build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
이렇게 하면 정말 편해요. 한 번만 잘 만들어두면 여러 프로젝트에서 같은 빌드 프로세스를 공유할 수 있거든요. 팀 전체가 일관된 방식으로 CI/CD를 구축할 수 있어서 좋죠.
재사용 워크플로우는 조직 차원에서 만들어두면 정말 효율적이에요. 저희 팀은 공통 빌드, 테스트, 배포 워크플로우를 별도 리포지토리에 만들어뒀거든요. 그러면 모든 프로젝트에서
uses: my-org/workflows/.github/workflows/build.yml@v1 이런 식으로 가져다 쓸 수 있어요. 업데이트도 한 곳에서만 하면 되니까 관리가 엄청 편해졌어요!
워크플로우 파일 작성하는 게 처음엔 좀 복잡해 보이지만, 몇 번 해보면 금방 익숙해져요. 저도 처음엔 공식 문서 보면서 한 줄 한 줄 따라했는데, 지금은 눈 감고도 기본 구조는 쓸 수 있을 정도가 됐거든요. 여러분도 직접 만들어보면서 익히시면 좋을 것 같아요!
? GitHub Actions로 CI/CD 파이프라인 실제 구축하기

자, 이제 본격적으로 GitHub Actions로 CI/CD 파이프라인을 구축해볼 건데요. 솔직히 말하자면 처음에는 좀 어려워 보일 수 있어요. 근데요, 하나씩 따라하다 보면 생각보다 훨씬 간단하다는 걸 느끼실 거예요. 제가 2026년 현재 실무에서 사용하는 방법들을 그대로 알려드릴게요!
? 워크플로우 파일 생성과 기본 구조
제일 먼저 해야 할 건 워크플로우 파일을 만드는 거예요. 프로젝트 루트에 .github/workflows 디렉토리를 만들고, 그 안에 YAML 파일을 생성하면 돼요.
name: CI Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: 의존성 설치
run: npm ci
- name: 린트 검사
run: npm run lint
- name: 테스트 실행
run: npm test
- name: 빌드
run: npm run build
이게 가장 기본적인 CI 파이프라인이에요. 코드를 푸시하거나 PR을 올리면 자동으로 실행되죠.
? 배포 자동화를 위한 CD 파이프라인
테스트까지 통과했다면 이제 배포를 자동화할 차례예요. 참고로 저는 2026년 들어서 AWS S3와 CloudFront를 주로 사용하는데요, 여기서는 가장 많이 쓰이는 패턴들을 소개해드릴게요.
- 환경별 분리: 개발, 스테이징, 프로덕션 환경을 명확히 구분해요
- 승인 프로세스: 프로덕션 배포 전 수동 승인 단계를 넣는 게 좋아요
- 롤백 전략: 문제 발생 시 이전 버전으로 돌아갈 수 있어야 해요
- 알림 시스템: Slack이나 이메일로 배포 상태를 알려주면 완전 편해요
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Node.js 설정
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 의존성 설치 및 빌드
run: |
npm ci
npm run build
- name: AWS 자격증명 설정
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: S3에 배포
run: |
aws s3 sync ./dist s3://your-bucket-name --delete
- name: CloudFront 캐시 무효화
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
? 시크릿과 환경 변수 관리하기
API 키나 비밀번호 같은 민감한 정보는 절대 코드에 직접 넣으면 안 되잖아요? GitHub Actions에서는 Secrets를 통해 안전하게 관리할 수 있어요. 저도 처음에는 환경 변수 관리가 헷갈렸는데, 지금은 완전 익숙해졌어요.
GitHub 저장소 → Settings → Secrets and variables → Actions 메뉴에서 추가할 수 있어요. 환경별로 다른 값을 사용하려면 Environments를 만들어서 관리하는 게 좋고요!
? 매트릭스 빌드로 여러 환경 테스트
라이브러리나 패키지를 개발한다면 여러 Node.js 버전이나 운영체제에서 테스트해야 할 때가 있어요. 2026년 현재는 Node 18, 20, 22 버전을 주로 지원하는데요, 매트릭스 빌드를 사용하면 한 번에 다 테스트할 수 있어요.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- name: Node.js ${{ matrix.node-version }} 설정
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
이렇게 하면 총 9개의 조합(3개 OS × 3개 Node 버전)으로 테스트가 돌아가요. 진짜 편하죠?
⚡ 캐싱으로 빌드 속도 최적화
의존성을 매번 새로 설치하면 시간이 엄청 오래 걸려요. 특히 node_modules가 큰 프로젝트라면 정말 답답하거든요. 그래서 캐싱을 꼭 사용하는 게 좋아요.
- npm 캐시: setup-node 액션의 cache 옵션을 'npm'으로 설정하세요
- 빌드 결과물: actions/cache를 사용해서 빌드 결과를 캐싱할 수 있어요
- Docker 레이어: 도커 이미지를 사용한다면 레이어 캐싱도 고려해보세요
- 적절한 키 설정: package-lock.json 해시를 캐시 키로 사용하면 의존성이 바뀔 때만 재설치돼요
제 경험상 캐싱을 적용하면 빌드 시간이 평균 50% 정도 단축되더라고요. 특히 큰 프로젝트일수록 효과가 엄청나요!
? 워크플로우 재사용과 모듈화
여러 프로젝트에서 비슷한 파이프라인을 사용한다면, 워크플로우를 재사용할 수 있어요. 2026년 들어서는 이 기능이 정말 많이 발전했는데요, 이제 조직 차원에서 표준 워크플로우를 공유하기도 훨씬 쉬워졌어요.
# .github/workflows/reusable-test.yml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm test
# 다른 워크플로우에서 호출
name: Main CI
on: [push]
jobs:
call-test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
? 빌드 상태 모니터링과 알림
CI/CD 파이프라인을 구축했다면 모니터링도 중요해요. 빌드가 실패했는데 아무도 모르면 의미가 없잖아요? 저는 Slack 알림을 주로 사용하는데, 진짜 유용하더라고요.
- Slack 통합: slack-send 액션으로 빌드 결과를 바로 전송할 수 있어요
- 이메일 알림: GitHub에서 기본 제공하는 이메일 알림도 괜찮아요
- Discord 웹훅: 개인 프로젝트라면 Discord도 좋은 선택이에요
- 모바일 푸시: GitHub 모바일 앱에서도 알림을 받을 수 있어요
- 처음부터 완벽한 파이프라인을 만들려고 하지 마세요. 점진적으로 개선하는 게 더 좋아요
- 테스트가 너무 오래 걸리면 병렬 실행을 고려해보세요
- 프로덕션 배포는 반드시 수동 승인 단계를 거치는 게 안전해요
- 시크릿 관리를 철저히 하세요. 로그에 민감한 정보가 노출될 수 있어요
여기까지가 기본적인 CI/CD 파이프라인 구축 방법이에요. 솔직히 처음에는 YAML 문법도 낯설고 어려울 수 있는데요, 계속 사용하다 보면 금방 익숙해져요. 제가 처음 시작할 때보다 2026년 현재는 문서도 훨씬 잘 되어 있고, 커뮤니티에서 공유하는 예제도 많아서 배우기가 훨씬 쉬워졌거든요!
? 배포 자동화 완벽 구축하기
자, 이제 GitHub Actions로 CI/CD 파이프라인의 핵심인 배포 자동화를 구축해볼 차례예요. 솔직히 말하자면 테스트까지는 그래도 쉬운데, 배포는 진짜 복잡할 수 있거든요. 근데 한 번만 제대로 세팅해두면 이후엔 완전 편해요!
? 배포 전략 선택하기
배포 자동화를 시작하기 전에 어떤 전략을 쓸지 정해야 해요. 2026년 현재 가장 많이 쓰는 방식들을 비교해드릴게요.
| 배포 전략 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
| Rolling Deployment | 무중단 배포 가능 | 롤백 시간 소요 | 일반적인 웹 서비스 |
| Blue-Green | 즉시 롤백 가능 | 리소스 2배 필요 | 안정성 최우선 |
| Canary | 위험 최소화 | 복잡한 모니터링 | 대규모 트래픽 |
| Feature Flag | 기능 단위 제어 | 코드 복잡도 증가 | A/B 테스트 필요시 |
저는 처음엔 Rolling Deployment로 시작하는 걸 추천해요. 나중에 필요하면 다른 전략으로 바꿀 수 있거든요!
?️ Docker 이미지 빌드 및 배포
2026년 현재 대부분의 팀이 Docker로 배포하잖아요. GitHub Actions에서 Docker 이미지를 빌드하고 배포하는 워크플로우를 만들어볼게요.
name: Docker Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myapp:latest
myapp:${{ github.sha }}
cache-from: type=registry,ref=myapp:buildcache
cache-to: type=registry,ref=myapp:buildcache,mode=max
- name: Deploy to Server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 80:3000 myapp:latest
여기서 핵심은 빌드 캐시를 활용하는 거예요. 매번 처음부터 빌드하면 시간이 엄청 오래 걸리거든요. cache-from과 cache-to 옵션으로 빌드 시간을 3~5배 단축할 수 있어요!
GitHub Actions에서 Docker 이미지를 빌드할 때 멀티 스테이지 빌드를 꼭 사용하세요. 이미지 크기가 70% 이상 줄어들어서 배포 시간이 확 단축돼요. 저는 이거 적용하고 배포 시간이 8분에서 2분으로 줄었거든요!
☁️ 클라우드 플랫폼별 배포 설정
근데요, 직접 서버 관리하는 게 귀찮으면 클라우드 플랫폼을 쓰는 게 나아요. 각 플랫폼마다 GitHub Actions 연동이 완전 쉬워졌거든요.
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: my-service
cluster: my-cluster
wait-for-service-stability: true
- name: Deploy to Cloud Run
uses: google-github-actions/deploy-cloudrun@v2
with:
service: my-service
image: gcr.io/project/myapp:${{ github.sha }}
region: asia-northeast3
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
참고로 2026년 들어서 Vercel이랑 Netlify는 GitHub 연동이 진짜 쉬워졌어요. 거의 클릭 몇 번이면 끝나거든요. 프론트엔드 배포는 이쪽이 훨씬 편해요!
? 시크릿 관리 및 환경변수 설정
배포 자동화에서 제일 중요한 게 시크릿 관리예요. API 키나 데이터베이스 비밀번호 같은 거 코드에 직접 넣으면 완전 큰일나잖아요.
| 시크릿 종류 | 저장 위치 | 사용 방법 | 보안 수준 |
|---|---|---|---|
| Repository Secret | Settings → Secrets | ${{ secrets.NAME }} | ⭐⭐⭐⭐⭐ |
| Environment Secret | Settings → Environments | environment 지정 필요 | ⭐⭐⭐⭐⭐ |
| Organization Secret | Organization Settings | 여러 레포에서 공유 | ⭐⭐⭐⭐ |
| 환경변수 (공개) | 워크플로우 파일 | env: 섹션 | ⭐⭐ |
절대로 시크릿을 로그에 출력하지 마세요! GitHub Actions는 자동으로 마스킹해주긴 하는데요, 완벽하지 않아요. 특히 base64 인코딩된 값이나 JSON 형태는 그대로 노출될 수 있거든요. 제가 예전에 실수로 노출시켜서 진짜 식은땀 났었어요...
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to Staging
run: |
echo "API_URL=${{ secrets.API_URL }}"
echo "DB_HOST=${{ secrets.DB_HOST }}"
deploy-production:
runs-on: ubuntu-latest
environment: production
needs: deploy-staging
steps:
- name: Deploy to Production
run: |
echo "API_URL=${{ secrets.API_URL }}"
echo "DB_HOST=${{ secrets.DB_HOST }}"
Environment 기능을 쓰면 환경별로 다른 시크릿을 관리할 수 있어요. staging과 production DB가 다르잖아요? 이렇게 분리하면 실수로 운영 DB를 건드릴 일이 없어요.
? 배포 승인 프로세스 구축
운영 환경 배포는 자동으로 바로 되면 위험할 수 있잖아요. 그래서 승인 단계를 넣는 게 좋아요. 2026년 GitHub는 이 기능이 완전 강력해졌거든요!
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Wait for approval
run: echo "Deployment approved!"
- name: Deploy
run: ./deploy.sh
Environment 설정에서 Required reviewers를 지정하면 돼요. Settings → Environments → production → Required reviewers 에서 팀원들을 추가하면 자동으로 승인 요청이 가요.
- 최대 6명까지 리뷰어 지정 가능해요
- 승인 대기 시간을 30일까지 설정할 수 있어요
- 특정 브랜치만 배포 가능하도록 제한할 수 있어요
- Slack 알림과 연동하면 더 편해요
? 롤백 전략 및 실패 처리
배포가 실패하면 어떻게 할까요? 빠르게 이전 버전으로 돌아가는 게 최선이죠. 롤백 전략도 미리 준비해둬야 해요.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy New Version
id: deploy
run: ./deploy.sh
continue-on-error: true
- name: Health Check
id: health
run: |
sleep 30
curl -f https://myapp.com/health || exit 1
continue-on-error: true
- name: Rollback on Failure
if: steps.deploy.outcome == 'failure' || steps.health.outcome == 'failure'
run: |
echo "Deployment failed! Rolling back..."
docker pull myapp:previous
docker stop myapp
docker run -d --name myapp myapp:previous
- name: Notify Team
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '? 배포 실패! 자동 롤백 완료'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
여기서 핵심은 Health Check예요. 배포가 성공했어도 실제로 서비스가 정상 동작하는지 확인해야 하거든요. 30초 정도 기다린 후 헬스체크 엔드포인트를 호출해서 확인해요.
저는 배포 후 모니터링을 5분 정도 더 해요. 처음엔 정상이었다가 트래픽이 몰리면 문제가 생기는 경우가 있거든요. 그래서 배포 직후 5분간 에러율, 응답시간, CPU 사용률을 체크하고 임계값을 넘으면 자동으로 롤백하게 만들었어요. 이거 진짜 생명의 은인이에요!
? 배포 모니터링 및 알림 설정
배포가 끝났다고 끝이 아니에요. 제대로 배포됐는지, 문제는 없는지 모니터링해야죠. 알림 설정도 중요하고요!
| 알림 채널 | 장점 | 설정 난이도 | 추천도 |
|---|---|---|---|
| Slack | 실시간 알림, 팀 공유 쉬움 | ⭐⭐ (쉬움) | ⭐⭐⭐⭐⭐ |
| Discord | 무료, 커스터마이징 자유 | ⭐⭐ (쉬움) | ⭐⭐⭐⭐ |
| 이메일 | 공식 기록 남음 | ⭐ (매우 쉬움) | ⭐⭐⭐ |
| SMS | 긴급 상황 대응 빠름 | ⭐⭐⭐ (보통) | ⭐⭐⭐⭐ |
| Datadog/New Relic | 상세한 메트릭 추적 | ⭐⭐⭐⭐ (복잡) | ⭐⭐⭐⭐⭐ |
- name: Notify Deployment Success
uses: 8398a7/action-slack@v3
with:
status: custom
fields: repo,message,commit,author,action,eventName,ref,workflow
custom_payload: |
{
attachments: [{
color: 'good',
text: `✅ 배포 완료!
${process.env.AS_REPO}
Commit: ${process.env.AS_COMMIT}
By: ${process.env.AS_AUTHOR}`
}]
}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: success()
- name: Notify Deployment Failure
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '? 배포 실패! 즉시 확인 필요'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: failure()
솔직히 말하자면 알림이 너무 많이 오면 피로해져요. 그래서 저는 중요한 것만 Slack으로 보내고, 나머지는 대시보드에서 확인해요. 실패한 배포나 production 배포만 알림 받게 설정하는 게 좋더라고요!
⚡ GitHub Actions 고급 활용 팁
GitHub Actions로 CI/CD 파이프라인을 구축하는 기본은 다 익히셨죠? 근데요, 여기서 멈추면 너무 아까워요. 제가 2026년 현재까지 실무에서 써보면서 발견한 고급 팁들을 공유해드릴게요. 솔직히 처음에는 저도 몰랐던 기능들인데, 알고 나니까 완전 작업 효율이 달라지더라고요.
? 매트릭스 전략으로 한 번에 여러 환경 테스트하기
여러분, 혹시 Node.js 버전마다 일일이 워크플로우를 따로 만들어 보신 적 있으세요? 저도 예전에는 그렇게 했거든요. 근데 매트릭스 전략을 쓰면 진짜 간단해져요.
name: Matrix Build
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
include:
- os: ubuntu-latest
experimental: true
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test
이렇게 하면 3개 OS × 3개 Node 버전 = 총 9개 조합을 자동으로 테스트해줘요. 엄청 편하죠?
? 캐시 최적화로 빌드 시간 단축하기
제가 직접 써봤는데요, 캐시를 제대로 설정하면 빌드 시간이 정말 엄청 줄어들어요. 특히 2026년 현재 GitHub Actions의 캐시 용량이 10GB까지 늘어나면서 더 활용하기 좋아졌거든요.
| 캐시 종류 | 캐시 경로 | 예상 절감 시간 |
|---|---|---|
| npm 의존성 | ~/.npm | 60-80% |
| pip 패키지 | ~/.cache/pip | 50-70% |
| Gradle 빌드 | ~/.gradle/caches | 70-85% |
| Docker 레이어 | /var/lib/docker | 40-60% |
| Maven 저장소 | ~/.m2/repository | 55-75% |
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
.next/cache
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
사실은요, restore-keys를 여러 개 설정해두면 완전 일치하는 캐시가 없어도 부분적으로 복원할 수 있어요. 이게 진짜 꿀팁이에요.
? 재사용 가능한 워크플로우 만들기
2026년 현재 GitHub Actions의 가장 강력한 기능 중 하나가 바로 재사용 가능한 워크플로우예요. 여러 프로젝트에서 똑같은 CI/CD 로직을 쓴다면 이거 정말 필수거든요.
# .github/workflows/reusable-build.yml
name: Reusable Build
on:
workflow_call:
inputs:
node-version:
required: true
type: string
environment:
required: false
type: string
default: 'production'
secrets:
deploy-token:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build
- name: Deploy
run: npm run deploy
env:
TOKEN: ${{ secrets.deploy-token }}
ENV: ${{ inputs.environment }}
그리고 이걸 다른 워크플로우에서 이렇게 불러 쓰면 돼요.
name: Production Deploy
on:
push:
branches: [main]
jobs:
call-build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '20'
environment: 'production'
secrets:
deploy-token: ${{ secrets.PROD_TOKEN }}
? 환경별 시크릿 관리 전략
근데요, 개발/스테이징/프로덕션 환경을 나눠서 관리하는 게 진짜 중요하거든요. GitHub의 Environment 기능을 쓰면 환경별로 시크릿을 분리할 수 있어요.
| 환경 기능 | 설명 | 추천 사용처 |
|---|---|---|
| Protection Rules | 배포 전 승인 필요 | 프로덕션 환경 |
| Wait Timer | 배포 전 대기 시간 설정 | 스테이징 검증 후 배포 |
| Required Reviewers | 특정 팀원 승인 필수 | 중요 서비스 배포 |
| Deployment Branches | 특정 브랜치만 배포 허용 | 프로덕션 브랜치 제한 |
Environment 기능은 공개 저장소에서는 무료지만, 사설 저장소는 GitHub Pro 이상이 필요해요. 참고로 2026년 현재 기준이니까 꼭 확인해보세요!
? 동적 워크플로우 생성 기법
제가 최근에 발견한 꿀팁인데요, GitHub Actions의 표현식을 활용하면 동적으로 워크플로우를 생성할 수 있어요. 예를 들어 변경된 파일에 따라서 다른 작업을 실행하는 거죠.
name: Smart Build
on: [push, pull_request]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
docs:
- 'docs/**'
build-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Frontend 빌드 실행"
build-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Backend 빌드 실행"
이렇게 하면 변경된 부분만 빌드해서 시간도 절약하고 리소스도 아낄 수 있죠. 완전 효율적이에요.
? 워크플로우 성능 모니터링
아 그리고요, 2026년에는 GitHub Actions 대시보드가 엄청 좋아졌어요. 근데 더 자세한 분석이 필요하다면 직접 메트릭을 수집할 수도 있거든요.
| 측정 항목 | 정상 범위 | 최적화 필요 |
|---|---|---|
| 전체 빌드 시간 | 5분 이하 | 10분 이상 |
| 의존성 설치 시간 | 1분 이하 | 3분 이상 |
| 테스트 실행 시간 | 3분 이하 | 7분 이상 |
| 배포 시간 | 2분 이하 | 5분 이상 |
| 월간 사용 시간 | 2000분 이하 | 무제한 요금제 필요 |
워크플로우가 느리다면 먼저 각 스텝의 실행 시간을 확인해보세요. GitHub Actions 로그에서 각 단계별 시간을 볼 수 있거든요. 가장 오래 걸리는 부분부터 최적화하면 효과가 진짜 크답니다. 저는 이 방법으로 빌드 시간을 15분에서 5분으로 줄였어요!
? 보안 강화 체크리스트
마지막으로 정말 중요한 보안 팁들을 공유할게요. 2026년 들어서 GitHub Actions 관련 보안 사고가 좀 늘었거든요. 이것만 지키면 대부분의 위험은 피할 수 있어요.
- GITHUB_TOKEN 권한 최소화: 필요한 권한만 부여하세요. 기본값은 너무 많은 권한을 가지고 있어요.
- 외부 액션 버전 고정: uses: actions/checkout@v4 처럼 버전을 명시하는 게 안전해요.
- Pull Request 워크플로우 주의: pull_request_target 이벤트는 위험할 수 있으니 신중하게 쓰세요.
- 시크릿 로깅 방지: echo로 시크릿 출력하지 마세요. GitHub이 자동 마스킹하지만 완벽하진 않아요.
- 환경별 접근 제어: 프로덕션 환경은 특정 브랜치와 리뷰어로 제한하세요.
제가 실무에서 쓰는 보안 설정 템플릿을 공유할게요. 이걸 각 워크플로우 상단에 추가하면 기본적인 보안은 확보할 수 있어요:
permissions:
contents: read
pull-requests: write
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
여기까지가 제가 2026년 현재까지 GitHub Actions를 써오면서 터득한 고급 팁들이에요. 처음에는 좀 복잡해 보일 수 있는데요, 하나씩 적용해보시면 금방 익숙해지실 거예요. 저도 처음엔 기본 기능만 쓰다가 이런 기능들을 알게 되면서 작업 효율이 정말 많이 올랐거든요!
? 자주 발생하는 오류와 해결 방법
GitHub Actions로 CI/CD 파이프라인을 구축하다 보면 여러 가지 오류를 마주하게 되죠. 저도 처음에 정말 많이 헤맸거든요. 근데 대부분의 오류는 패턴이 있어요. 몇 가지만 알아두면 훨씬 수월하게 해결할 수 있어요.
권한 관련 오류 해결하기
가장 자주 마주치는 건 권한 문제예요. "Permission denied"나 "Forbidden" 같은 에러 메시지가 뜨면서 배포가 실패하는 거죠.
- GITHUB_TOKEN 권한: Settings → Actions → General에서 "Read and write permissions" 확인하세요
- Secrets 설정: 대소문자 정확히 일치하는지, 만료되지 않았는지 확인해야 해요
- SSH 키: 공개키가 제대로 등록되어 있는지 다시 한 번 체크하세요
- AWS 권한: IAM 정책에서 필요한 모든 액션이 허용되어 있는지 확인하세요
저는 처음에 AWS_SECRET_ACCESS_KEY를 잘못 복사해서 한 시간 동안 고생했거든요. 공백이나 줄바꿈이 들어가면 안 되는데, 조심해야 해요.
빌드 실패 트러블슈팅
빌드가 실패하면 정말 답답하죠. 로컬에서는 잘 되는데 GitHub Actions에서만 안 되는 경우가 많아요.
1. 환경 변수 누락
로컬에서는 .env 파일이 있지만, GitHub Actions에서는 Secrets로 설정해야 해요. 환경 변수를 제대로 전달했는지 확인하세요.
2. Node.js 버전 차이
로컬과 CI 환경의 Node 버전이 다르면 의존성 설치가 실패할 수 있어요. package.json에 engines 필드를 명시하는 게 좋아요.
3. 캐시 문제
node_modules 캐시가 꼬이면 이상한 오류가 나요. 워크플로우를 재실행할 때 캐시를 무시해보세요.
솔직히 말하자면, 빌드 로그를 꼼꼼히 읽는 게 제일 중요해요. 에러 메시지 첫 줄만 보고 판단하지 말고, 스택 트레이스 전체를 확인하세요.
테스트 타임아웃과 성능 문제
테스트가 너무 오래 걸리거나 타임아웃되는 경우도 정말 많아요. 특히 E2E 테스트를 돌릴 때 그런 경험 있으셨죠?
-
타임아웃 설정 조정
기본 타임아웃이 너무 짧으면 늘려주세요. timeout-minutes를 job이나 step 레벨에서 설정할 수 있어요. -
병렬 테스트 실행
Matrix strategy를 활용해서 여러 테스트를 동시에 돌리면 시간이 훨씬 단축돼요. -
의존성 캐싱 최적화
node_modules를 매번 새로 설치하면 느려요. actions/cache를 꼭 사용하세요. -
불필요한 step 제거
정말 필요한 작업만 실행하도록 조건문을 잘 활용하세요.
제 경험상 의존성 캐싱만 제대로 설정해도 빌드 시간이 절반으로 줄어들더라고요. 2026년 현재 actions/cache@v4가 나오면서 캐싱 성능이 더 좋아졌어요.
Docker 이미지 빌드 문제
Docker 이미지를 빌드하다가 막히는 경우도 있어요. "Cannot connect to the Docker daemon"이나 "No space left on device" 같은 오류죠.
| 오류 메시지 | 원인 | 해결 방법 |
|---|---|---|
| Docker daemon 연결 실패 | Docker service가 실행되지 않음 | runs-on: ubuntu-latest 사용 확인 |
| 디스크 공간 부족 | 빌드 캐시 및 이미지 누적 | docker system prune -af 실행 |
| 레이어 캐시 미스 | Dockerfile 작성 순서 문제 | 자주 변경되는 명령어를 뒤로 배치 |
| Multi-arch 빌드 실패 | QEMU 설정 누락 | docker/setup-qemu-action 추가 |
근데요, Docker 빌드는 정말 시간이 오래 걸려요. docker/build-push-action@v5를 사용하면 레이어 캐싱이 자동으로 되니까 훨씬 빨라져요.
배포 실패 디버깅 전략
빌드는 성공했는데 배포에서 실패하면 정말 속상하죠. 이럴 때는 단계별로 접근해야 해요.
- SSH 연결 테스트: 먼저 수동으로 서버에 접속이 되는지 확인하세요
- 파일 경로 확인: 배포 스크립트의 절대 경로가 맞는지 체크해야 해요
- 환경별 설정: dev, staging, production 환경 설정을 제대로 분리했는지 확인하세요
- 롤백 준비: 실패했을 때 이전 버전으로 돌아갈 수 있도록 백업해두세요
저는 배포 실패할 때마다 Slack으로 알림을 받도록 설정해뒀어요. 그래야 빨리 대응할 수 있거든요.
Secrets와 환경 변수 관리 오류
가장 헷갈리는 부분 중 하나가 바로 Secrets 관리예요. 환경 변수가 제대로 전달되지 않으면 애플리케이션이 제대로 동작하지 않죠.
# ❌ 잘못된 사용
echo ${{ secrets.API_KEY }} # 보안상 절대 출력하면 안 돼요!
# ✅ 올바른 사용
- name: Set environment variables
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "환경 변수가 설정되었습니다"
# 실제 값은 출력하지 않기
# ✅ 파일로 저장하기
- name: Create env file
run: |
echo "API_KEY=${{ secrets.API_KEY }}" >> .env
echo "DB_URL=${{ secrets.DATABASE_URL }}" >> .env
참고로 Secrets는 한 번 저장하면 다시 볼 수 없어요. 그래서 잘못 입력했는지 확인하기가 어렵죠. 이럴 때는 새로 만들어서 다시 시도하는 게 빨라요.
워크플로우 재사용 시 주의사항
2026년 현재는 reusable workflows를 많이 사용하는데요, 여기서도 함정이 있어요.
- Secrets 전달 문제: 재사용 가능한 워크플로우에 secrets를 전달할 때는 inherit 키워드를 사용하거나 명시적으로 전달해야 해요
- 권한 상속: 호출하는 워크플로우의 권한이 제대로 전달되지 않을 수 있어요. permissions를 명시적으로 설정하세요
- 컨텍스트 변수: github.event 같은 컨텍스트 변수가 재사용 워크플로우에서 다르게 동작할 수 있어요
- 경로 참조: 상대 경로가 꼬일 수 있으니 절대 경로나 $GITHUB_WORKSPACE를 사용하세요
솔직히 처음엔 이것 때문에 엄청 헤맸어요. 근데 한 번 제대로 설정해두면 정말 편하더라고요.
문제를 찾기 어려울 때는 디버그 모드를 켜보세요. 리포지토리 Settings → Secrets에서 다음 변수를 추가하면 돼요:
- ACTIONS_RUNNER_DEBUG: true로 설정하면 러너의 상세 로그가 나와요
- ACTIONS_STEP_DEBUG: true로 설정하면 각 step의 디버그 정보가 출력돼요
진짜 자세한 정보가 나오니까 문제 파악하기 훨씬 쉬워져요. 다만 로그가 엄청 길어지니까 주의하세요!
마지막으로 한 가지 더 알려드릴게요. GitHub Actions 워크플로우가 실패하면 재실행하기 전에 꼭 로그를 저장해두세요. 재실행하면 이전 로그가 사라질 수 있거든요. 저는 실수로 중요한 에러 메시지를 날려버린 적이 있어요. 정말 속상했죠.
❓ 자주 묻는 질문
퍼블릭 레포지토리는 무제한 무료라 걱정 없어요. 프라이빗 레포지토리는 2026년 기준 월 2,000분이 무료인데요. 이걸 넘으면 자동으로 워크플로우가 멈춰요. Settings > Billing에서 사용량 확인할 수 있고요. Self-hosted runner를 쓰거나, 워크플로우를 최적화해서 실행 시간을 줄이면 해결돼요. 저는 캐싱 잘 활용해서 빌드 시간 절반으로 줄였거든요!
GitHub Secrets 기능을 무조건 쓰세요! Settings > Secrets and variables > Actions에서 등록하면 돼요. API 키, 데이터베이스 비밀번호, AWS 자격증명 같은 거 절대 코드에 하드코딩하면 안 돼요. 워크플로우에서 ${{ secrets.API_KEY }} 형태로 사용하고요. Environment secrets로 개발/프로덕션 환경별로 따로 관리할 수도 있어요. 로그에도 자동으로 마스킹되니까 안전하죠.
프로젝트 규모에 따라 다른데요. 소규모~중규모 팀이면 GitHub Actions가 완전 편해요. 설정 쉽고, 별도 서버 관리 안 해도 되고, GitHub와 통합도 완벽하거든요. 근데 대규모 엔터프라이즈에서 복잡한 파이프라인이 수십 개 있거나, 온프레미스 환경이 필수라면 Jenkins가 나을 수 있어요. 저희 팀은 GitHub Actions로 마이그레이션하고 유지보수 시간 70% 줄었어요. 2026년엔 GitHub Actions가 더 강력해졌죠!
여러 방법이 있어요! 기본적으로 GitHub 이메일 알림이 오는데요. 슬랙 연동하면 훨씬 편해요. slack-send 액션 쓰거나 Webhook 설정하면 실시간으로 팀 채널에 알림 떨어지거든요. 디스코드, 텔레그램 연동도 가능하고요. if: failure() 조건으로 실패 시에만 알림 보내게 설정하세요. 저는 슬랙에 성공/실패 모두 보내도록 해놨는데, 배포 상태 한눈에 보여서 좋아요!
당연히 가능하죠! on: push: branches에서 브랜치별로 지정하면 돼요. main 브랜치는 프로덕션 배포, develop은 스테이징 배포 이런 식으로요. 브랜치 패턴도 쓸 수 있어요. feature/**는 테스트만 돌리고, release/**는 풀 파이프라인 실행하는 거죠. Environment 기능 쓰면 브랜치별로 다른 secrets도 적용할 수 있어요. 실무에서 진짜 유용해요. 개발/스테이징/프로덕션 환경 완전 분리 가능하거든요!
완전 가능해요! paths 필터로 변경된 파일 경로 감지해서 필요한 워크플로우만 실행하면 되거든요. frontend/** 파일 바뀌면 프론트엔드만, backend/** 바뀌면 백엔드만 빌드하는 식이죠. dorny/paths-filter 액션 쓰면 더 편하고요. 매트릭스 전략으로 여러 패키지 병렬 빌드도 가능해요. 모노레포는 처음 설정이 좀 복잡한데, 한번 해놓으면 정말 효율적이에요. 변경된 부분만 빌드되니까 시간 엄청 절약돼요!
✨ 마무리하며
여기까지 2026년 GitHub Actions로 CI/CD 파이프라인 구축하는 방법 전부 알려드렸어요. 처음엔 복잡해 보일 수 있는데, 하나씩 따라 해보면 생각보다 쉬워요. 기본 워크플로우부터 시작해서, 테스트 자동화하고, 캐싱으로 속도 올리고, 마지막엔 자동 배포까지. 이 과정 다 거치고 나면 정말 개발이 편해지거든요.
저도 처음엔 수동으로 다 했었는데, GitHub Actions 도입하고 나서 진짜 삶의 질이 달라졌어요. 코드 푸시하면 알아서 테스트하고 배포까지 되니까, 저는 개발에만 집중할 수 있게 됐죠. 여러분도 한번 도전해보세요. 막히는 부분 있으면 댓글로 물어보시고요. 같이 더 나은 개발 환경 만들어가요!
CI/CD 파이프라인 구축, 생각만 하지 말고 오늘 바로 시작해보세요. 도움 됐으면 좋겠네요! ?
댓글 0개
첫 번째 댓글을 남겨보세요!