지난 글에서 push하면 자동으로 배포되는 파이프라인을 완성했다. 잘 돌아간다. 그런데 실무에 쓰기엔 두 가지가 걸린다.
첫째, 배포할 곳이 하나뿐이다. 실무에선 보통 개발 서버(dev)와 운영 서버(prod)를 따로 둔다. 테스트는 개발 서버에서 해보고, 검증되면 운영에 올린다. 지금은 main에 push하면 무조건 그 서버 하나로 나간다.
둘째, 태그가 latest 하나뿐이다. 이러면 지금 서버에 떠 있는 게 어떤 코드인지 알 수 없다. 배포했는데 문제가 생겨서 되돌리고 싶어도, 이전 버전 이미지가 남아있지 않다.
오늘은 이 두 가지를 해결해서 파이프라인을 실무 수준으로 다듬는다.
1. 브랜치별로 다른 환경에 배포하기
가장 흔한 구성은 이렇다.
| 브랜치 | 배포 대상 | 용도 |
|---|---|---|
develop |
개발 서버 | 기능 테스트, 내부 확인 |
main |
운영 서버 | 실제 서비스 |
develop에 push하면 개발 서버로, main에 push하면 운영 서버로 나가게 만드는 거다.
방법: 조건문(if)으로 분기
GitHub Actions는 스텝이나 잡에 if 조건을 걸 수 있다. 여기에 "지금 어떤 브랜치인지"를 물어보면 된다.
on:
push:
branches: [ main, develop ] # 두 브랜치 모두 감시
jobs:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... (빌드 & push 스텝은 동일) ...
- name: 개발 서버 배포
if: github.ref == 'refs/heads/develop'
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEV_SERVER_HOST }}
username: ${{ secrets.DEV_SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:develop
# ... 재시작 명령 ...
- name: 운영 서버 배포
if: github.ref == 'refs/heads/main'
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SERVER_HOST }}
username: ${{ secrets.PROD_SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:latest
# ... 재시작 명령 ...
github.ref가 현재 브랜치 정보를 담고 있다. refs/heads/develop 형태로 들어온다. 그래서 if: github.ref == 'refs/heads/develop'은 "지금 develop 브랜치일 때만 이 스텝 실행"이 된다.
시크릿도 서버별로 따로 등록해둔다. DEV_SERVER_HOST, PROD_SERVER_HOST 이런 식으로.
더 깔끔한 방법: Environments 사용
GitHub에는 Environments라는 기능이 있다. 저장소 Settings → Environments에서 development, production 같은 환경을 만들고, 환경별로 시크릿을 따로 등록할 수 있다.
jobs:
deploy-prod:
if: github.ref == 'refs/heads/main'
environment: production # 이 환경의 시크릿을 사용
runs-on: ubuntu-latest
steps:
# secrets.SERVER_HOST 하나로 쓰면
# production 환경에 등록된 값이 자동으로 들어온다
이러면 시크릿 이름을 DEV_, PROD_로 나눌 필요 없이 같은 이름을 쓰고, 환경만 바꿔 끼우면 된다. 훨씬 깔끔하다.
보너스로 승인 기능도 붙는다. Environments에는 "이 환경으로 배포하려면 지정된 사람이 승인해야 함"을 설정할 수 있다. 운영 배포에 이걸 걸어두면, 자동으로 다 준비되고 마지막 승인 버튼만 사람이 누르게 된다. 첫 CI/CD 글에서 말한 Continuous Delivery가 바로 이 형태다.
2. 이미지 태그 전략 — latest만 쓰면 안 되는 이유
지난 글에서 이미지 태그를 latest 하나로만 썼다. 편하지만 실무에선 위험하다.
문제 1: 지금 뭐가 떠 있는지 모른다. 서버에 latest가 떠 있다는 건 알겠는데, 그게 어제 코드인지 오늘 코드인지 알 수 없다.
문제 2: 롤백이 안 된다. 배포했는데 버그가 터졌다. 이전 버전으로 되돌리고 싶다. 근데 latest는 계속 덮어써져서 이전 이미지가 남아있지 않다. 되돌릴 수가 없다.
해결: 커밋 해시나 버전으로 태그 달기
가장 실용적인 방법은 커밋 SHA를 태그로 쓰는 거다. 커밋마다 고유하니까 이미지와 코드가 1:1로 대응된다.
- name: 이미지 빌드 & push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
tags에 여러 줄을 주면 같은 이미지에 태그를 여러 개 달 수 있다. 여기서는 두 개를 단다.
:abc1234...(커밋 SHA) — 고유 식별용. 이건 절대 덮어써지지 않는다.:latest— 편의용. 항상 최신을 가리킨다.
이러면 이전 커밋의 이미지가 레지스트리에 그대로 남아있으니, 문제가 생겼을 때 그 태그로 되돌리면 된다.
# 롤백: 이전 커밋 이미지로 다시 띄우기
docker pull ghcr.io/user/repo:이전커밋SHA
docker stop my-app && docker rm my-app
docker run -d --name my-app ... ghcr.io/user/repo:이전커밋SHA
릴리스는 버전 태그로
정식 릴리스가 있는 프로젝트라면 Git 태그(v1.2.0)를 이미지 태그로 쓰는 것도 좋다.
on:
push:
tags: [ 'v*' ] # v로 시작하는 태그가 push될 때
git tag v1.2.0 && git push --tags 하면 그 버전으로 이미지가 만들어지고 배포된다. 버전 관리가 명확해진다.
태그 전략 정리
| 태그 | 언제 쓰나 | 특징 |
|---|---|---|
latest |
편의용 | 계속 덮어써짐. 이것만 쓰면 위험 |
| 커밋 SHA | 모든 빌드 | 고유함. 롤백의 핵심 |
develop |
개발 브랜치 | 개발 서버가 바라보는 태그 |
v1.2.0 |
정식 릴리스 | 사람이 읽기 좋은 버전 |
실무에서는 보통 커밋 SHA + 브랜치명(또는 latest) 조합으로 시작하면 충분하다.
정리
- 브랜치별 환경 분리:
if: github.ref == '...'로 develop은 개발 서버, main은 운영 서버로 나눈다. - Environments를 쓰면 환경별 시크릿을 깔끔하게 관리하고, 운영 배포에 승인 단계도 걸 수 있다.
latest만 쓰면 롤백이 불가능하다. 커밋 SHA 태그를 같이 달아서 이전 버전을 남겨두자.- 태그는 여러 개를 동시에 달 수 있다. (
SHA+latest)
이 두 가지만 적용해도 파이프라인이 훨씬 실무다워진다. "개발 서버에서 먼저 확인하고, 운영은 승인받고 배포하고, 문제 생기면 이전 커밋으로 되돌린다"는 흐름이 완성되는 거다.
이걸로 CI/CD 계열을 마무리한다. 개념부터 시작해서 자동 테스트, 자동 배포, 환경 분리와 롤백까지 왔다. 다음부터는 지난 글에서 예고했던 쿠버네티스 이야기로 넘어간다. 컨테이너가 수십 개로 늘어나고, 무중단 배포가 필요해질 때 등장하는 물건이다.
'프로그래밍 > DevOps' 카테고리의 다른 글
| [DevOps] 쿠버네티스 핵심 3개 — Pod, Deployment, Service 이해하기 (0) | 2026.07.29 |
|---|---|
| [DevOps] 쿠버네티스가 왜 필요한데? Docker Compose로는 부족한 순간 (0) | 2026.07.27 |
| [DevOps] GitHub Actions로 진짜 배포까지 — Docker 이미지 빌드하고 서버에 자동 배포하기 (0) | 2026.07.17 |
| [DevOps] GitHub Actions 첫걸음 — 푸시하면 자동으로 테스트 돌리기 (0) | 2026.07.17 |
| [DevOps] CI/CD가 대체 뭔데? 자동화 배포의 첫걸음 (0) | 2026.07.17 |