"docker: Error response from daemon: Conflict. The container name '/my-app' is already in use..."
docker run 명령어는 이미 수십 줄을 넘겼고, 오타 하나라도 날까 봐 손을 떨며 배포 로그를 지켜보진 않으셨나요?만약 여러분이 배포할 때마다 기도하는 마음으로 모니터를 뚫어지게 쳐다보고 있다면, 이 글이 그 '배포 지옥'에서 탈출하는 이정표가 될 것입니다.
1. Problem: 우리는 서버를 '간호'하고 있다
독자가 경험할 수 있는 최악의 시나리오: "디스크 풀(Disk Full)의 습격"
배포 스크립트를 이렇게 짰다고 가정해 봅시다.
docker stop my-app
docker rm my-app
docker pull my-registry/my-app:latest
docker run -d --name my-app my-registry/my-app:latest
docker pull을 할 때마다 낡은 이미지들이 서버 구석에 쌓여 **디스크 용량을 100% 점유 **해버리기 때문입니다. 뒤늦게 서버에 접속해 docker image prune을 입력하지만, 이미 서비스는 중단된 상태죠.이처럼 기존 서버를 계속 '수리'해서 쓰는 방식은 **리소스 충돌 **과 **잔여물 문제 **라는 시한폭탄을 안고 가는 것과 같습니다.
2. '눈사람 서버(Snowflake Server)'의 공포
지금은 개발(Dev) 서버가 한 대뿐이라 직접 들어가서 고치면 된다고 생각할 수 있습니다. 하지만 실제 운영(Prod) 환경에서 사용자가 몰려 서버를 3대, 10대로 늘려야 한다면 어떨까요?
"어제 내가 서버 1번에 무슨 설정을 했더라?"
- "서버 1번은 Java 17인데, 2번은 왜 11이지?" (런타임 버전 불일치)
- "DB 커넥션 정보가 1번 서버에만 구식으로 남아있네?" (설정 값 동기화 실패)
- "왜 1번 서버 로그만 시간이 9시간 느리지?" (OS 타임존 설정 불일치)
- "분명 같은 코드인데 이미지 처리 라이브러리 버전이 달라서 사진 업로드가 안 되네?" (OS 패키지 의존성 차이)
서버마다 환경이 미세하게 달라지면 배포 결과도 예측 불가능해집니다. 똑같은 코드를 배포했는데 1번 서버는 살고 2번 서버는 죽는 기현상이 발생하죠. 결국 인프라가 개발자의 발목을 잡는 '기술 부채'가 되어, 배포가 공포로 다가오게 됩니다.
3. Solution: 서버는 애완동물이 아니라 가축이다 (Pets vs Cattle)
"서버를 고쳐 쓰지 말고, 새 제품으로 교체하자"는 발상입니다. 예전에는 물리 장비가 비싸서 불가능했지만, 이제는 클릭 몇 번으로 서버를 생성하는 시대입니다.
① 인스턴스 교체 전략 (Blue-Green 배포)
배포할 때 기존 EC2를 건드리지 마세요. 아예 새로운 EC2(버전 B)를 하나 더 띄웁니다.
- 신규 생성: 새 서버에 최신 코드를 배포합니다.
- 검증: 로드밸런서 뒤에서 새 서버가 정상인지 테스트합니다.
- 교체: 트래픽을 새 서버로 돌리고, 낡은 서버(버전 A)는 가차 없이 삭제(Terminate) 합니다.
- 이름 중복이나 포트 충돌, 찌꺼기 파일 고민이 단칼에 해결됩니다.
② 설정의 분리 (Cloud-Native Config)
docker run -e ... 환경 변수 나열에서 탈출하세요.- Bad: 쉘 스크립트에 하드코딩된 API 키와 DB 비밀번호
- Good: **AWS Parameter Store **나 Secrets Manager 사용
서버가 실행될 때 중앙 저장소에서 설정값을 읽어오게 하세요. 배포 스크립트는 깨끗해지고, 설정 변경 시 코드를 다시 빌드할 필요도 없어집니다.
③ 관리형 서비스로의 전환 (ECS, Fargate)
직접 EC2를 부수고 짓는 게 번거롭다면, AWS가 대신 해주는 서비스를 활용하세요.
- AWS ECS: "이 이미지를 2개 띄워줘"라고 선언만 하면 됩니다. ECS가 알아서 남는 서버를 찾고, 낡은 컨테이너는 우아하게 종료시킵니다.
- AWS Fargate: 아예 서버(EC2) 자체를 관리할 필요가 없습니다. 컨테이너만 던져주면 인프라는 AWS가 책임집니다.
4. 실전 예제: 무중단 배포를 위한 첫걸음
지금 당장 거창한 쿠버네티스를 도입할 필요는 없습니다. 작은 변화부터 시작해 보세요.
# EC2 내부에서 실행되는 위험한 스크립트
docker stop app || true
docker rm app || true
docker run -d --name app -e DB_URL=... -e API_KEY=... my-image
- **GitHub Actions **에서 새 이미지를 빌드하고 ECR에 푸시합니다.
- **AWS ECS **를 통해 "새 태그로 업데이트" 명령을 보냅니다.
- ECS가 자동으로 새 컨테이너를 띄우고, 헬스 체크가 성공하면 기존 컨테이너를 내립니다.
5. 마치며: 개발자는 코드에 집중해야 합니다
인프라 고민에 쓸 시간을 비즈니스 로직 한 줄 더 짜는 데 쓰세요. 서버에 이름을 붙여주고 아끼지 마세요. 언제든 버리고 새로 만들 수 있는 환경을 구축했을 때, 여러분의 서비스는 비로소 '확장 가능(Scalable)'해집니다.
지금 여러분의 서버는 안녕한가요? 혹시 오늘도 죽어가는 서버에 인공호흡을 하고 계시진 않나요? 이제는 그 서버를 보내줄 때입니다.