기획서보다 강력한 한마디, “일단 한번 보여주세요”
공공기관 SI 현장에서 홀로 개발을 도맡게 된 나에게 가장 큰 벽은 기술이 아니었다. 바로 ‘불확실성’이었다. 치밀한 요구사항 명세서에 따라 설계가 이루어지는 이상적인 환경은 없었다. 개발 지식이 부족한 담당자들은 화면을 직접 보기 전까지는 본인이 무엇을 원하는지조차 모르는 경우가 많았다.
“음, 그림으로 보니까 잘 모르겠는데… 실제로 눌러볼 순 없나요?”
나는 기능을 하나 만들 때마다 로컬(내 컴퓨터)에서 실행하고, 화면을 캡처하고, 빨간 펜으로 주석을 단 ‘보고서 파일(HWP/PPT)’을 매번 만들어 메일로 보냈다. 하지만 정적인 사진만으로는 담당자를 만족시킬 수 없었다.
“아, 아까 그 기능이 더 나은 것 같아요. 다시 되돌려주세요.” “이 버튼 누르면 어떻게 되나요? 영상으로 찍어주세요.”
어제는 A가 좋다더니 오늘은 B를 찾는다. 로컬에서 코드를 수정하고, 다시 캡처하고, 보고서를 쓰는 무한 루프. 개발 시간보다 보고서 쓰는 시간이 더 길어지는 주객전도의 상황이었다.
나는 절실하게 깨달았다. ‘내 컴퓨터 화면을 캡처해서 보내는 게 아니라, 담당자가 직접 들어와서 마음껏 눌러보고 깨트려볼 수 있는 놀이터(개발 서버)가 필요하다.’

기회는 우연히 찾아온다: 버려진 서버의 재발견
마침 회사 분리 과정에서 운 좋게도 우리 팀에 할당된 유휴 서버(빈 컴퓨터)가 한 대 생겼다. 사양은 좋지 않았지만, 리눅스(Linux)가 깔려 있고 공인 IP가 연결된 소중한 자원이었다.
“팀장님, 이 남는 서버 제가 써도 될까요? 담당자 확인용 서버로 구축하고 싶습니다.”
허가는 떨어졌다. 나는 이 빈 깡통 서버에 지금까지 배웠던 모든 기술을 쏟아부어 ‘나만의 작은 인프라 제국’을 건설하기 시작했다. 이것은 지난 몇 달간의 삽질(Works on My Machine & The Pipeline 시리즈)을 총정리하는 작업이었다.
- GitLab (코드 저장소): 사설 저장소를 설치해 코드를 안전하게 관리한다.
- Docker Registry (이미지 저장소): 빌드된 도커 이미지를 저장할 우리만의 창고를 만든다.
- Portainer (컨테이너 관리): CLI가 무서운 나를 위해 도커를 GUI로 관리할 수 있게 한다.
- Nginx (웹 서버): 프론트엔드와 백엔드 요청을 교통정리 해주는 문지기를 세운다.

해결책: 환경의 완전한 분리 (Dev vs Prod)
이제 나에게는 두 개의 무대(Server)가 생겼다. 나는 담당자의 변덕을 수용하면서도 운영 서버를 지키기 위해 배포 전략을 분리했다.
- 개발 서버 (Development): ‘자유로운 실험실’
- 담당자가 “그거 됐나요?” 물어볼 틈도 주지 않는다.
develop브랜치에 코드가 올라오면 파이프라인이 즉시 돌아서 자동 배포된다.- 담당자는 언제든 개발 서버 URL로 접속해서 기능을 테스트하고 피드백을 준다.
- 운영 서버 (Production): ‘검증된 성역’
- 담당자가 “이 기능으로 최종 확정입니다!”라고 선언했을 때만 배포된다.
master브랜치로 코드를 합치고(Merge), 내가 GitLab에서 ‘배포 승인 버튼’을 눌러야만(Manual) 배포된다.
[Code Verification] 조건부 배포로 질서 잡기
GitLab CI는 브랜치별로 배포 경로를 다르게 지정할 수 있다. 이 전략을 코드로 구현하면 다음과 같다.
stages:
- build
- deploy
# 1. 빌드 (공통)
build_job:
stage: build
script:
- ./gradlew build
- docker build -t my-reg/app:$CI_COMMIT_SHA .
- docker push my-reg/app:$CI_COMMIT_SHA
# 2. 개발 서버 배포 (자동)
# 'develop' 브랜치에 코드가 올라오면 묻지도 따지지도 않고 배포한다.
deploy_to_dev:
stage: deploy
script:
- ssh user@dev-server "docker service update --image my-reg/app:$CI_COMMIT_SHA app-dev"
only:
- develop
# 3. 운영 서버 배포 (수동 승인)
# 'master' 브랜치에 코드가 올라와도, 내가 버튼을 누르기 전까진 배포되지 않는다.
deploy_to_prod:
stage: deploy
script:
- ssh user@prod-server "docker service update --image my-reg/app:$CI_COMMIT_SHA app-prod"
only:
- master
when: manual # 핵심! '사람이 버튼을 눌러야만' 실행된다.
핵심 포인트:
only: - develop: 개발 브랜치는 무조건 자동 배포. 속도가 생명이다.when: manual: 운영 브랜치는 사람이 한 번 더 확인하고 누르는 안전장치를 둔다.
마치며: 비로소 완성된 ‘나 혼자 산다’
이제 1인 개발자인 나에게도 완벽한 시스템이 갖춰졌다.
- 로컬(Local): 내 컴퓨터에서 기능 개발.
- 개발(Dev):
git push한 방이면 5분 뒤 개발 서버에 반영. 담당자가 직접 확인. - 운영(Prod): 컨펌된 기능만 모아서 안전하게 클릭 한 번으로 배포.
입사 초기, 캡처 도구를 켜고 한글 파일에 주석을 달며 야근하던 나는 이제 없다. 담당자가 “이거 좀 바꿔주세요”라고 하면, “네, 수정해서 개발 서버에 올렸으니 확인해 보세요”라고 말하며 여유롭게 커피를 마실 수 있게 되었다. 시스템이 나를 대신해 일하고 있기 때문이다.
이것으로 길었던 [Re: Booting] 프로젝트의 막을 마친다. CS 기초부터 시작해 리눅스, 도커, 그리고 CI/CD 파이프라인까지. 맨땅에 헤딩하며 쌓아 올린 이 지식들이, 지금도 어딘가에서 홀로 고군분투하고 있을 ‘생계형 풀스택 개발자’들에게 작은 등불이 되기를 바란다.
다음 글은 언제 올라오나요??