티스토리 뷰
지난 글에서 docker-compose로 로컬 개발 환경을 구축하는 방법을 정리했다.
하지만 내 컴퓨터에서만 잘 돌아가는 서비스는 그냥 '자기만족'일 뿐이다. 이제 이 잘 만든 컨테이너를 서버로 옮겨서 전 세계 사람들이 접속하게 만들어야 한다.
FTP로 소스 코드 올리고 서버에서 npm install 하던 시절은 이제 그만 잊자. (그거 하다가 밤샌 날이 며칠인가..)
"로컬에선 되는데 서버에선 안 돼요"의 종말
신입 시절, 배포 날만 되면 식은땀이 흘렀다. 로컬에서는 기가 막히게 돌아가던 코드가 서버에만 올리면 에러를 뿜었다.
알고 보니 서버의 Node.js 버전이 달랐거나, 실수로 라이브러리 설치를 하나 빼먹었기 때문이었다.
"아니, 내 컴퓨터 환경을 그대로 서버에 복사해 넣을 순 없나?"
도커가 바로 그걸 해준다. '소스 코드'를 배포하는 게 아니라, 실행 환경까지 통째로 얼린 '이미지'를 배포하는 것이다.
이러면 서버에 Node.js가 깔려 있든 말든 상관없다. 그냥 이미지를 가져와서(Pull) 실행(Run)하면 끝이다.
그럼 이 이미지를 어디에 저장하고 어떻게 옮길까?
USB에 담아서 서버실로 뛰어갈 순 없으니,
우리는 '도커 레지스트리(Docker Registry)'라는 이미지 저장소를 사용한다.
마치 코드를 GitHub에 올리듯, 이미지는 Docker Hub(혹은 ECR 등)에 올리는 것이다.
이번 글은 내가 만든 컨테이너를 안전하게 포장해서 배송하는 과정을 뜯어본다.
배포의 정석 프로세스 (Build -> Push -> Run)
원리는 간단하다.
- 내 컴퓨터: 이미지를 빌드하고 레지스트리에 업로드한다. (Push)
- 레지스트리: 이미지를 보관하는 창고 (예: Docker Hub, AWS ECR).
- 운영 서버: 레지스트리에서 이미지를 다운로드 받아 실행한다. (Pull & Run)
이 과정을 쉘 스크립트 한 줄로 끝낼 수 있어야 진정한 개발자다.
1. 이미지 이름 규칙 (Tagging Strategy)
그냥 docker build -t my-app . 이라고 하면 안 된다. 레지스트리에 올리려면 '누구의 저장소에 넣을지' 명확한 주소를 적어줘야 한다.
형식: {레지스트리_계정명}/{이미지_이름}:{태그}
# 잘못된 예 (로컬에서만 쓸 때)
docker build -t my-blog-server .
# 올바른 예 (Docker Hub 계정이 'devchoon'이라고 가정)
# 태그(v1.0)를 안 붙이면 자동으로 latest가 되는데, 실무에선 버전 명시가 생명이다.
docker build -t devchoon/my-blog-server:v1.0 .
2. 세상 밖으로 업로드 (Push)
이제 Docker Hub에 로그인하고 이미지를 쏜다. (물론 사전에 Docker Hub 가입은 되어 있어야 한다.)
# 1. 도커 로그인 (터미널에서 한 번만 하면 된다)
docker login
# 2. 이미지 업로드
docker push devchoon/my-blog-server:v1.0
터미널에 진행률 바(ProgressBar)가 올라가는 걸 보면 묘한 쾌감이 있다. 여기서 [Tip] 하나. 두 번째 배포부터는 속도가 엄청 빠르다. 도커는 '레이어(Layer)' 구조라 변경된 코드 부분만 전송하기 때문이다. (기가 막힌 효율성이다.)
3. 서버에서 실행 (Deployment)
이제 AWS EC2 같은 서버에 접속했다고 치자. 서버엔 도커만 설치되어 있으면 된다. npm, python, java? 다 필요 없다.
# 1. 이미지 다운로드
docker pull devchoon/my-blog-server:v1.0
# 2. 컨테이너 실행 (백그라운드 모드)
docker run -d -p 80:3000 --name blog-server devchoon/my-blog-server:v1.0
끝이다. 정말로 끝이다. dependencies 설치하느라 에러 날 일도, 환경 변수 꼬일 일도 (제대로 설정했다면) 없다.
Deep Dive: 보안과 아키텍처 (그냥 넘어가면 털린다)
[중요] .env 파일은 이미지에 굽지 마라
가장 많이 하는 실수다. COPY . . 할 때 .env 파일까지 이미지 안에 넣어버리는 경우다. 도커 이미지는 docker history 명령어로 내부를 다 까볼 수 있다. 즉, 이미지를 받은 누군가가 내 AWS Secret Key나 DB 비밀번호를 볼 수 있다는 뜻이다.
해결책:
- .dockerignore 파일에 .env를 추가해서 빌드에서 제외한다.
- 환경 변수는 컨테이너를 실행(Run)할 때 주입한다.
# 실행 시점에 환경 변수 파일 주입 (--env-file)
docker run -d \
--env-file ./.env.production \
-p 80:3000 \
devhoon/my-blog-server:v1.0
이렇게 해야 이미지는 '순수한 로직'만 담고, 설정값은 '서버 환경'에 따라 분리된다. 이게 12-Factor App의 핵심이다.
[Tip] 아키텍처 이슈 (Apple Silicon vs Linux)
내가 맥북 M1/M2/M3(ARM 아키텍처)를 쓴다면 주의해야 한다. 내 맥북에서 빌드한 이미지는 기본적으로 linux/arm64용이다. 그런데 대부분의 서버(AWS EC2 등)는 linux/amd64다. 서버에서 실행하려는데 exec format error가 뜬다면 100% 이 문제다.
해결책 (멀티 플랫폼 빌드):
# 빌드할 때 플랫폼을 명시해준다.
docker build --platform linux/amd64 -t devhoon/my-blog-server:v1.0 .
이거 몰라서 3시간 날린 적 있다.
삽질 로그 (Troubleshooting)
이론은 완벽해도 현실은 시궁창일 때가 많다. 내가 겪은 찐 에러들이다.
[상황 1] denied: requested access to the resource is denied
에러 메시지: push를 했는데 권한이 없다며 빨간 줄이 뜬다.
denied: requested access to the resource is denied
원인 분석:
- docker login을 안 했거나.
- [가장 흔함] 내 아이디와 이미지 태그의 네임스페이스가 다를 때. 내 아이디는 devhoon인데, 이미지를 park/my-server로 만들어서 올리려고 하면, 도커 허브는 "너 park 아니잖아?" 하고 걷어찬다.
해결책: 이미지 태그 맨 앞이 내 도커 허브 아이디와 일치하는지 확인해라. 틀렸다면 다시 태그를 달자.
# 기존 이미지에 새 이름표(Tag) 붙이기
docker tag my-server devchoon/my-server:v1.0
[상황 2] 서버 디스크가 꽉 찼어요 (No space left on device)
상황: 배포를 몇 달 하다 보니 갑자기 서버가 멈췄다. 용량을 확인하니 도커가 100%를 먹고 있었다.
원인 분석: 배포할 때마다 새로운 이미지를 Pull 받으면, **과거의 이미지들(Dangling images)**은 지워지지 않고 서버 구석에 쌓인다. 쓰지도 않는 v0.1, v0.2 ... v9.9가 디스크를 점령한 것이다.
해결책: 배포 스크립트에 청소 명령어를 추가하거나 주기적으로 실행해준다.
# 사용하지 않는 모든 이미지, 컨테이너, 네트워크 한방에 정리
docker system prune -f
(주의: 현재 실행 중인 건 안 건드리지만, 멈춰 있는 건 다 날아가니 조심해서 써야 한다.)
이제 '내 컴퓨터'라는 좁은 우물을 벗어났다.
내가 짠 코드가 이미지라는 컨테이너 박스에 담겨, 지구 반대편의 서버에서도 똑같이 돌아간다. 이것이 컨테이너 기술이 가져온 혁명이다.
하지만 매번 코드 수정할 때마다 build, push, 서버 들어가서 pull, run 치는 것도 슬슬 귀찮아질 때가 온다. 사람은 반복을 싫어하니까.
다음 글에서는 이 귀찮은 과정을 기계에게 시키는 [Docker] 8. CI/CD 입문: GitHub Actions로 배포 자동화하기를 정리해보려고 한다.
내가 코드를 push만 하면 알아서 서버에 배포까지 되는 마법을 부려보자.
끝!
'Docker' 카테고리의 다른 글
| [Docker] 7. CI/CD 입: GitHub Actions로 배포 자동화하기 (0) | 2025.12.05 |
|---|---|
| [Docker] 5. Docker Compose: 컨테이너 오케스트라의 지휘자 (0) | 2025.12.05 |
| [Docker] 4. 이미지 다이어트의 정석: Multi-stage Build (0) | 2025.12.05 |
| [Docker] 3. Dockerfile 작성법: 한 줄 한 줄의 의미 (0) | 2025.12.05 |
| [Docker] 2. 설치 (0) | 2025.12.05 |
- 인천 구월동 맛집
- 인천 구월동 이탈리안 맛집
- 맛집
- 파니노구스토
- javascript
- 정보보안기사 #실기 #정리
- Async
- react-native
- 이탈리안 레스토랑
- react
- redux-thunk
- AsyncStorage
- Promise
- redux
- await
- Total
- Today
- Yesterday