1. 글의 목적
이전글에 이어 blue-green 배포에 대해서 공부하고 실습하고 정리하고 있었다. 글이 길어져서 2편으로 나누게 되었는데 이번 편에서는 blue-green 버전 나누기, 롤백, 실행 자동화를 실습하고 정리해보려고 한다.
2. 전체 아키텍처 한 장 요약
[ GitHub Actions ]
├─ 1. 코드 변경 감지
├─ 2. Docker 이미지 빌드
├─ 3. Docker Hub에 :deploy 푸시
└─ 4. 서버에 "배포 시작" 신호 (ssh)
↓
[ Server ]
├─ 현재 Active 색상 판별
├─ 반대편(Target) 결정
├─ Target 컨테이너만 업데이트
├─ 헬스체크
└─ 성공 시 Nginx 전환 (실패 시 롤백)
(made by chatGPT)
3. 서버에서 준비하기
이전에 켜둔 docker 컨테이너들을 docker compose down으로 모두 종료하자.
다음 파일들을 /usr/local/bin/ 경로에 만들자. 파일을 만들때 sudo nano /usr/local/bin/<file_name> 을 이용해 만들어 주자. sudo가 필수다!
sudo chmod +x <file_name>를 실행해서 실행 권한을 주는 것도 잊지 말자!
먼저 다음과 같이 nginx 설정 파일을 준비하자.
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
/etc/nginx/sites-available/blue.conf 에 저장하자.
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:3002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
/etc/nginx/sites-available/green.conf 에 저장하자.
sudo rm -f /etc/nginx/sites-enabled/*
sudo ln -s /etc/nginx/sites-available/blue.conf \
/etc/nginx/sites-enabled/active.conf
sudo nginx -t && sudo systemctl reload nginx
이것을 통해 직접 설정파일을 수정핮 않고 심볼릭링크만 수정하여 blue-green간 스위치가 가능하다.
① detect-active.sh
역할
현재 사용자가 보고 있는 색상(Active)을 판별
하는 일
- Nginx 설정 파일을 읽음
- proxy_pass가 가리키는 포트 확인
- 결과: blue 또는 green
왜 필요한가?
- 사람 기억 ❌
- 자동화 판단의 출발점
#!/bin/bash
set -e
ACTIVE_LINK=$(readlink /etc/nginx/sites-enabled/active.conf)
case "$ACTIVE_LINK" in
*blue.conf)
echo "blue"
;;
*green.conf)
echo "green"
;;
*)
echo "unknown"
exit 1
;;
esac
② get-target.sh
역할
이번 배포 대상(Target)을 결정
하는 일
- detect-active.sh 결과를 뒤집음
- Active가 blue → Target은 green
- Active가 green → Target은 blue
왜 필요한가?
- 운영 중인 컨테이너 보호
- “반대편에만 배포” 보장
#!/bin/bash
ACTIVE=$(/usr/local/bin/detect-active.sh)
if [ "$ACTIVE" = "blue" ]; then
echo "green"
elif [ "$ACTIVE" = "green" ]; then
echo "blue"
else
echo "error"
exit 1
fi
③ deploy-target.sh
역할
반대편(Target) 컨테이너만 새 버전으로 교체
하는 일
- Docker Hub에서 :deploy 이미지 pull
- 이를 :blue 또는 :green으로 tag
- docker-compose로 해당 서비스만 재시작
왜 필요한가?
- GitHub Actions는 색상 모름
- 서버에서 색상에 맞게 “옮겨 심기”
#!/bin/bash
set -e
TARGET=$(/usr/local/bin/get-target.sh)
echo "Deploying to $TARGET"
docker pull <your_id>/blue-green-test:deploy # <your_id> 를 자신의 docker id로 바꾸세요.
docker tag <your_id>/blue-green-test:deploy <your_id>/blue-green-test:$TARGET
docker compose up -d $TARGET
④ health-check.sh
역할
배포된 Target이 정상인지 확인
하는 일
- Target 포트로 HTTP 요청
- 200 OK 아니면 실패 처리
왜 필요한가?
- “배포 성공” ≠ “서비스 정상”
- 전환 전 마지막 안전장치
#!/bin/bash
TARGET=$(/usr/local/bin/get-target.sh)
if [ "$TARGET" = "blue" ]; then
PORT=3001
else
PORT=3002
fi
echo "Health check on port $PORT..."
for i in {1..10}; do
if curl -f http://localhost:$PORT/health > /dev/null; then
echo "Health check passed"
exit 0
fi
echo "Waiting for app... ($i)"
sleep 1
done
echo "Health check failed"
exit 1
⑤ switch.sh
역할
Nginx 트래픽을 Target으로 전환
하는 일
- proxy_pass 포트를 Target 쪽으로 변경
- nginx -t
- reload
왜 필요한가?
- 무중단의 핵심
- 컨테이너 재시작 ❌
#!/bin/bash
set -e
TARGET=$(/usr/local/bin/get-target.sh)
if [ "$TARGET" != "blue" ] && [ "$TARGET" != "green" ]; then
echo "Usage: switch.sh blue|green"
exit 1
fi
echo "Switching traffic to $TARGET..."
sudo ln -sf /etc/nginx/sites-available/$TARGET.conf \
/etc/nginx/sites-enabled/active.conf
sudo nginx -t
sudo systemctl reload nginx
echo "Traffic switched to $TARGET"
⑥ blue-green-deploy.sh (총괄 스크립트)
역할
전체 Blue-Green 배포 흐름을 한 번에 실행
실행 순서
특징
- 하나라도 실패하면 즉시 중단
- Active 쪽은 절대 건드리지 않음
#!/bin/bash
set -e
ACTIVE=$(/usr/local/bin/detect-active.sh)
TARGET=$(/usr/local/bin/get-target.sh)
/usr/local/bin/deploy-target.sh
docker compose up -d $TARGET
if /usr/local/bin/health-check.sh $TARGET; then
/usr/local/bin/switch.sh $TARGET
echo "Deployment succeeded"
else
echo "Health check failed. Rolling back."
docker compose stop $TARGET
exit 1
fi
4. GitHub Actions가 하는 일 & 코드
- 코드 변경 감지 (push)
- Docker 이미지 빌드
- Docker Hub에 항상 동일한 태그 :deploy로 푸시
- 서버에 SSH로 접속해 blue-green-deploy.sh 실행
name: Blue-Green Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push deploy image
run: |
docker build -t your-id/blue-green-test:deploy .
docker push your-id/blue-green-test:deploy
- name: Trigger deploy on server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /srv/blue-green
/usr/local/bin/blue-green-deploy.sh
5. docker-compose.yml 수정
services:
blue:
image: <your_id>/blue-green-test:blue # 본인의 Docker ID로 수정하세요
ports:
- "3001:3000"
environment:
- COLOR=blue
green:
image: <your_id>/blue-green-test:green # 본인의 Docker ID로 수정하세요
ports: - "3002:3000"
environment:
- COLOR=green
6. Docker Hub 태그의 역할 정리
| 태그 | 의미 | 업데이트 시점 |
| :deploy | 이번 배포 후보 (항상 최신) | GitHub Actions 빌드 직후 |
| :blue | 이전 안정 버전 | 서버 배포 시점 (deploy-target.sh) |
| :green | 현재 운영 or 대기 버전 | 서버 배포 시점 (deploy-target.sh) |
7. 직접 실행
// app.js
const express = require("express");
const app = express();
const COLOR = process.env.COLOR || "unknown";
app.get("/", (req, res) => {
res.send(`Hello from ${COLOR} v3!`);
});
app.get("/health", (req, res) => {
res.send("OK");
});
app.listen(3000, () => {
console.log(`Server running on ${COLOR}`);
});
앱의 코드를 다음과 같이 바꾸고 깃허브에 push 하자. 이전코드에서는 /health 라우터가 없었는데 꼭 넣어야 한다!!
깃허브 액션이 실행되고

다음과 같이 Blue-Green deployment completed. 가 뜬다면 성공이다. 이전과 같이 브라우저에 http://<server_ip>에 접속해본다면 Hello from green v3! 가 뜨면 잘 된 것이다. (이전에 3002포트가 nginx에 되어 있었다면 blue가 나올 것이다.)
8. 의도적인 롤백 발생시키기

의도하지는 않았지만 blue-green-deploy.sh를 수정중 실수로 인해 에러가 발생하였다.
위 로그를 보면 health-check 실패 시 switch.sh로 넘어가지 않고 즉시 docker compose stop을 실행하여 잘못된 컨테이너를 정리하는 것을 볼 수 있다. 이 덕분에 트래픽은 여전히 안전한 'Green' 환경에 머물러 있게 된다.
9. 신버전에 버그 등으로 인해 이전 버전으로 돌려야 한다면?
sudo /usr/local/bin/switch.sh
이 코드로 트래픽을 이전버전으로 돌린후 코드를 수정하고 다시 올리면 끝!
10. 마무리
막연하게만 느껴졌던 무중단 배포를 내 손으로 직접 구현해 보면서, CI/CD가 단순히 코드를 옮기는 것이 아니라 안전하게 서비스를 지속하는 기술이라는 것을 깨달았다.
물론 이번 실습은 단일 서버 안에서 이루어졌지만 실제 운영 환경에서는 여러 대의 서버나 쿠버네티스(K8s) 환경에서 더 복잡하게 돌아갈 것 같다고 생각했다. 하지만 그 핵심 원리는 오늘 배운 Blue-Green과 크게 다르지 않다는 자신감을 얻을 수 있었습니다.
에러 메시지를 보며 당황했던 시간만큼 제 실력도 한 뼘 자란 것 같아 뿌듯하네요. 다음에는 이 배포 과정을 더욱 고도화하거나 오류 발생시 외부로 report 그리고 다른 배포 전략(Canary 등)에 대해서도 공부해 보고 싶네요. 긴 글 읽어주셔서 감사합니다!
전체 코드 레포는 여기서 볼 수 있습니다: https://github.com/hafskjfha/Blue-Green-study (nginx/ 부분의 코드는 사용을 하지 않습니다.)
'프로그래밍 > 개발 일지' 카테고리의 다른 글
| 그때 나는 몰랐다 — EP.00 프롤로그 (0) | 2026.06.01 |
|---|---|
| [MS OAuth + Xbox Live] Xbox OAuth 인증 구현하기 (0) | 2026.03.14 |
| [CD/CI] blue-green 배포 공부해보기 1 (0) | 2026.01.13 |
| [Electron + Vite] 환경 설정과 트러블슈팅 가이드 (7) | 2025.08.11 |
| [Next.js App Router] lucide-react ESM 문제 해결하며 Jest 테스트 도입기 (1) | 2025.06.18 |