7장. 컨테이너 빌드 · 실행
1부의 마지막 장입니다. 6장에서 이미지를 만드는 설명서(Dockerfile)를 준비했습니다. 이제 그 설명서로 실제 이미지를 굽고(빌드), 컨테이너로 실행하고, 마지막에는 프론트엔드 · API · 데이터베이스 세 상자를 명령 한 번으로 한꺼번에 띄워, 우리 투표 서비스가 "상자 안에서" 도는 것을 브라우저로 확인합니다. 5장의 붕어빵 비유로 하면, 설계도(Dockerfile)로 틀(이미지)을 만들고, 그 틀로 붕어빵(컨테이너)을 굽는 단계입니다.
명령어가 몇 개 나오지만, 대부분 Claude Code에게 시키면 됩니다. 다만 각 명령이 무슨 뜻인지 알아 두면 문제가 생겼을 때 스스로 대응할 수 있으니, 뜻풀이를 함께 봅니다.
[지금 여기]
우리는 22단계를 거쳐 "라이브 투표 서비스"를 만들고, 컨테이너에 담아, AWS 클라우드에 배포합니다. 아래가 그 전체 여정 지도입니다. 지금은 7단계, 1부의 마지막에 있습니다.
- 1장개발 환경 준비 (터미널 / Node.js / Git / GitHub / VS Code / Claude Code)
- 2장Claude Code 사용법 익히기
- 3장무엇을 만들지 기획 (라이브 투표 서비스)
- 4장로컬에서 서비스 만들기 (React + Express + PostgreSQL)
- 5장Docker 개념
- 6장Dockerfile 작성
- 7장컨테이너 빌드·실행
- 8장AWS 시작 (계정 / IAM / 리전 / 비용)
- 9장이미지 태깅
- 10장ECR에 이미지 올리기
- 11장VPC 네트워크 구성
- 12장RDS 데이터베이스 생성
- 13장SSM 터널로 RDS 초기화
- 14장ECS Fargate + ALB로 배포 (기본 완성)
- 15장CI/CD (GitHub Actions)
- 16장IaC (Terraform)
- 17장배포 자동화 연결
- 18장현업 운영 구조 이해
- 19장무중단 배포
- 20장신규 버전 배포 & 롤백
- 21장(심화) 프론트를 Vercel로 분리 배포
- 22장마무리 (회고 + 리소스 삭제)
- 방금 끝낸 것: 6장에서 우리 서비스의 Dockerfile을 준비했습니다.
(API 서버용 Dockerfile, 프론트엔드용 멀티스테이지 Dockerfile,
.dockerignore.) - 이번 장에서 하는 것: 그 Dockerfile로 이미지를 빌드하고 컨테이너로 실행합니다. 그리고 docker compose로 프론트엔드 · API · DB 세 컨테이너를 한 번에 띄워, 서비스가 컨테이너 안에서 도는 것을 브라우저로 확인합니다. 여기까지가 1부의 목적지 — "어디서든 도는 상자에 담았다"입니다.
이번 장에서 완성되는 것
한 문장으로: 6장의 Dockerfile로 이미지를 빌드하고, docker compose로 프론트엔드 · API · DB 세 컨테이너를 한꺼번에 띄워, 투표 서비스가 전부 컨테이너 안에서 도는 것을 브라우저로 확인합니다.
이렇게 되면 성공입니다: VS Code 터미널에서 docker compose up을 실행한 뒤
브라우저로 http://localhost:8080에 접속하면, 4장에서 만든 것과 똑같은
투표 화면이 뜨고, 설문 만들기 · 투표 · 결과 그래프 · 새로고침 후 유지까지
전부 동작합니다. 다만 이번엔 로컬에 직접 깐 게 아니라 전부 컨테이너
안에서 돌고 있습니다.
사전 조건
이 장은 1~6장의 결과물 위에서 시작합니다. 아래가 갖춰져 있어야 합니다.
- 6장까지 완료: 문서(Documents) 폴더 안에
vote-app프로젝트가 있고, 그 안에backend(Express API)와frontend(React) 폴더, 그리고 각 폴더에 6장에서 만든Dockerfile과.dockerignore가 있습니다. 백엔드 폴더에는 4장에서 만든.env(DB 접속 정보 등)도 있습니다. - Docker Desktop이 설치되어 실행 중: 5장에서 Docker Desktop을 설치했습니다. 이번 장의 명령들은 Docker Desktop이 켜져 있어야 동작합니다. 화면 위(macOS는 메뉴 막대, Windows는 작업 표시줄)의 고래 아이콘이 움직임 없이 안정되어 있으면 켜진 상태입니다. 안 보이면 지금 Docker Desktop을 실행하고, 고래 아이콘이 안정될 때까지 기다리세요.
- VS Code로
vote-app폴더가 열려 있음: 이 책의 모든 명령은 VS Code 안의 터미널(터미널 → 새 터미널)에서 실행합니다.
시작 전에 Docker가 켜져 있는지 확인
VS Code에서 Terminal(터미널) → New Terminal(새 터미널)로 터미널을 열고
(새 터미널은 항상 vote-app 폴더에서 시작합니다), 아래를 한 줄씩 입력합니다.
docker --version # 설치된 Docker 버전을 보여 준다
docker ps # 지금 실행 중인 컨테이너 목록을 보여 준다
docker --version이 버전을 보여 주고, docker ps가 오류 없이 표의 제목
줄(CONTAINER ID IMAGE ...)을 보여 주면 준비 완료입니다.
docker ps에서 Cannot connect to the Docker daemon이라는
메시지가 나오면 Docker Desktop이 실행 중이 아닙니다. Docker Desktop을
켜고 고래 아이콘이 안정될 때까지 기다린 뒤 다시 입력하세요.
7.1 이미지 빌드 — docker build
(1) 개념 — 빌드가 무엇인가
6장에서 만든 Dockerfile은 아직 "설명서"일 뿐입니다. 이 설명서를 실제
이미지로 바꾸는 과정을 빌드(build)라고 하고, 그 명령이 docker build
입니다.
docker build
Dockerfile(설명서)을 읽어, 그 순서대로 실행하면서 이미지를 만들어 내는
명령입니다. 레시피(Dockerfile)를 보고 실제로 요리(이미지)를 완성하는
단계라고 보면 됩니다.
빌드한 이미지에는 이름표(태그)를 붙입니다. 이름을 붙여 둬야 나중에 그 이름으로 실행하거나 클라우드에 올릴 수 있습니다.
-t(태그, tag)
만들 이미지에 붙이는 이름입니다. docker build -t vote-api . 는
"이 이미지를 vote-api라는 이름으로 만들어라"라는 뜻입니다. 2부에서 이
태그가 버전 관리 · 롤백과 어떻게 연결되는지 더 다룹니다.
명령의 생김새는 이렇습니다.
docker build -t vote-api .
- docker build — Dockerfile을 읽어 이미지를 만들어라.
- -t vote-api — 만든 이미지에
vote-api라는 이름표(태그)를 붙여라. .(점) — Dockerfile이 있는 위치. 여기서는 "지금 이 폴더"를 뜻합니다. (그래서 빌드할 때는 그 Dockerfile이 있는 폴더에서 명령을 실행합니다.)
(2) 활용 사례 — 빌드는 어디에 쓰이나
- 회사에서 새 팀원 온보딩: 신입에게 "이 폴더에서
docker build한 줄만 치면 개발 환경이 통째로 만들어진다"고 안내합니다. 프로그램을 하나하나 깔 필요가 없습니다. - 오픈소스 실행: 인터넷에서 받은 프로젝트에 Dockerfile이 들어 있으면, 내 컴퓨터에 뭘 깔지 고민할 것 없이 빌드해서 바로 돌려 볼 수 있습니다.
- 배포 준비: 우리가 2부에서 할 일입니다. 서비스를 이미지로 빌드해 두면, 그 이미지를 AWS로 옮겨 그대로 인터넷에 서비스할 수 있습니다.
(3) [맛보기] 아주 작은 이미지 하나 직접 빌드해 보기
우리 프로젝트를 빌드하기 전에, 연습으로 아주 작은 이미지 하나를 직접 만들어 봅니다. 우리 프로젝트를 건드리지 않도록 문서 폴더에 연습용 폴더를 따로 만들어서 합니다.
-
VS Code에서 파일 → 폴더 열기(Open Folder)를 누릅니다. 문서(Documents) 폴더로 간 뒤, 창의 새 폴더(New Folder) 버튼으로
docker-hello폴더를 만들고 그 폴더를 엽니다. (우리 프로젝트를 건드리지 않도록 vote-app 바깥에 따로 만드는 것입니다.) 이제 VS Code 왼쪽 탐색기에 빈docker-hello폴더가 열립니다. -
이 폴더에 아주 짧은
Dockerfile을 하나 만듭니다. 왼쪽 탐색기에서docker-hello위에 마우스를 올리면 나타나는 새 파일(New File) 아이콘을 눌러, 파일 이름을Dockerfile(확장자 없이 그대로)로 짓습니다. 그리고 열린 빈 파일에 아래 내용을 복사해 붙여넣고 저장합니다(Ctrl/Cmd+S).
FROM alpine
CMD ["echo", "Hello from Docker!"]
FROM alpine— 가벼운 리눅스 이미지에서 시작합니다.CMD ["echo", "Hello from Docker!"]— 컨테이너가 켜지면 이 인사말을 출력합니다.
- 이 폴더에서 이미지를 빌드합니다. VS Code에서 터미널 → 새 터미널(New
Terminal)을 열면 방금 연
docker-hello폴더에서 시작합니다. 아래를 입력하세요.
docker build -t hello-docker . # 지금 폴더의 Dockerfile로 hello-docker 이미지를 만든다
FROM alpine 이미지를 내려받고 층(레이어)을 쌓는 모습이 터미널에
주르륵 나오고, 마지막에 성공 메시지가 나오면 이미지가 만들어진 것입니다.
- 만들어진 이미지를 목록으로 확인합니다.
docker images # 내 컴퓨터에 있는 이미지 목록을 보여 준다
목록의 REPOSITORY 칸에 hello-docker가 보이면 성공입니다. (내친김에
docker run hello-docker를 실행하면 Hello from Docker!가 출력됩니다.
실행 명령은 바로 다음 7.2에서 자세히 다룹니다.)
방금 여러분은 설명서 한 장으로 진짜 이미지를 처음 구워 봤습니다. 이게
docker build의 전부입니다.
(4) [우리 프로젝트] 우리 서비스 이미지 빌드하기
이제 6장에서 준비한 Dockerfile로 우리 서비스의 이미지 두 개(vote-api,
vote-web)를 빌드합니다.
먼저 다시 vote-app으로 돌아가야 합니다. 헷갈리지 않게, 새 터미널을
여세요. VS Code에서 Terminal → New Terminal로 새 터미널을 열면 항상
vote-app 폴더에서 시작합니다. (앞의 맛보기에서 docker-hello로 옮겨 간
터미널 대신 깨끗한 새 터미널로 이어 갑니다.)
- 백엔드(API 서버) 이미지를 빌드합니다. Dockerfile은
backend폴더 안에 있으니, 그 폴더로 들어가서 빌드합니다.
cd backend # vote-app 안의 backend 폴더로 이동
docker build -t vote-api . # backend/Dockerfile로 vote-api 이미지를 만든다
- 프론트엔드 이미지를 빌드합니다.
cd ../frontend # backend에서 나와 frontend 폴더로 이동
docker build -t vote-web . # frontend/Dockerfile로 vote-web 이미지를 만든다
프론트엔드는 6장에서 본 멀티스테이지 방식이라, "빌드 단계"와 "Nginx로 담는 단계"가 차례로 지나가는 게 보입니다.
- 두 이미지가 잘 만들어졌는지 확인합니다.
docker images # 이미지 목록 확인
목록에 vote-api와 vote-web이 둘 다 보이면 성공입니다.
7.2 컨테이너 실행 — docker run
(1) 개념 — 실행이 무엇인가
이미지(틀)가 준비됐으니, 이제 그 틀로 컨테이너(붕어빵)를 찍어 냅니다. 이걸
하는 명령이 docker run입니다.
docker run
이미지를 실제로 실행해 컨테이너로 띄우는 명령입니다. docker build가
"틀을 만드는" 것이라면, docker run은 "그 틀로 붕어빵을 굽는" 것입니다.
API 서버를 실행하는 명령의 생김새는 이렇습니다.
docker run -p 3000:3000 --env-file .env vote-api
- docker run — 이미지를 컨테이너로 실행하라.
- -p 3000:3000 — 포트를 연결하라(포트 매핑).
- --env-file .env —
.env파일의 환경변수를 컨테이너에 넣어 줘라. - vote-api — 실행할 이미지 이름.
여기서 가장 헷갈리는 게 포트 매핑입니다. 짚고 갑시다.
포트(Port) 한 컴퓨터 안에서 프로그램들이 통신하려고 쓰는 "번호가 붙은 창구"입니다. 예를 들어 우리 API 서버는 3000번 창구로 요청을 받습니다.
포트 매핑(Port Mapping) — -p
컨테이너는 5장에서 배웠듯 "상자"라, 그 안의 창구(포트)는 바깥에서 바로
보이지 않습니다. 그래서 "내 컴퓨터의 몇 번 창구를 → 상자 안의 몇 번
창구로 연결한다"고 지정해 줘야 합니다. 이게 포트 매핑입니다.
-p 3000:3000은 "내 컴퓨터 3000번 → 컨테이너 3000번으로 연결"이라는
뜻입니다. 앞이 내 컴퓨터, 뒤가 상자 안쪽입니다.
환경변수 주입 — --env-file
4장에서 비밀번호 같은 민감한 값을 .env 파일로 분리했습니다. 컨테이너는
상자라서 그 값을 안에서 알 수 없으므로, 실행할 때 --env-file .env로
넣어 줍니다. 이렇게 하면 비밀번호를 이미지에 구워 넣지 않고도 전달할 수
있습니다.
(2) 활용 사례 — docker run은 어디에 쓰이나
- 필요한 프로그램을 즉석에서 띄우기: 데이터베이스나 캐시 서버 같은 걸
내 컴퓨터에 설치하지 않고,
docker run으로 컨테이너 하나 띄웠다가 다 쓰면 지웁니다. 컴퓨터가 지저분해지지 않습니다. - 같은 프로그램의 여러 버전 동시 실행: 포트만 다르게 매핑하면(
-p 8080:80,-p 8081:80) 같은 이미지를 여러 개 띄워 나란히 비교할 수 있습니다. - 일회성 도구 실행: 어떤 변환 도구를 딱 한 번 쓰고 싶을 때, 설치 없이 컨테이너로 실행하고 끝냅니다.
(3) [맛보기] 컨테이너를 띄워 브라우저로 보기
포트 매핑을 손으로 느껴 보는 게 좋습니다. 공개된 nginx(웹 서버) 이미지를
띄워, 내 컴퓨터 포트로 접속해 봅니다.
- 아무 터미널에서나(위치는 상관없습니다) 아래를 입력합니다.
docker run -d -p 8080:80 nginx # nginx를 백그라운드로 띄우고, 내 컴퓨터 8080 → 컨테이너 80 연결
- -d — "백그라운드로 실행"하라는 뜻입니다(터미널을 계속 붙잡지 않습니다).
- -p 8080:80 — 내 컴퓨터 8080번을 컨테이너 안 80번(Nginx가 쓰는 기본 포트)에 연결합니다.
- 처음이라면
nginx이미지를 잠깐 내려받은 뒤 실행됩니다.
-
브라우저를 열어 주소창에
http://localhost:8080을 입력합니다. "Welcome to nginx!" 페이지가 보이면 성공입니다. 방금 브라우저의 요청이 포트 매핑(8080 → 80)을 타고 상자 안 Nginx까지 전달된 것입니다. -
확인이 끝났으면 이 연습용 컨테이너를 멈춥니다.
docker ps # 실행 중인 컨테이너 목록. 맨 앞 CONTAINER ID(예: a1b2c3d4)를 확인
docker stop a1b2c3d4 # 그 CONTAINER ID로 컨테이너를 멈춘다 (자기 값으로 바꿔 입력)
docker ps목록의 맨 왼쪽에 나오는 값이 CONTAINER ID입니다. 그 값을docker stop뒤에 붙이면 멈춥니다.
(4) [우리 프로젝트] API 컨테이너 실행 명령 이해하기
우리 API도 방금 배운 방식으로 실행합니다. backend 폴더에서(그래야 옆에
있는 .env를 --env-file .env로 바로 가리킬 수 있습니다) 이렇게 실행합니다.
docker run -p 3000:3000 --env-file .env vote-api
그런데 여기서 중요한 사실이 하나 있습니다. 이렇게 API 하나만 띄우면,
API는 켜지지만 옆에 붙을 데이터베이스가 없어서 완전히 동작하지는
않습니다. 우리 서비스는 부품이 세 개(프론트엔드 · API · DB)이고, 이 셋이
서로 연결돼야 제대로 돕니다. 그래서 컨테이너를 하나씩 docker run으로
따로 띄우고 서로 연결까지 신경 쓰는 건 번거롭습니다.
이 명령을 실제로 실행해 API 컨테이너가 뜨는 모습만 잠깐 봐도 좋습니다.
확인했으면 터미널에서 Ctrl + C를 눌러 멈추세요. 세 컨테이너를 함께
띄우는 깔끔한 방법은 바로 다음 7.3의 docker compose입니다.
7.3 세 컨테이너를 한 번에 — docker compose
(1) 개념 — docker compose가 무엇인가
7.2 끝에서 본 문제, "부품이 셋이라 하나씩 띄우고 연결하기가 번거롭다"를 푸는 도구가 docker compose입니다.
docker compose(도커 컴포즈) 여러 컨테이너를 하나의 설정 파일에 정의해 두고, 명령 한 번으로 한꺼번에 실행 · 중지하는 도구입니다. "우리 서비스는 이 세 상자로 이루어진다"를 한 파일에 적어 두는 셈입니다.
docker-compose.yml
compose가 읽는 설정 파일입니다. 어떤 컨테이너들을(services), 어떤
이미지 또는 어떤 Dockerfile로, 어떤 포트와 환경변수로 띄울지를 적습니다.
.yml은 사람이 읽기 쉬운 형식의 설정 파일 확장자입니다.
docker compose up / down
- up: 설정 파일에 정의된 컨테이너들을 한꺼번에 띄운다.
- down: 그 컨테이너들을 한꺼번에 멈추고 정리한다.
(2) 활용 사례 — compose는 어디에 쓰이나
- 로컬 개발 환경 통째로: 웹 서버 + API + DB + 캐시처럼 여러 부품으로 된
서비스를, 팀원 누구나
docker compose up한 줄로 똑같이 띄웁니다. "내 컴퓨터에선 되는데" 문제가 사라집니다. - 오픈소스 체험: 많은 오픈소스가
docker-compose.yml을 함께 제공합니다. 받아서up한 번이면 필요한 부품이 전부 뜹니다. - 자동 테스트 환경(CI): 테스트를 돌릴 때 DB 같은 딸림 부품을 compose로
띄웠다가, 끝나면
down으로 깨끗이 정리합니다. - 여러 상자의 실행 순서 · 연결 관리: "DB가 먼저 뜨고 나서 API를 띄운다" 같은 순서와, 상자끼리 서로를 찾는 연결을 한 파일에서 관리합니다.
(3) [맛보기] compose로 컨테이너 하나 띄우고 내리기
먼저 아주 작은 compose를 손으로 만져 봅니다. 앞서 만든 연습용 폴더
docker-hello를 다시 씁니다. (VS Code에서 그 폴더가 열려 있지 않다면,
파일 → 폴더 열기로 문서 폴더의 docker-hello를 다시 엽니다.)
- 이 폴더에
docker-compose.yml파일을 하나 만듭니다. 왼쪽 탐색기에서docker-hello위의 새 파일(New File) 아이콘을 눌러 이름을docker-compose.yml로 짓고, 열린 파일에 아래 내용을 복사해 붙여넣고 저장합니다.
services:
web:
image: nginx
ports:
- "8081:80"
- nginx 컨테이너 하나를, 내 컴퓨터 8081번 → 컨테이너 80번으로 연결해 띄우라는 설정입니다.
들여쓰기(띄어쓰기)가 중요합니다
.yml 파일은 줄 앞의 띄어쓰기 칸 수로 상하 관계를 나타냅니다. 위
내용을 그대로 복사해 붙여넣으면 칸이 맞습니다. 직접 칠 때는 탭이 아니라
스페이스로, 예시와 같은 칸 수로 맞춰야 합니다.
- compose로 띄웁니다. VS Code에서 터미널 → 새 터미널을 열면
docker-hello폴더에서 시작합니다. 아래를 입력하세요.
docker compose up -d # 설정에 정의된 컨테이너를 백그라운드로 띄운다
브라우저에서 http://localhost:8081에 접속해 "Welcome to nginx!"가
보이면 성공입니다.
- 다 봤으면 내립니다.
docker compose down # 이 compose로 띄운 컨테이너를 한꺼번에 멈추고 정리
up으로 띄우고 down으로 내린다 — compose의 기본 리듬을 손으로
익혔습니다.
(4) [우리 프로젝트] 프론트 컨테이너가 API를 찾게 하기 — Nginx 프록시
세 컨테이너를 묶기 전에, 프론트 이미지에 손질이 하나 필요합니다. 6장에서
만든 프론트 이미지의 Nginx는 지금 화면 파일만 서빙합니다. 그런데
컨테이너로 띄우면, 브라우저가 보내는 /api 요청을 API 서버로 넘겨줄
사람이 없습니다. 4장에서 그 일을 하던 Vite 개발 프록시는 개발용이라,
빌드된 이미지 안에는 없기 때문입니다.
그래서 이번엔 web의 Nginx가 /api 요청을 api 컨테이너로 넘기도록
설정을 추가합니다. 프론트 코드는 그대로 상대 경로(/api)로 부르고,
넘겨주는 역할만 개발 프록시에서 Nginx로 바뀌는 것입니다.
리버스 프록시(Reverse Proxy)
요청을 대신 받아 뒤의 서버로 넘겨주는 중계입니다. 여기서는 Nginx가
브라우저의 /api 요청을 받아 api 컨테이너로 넘깁니다. 브라우저 입장에서는
늘 같은 주소(web)와만 대화하므로 CORS가 생기지 않습니다. 이 아이디어는
2부에서 로드밸런서(ALB)로 규모를 키워 다시 만납니다.
[참고] 왜 대상 주소를 환경변수로 받나 — 로컬과 AWS의 차이
docker compose에서는 컨테이너마다 네트워크가 나뉘어 있어 상대 컨테이너를
서비스 이름(api)으로 불러야 합니다. 반면 AWS ECS에서는 web과 api를
한 태스크에 담아 localhost로 통신합니다. 실행 환경에 따라 달라지는
값이므로 — 1부에서 배운 원칙대로 — 환경변수(API_URL)로 분리합니다.
그래서 compose에서는 API_URL=http://api:3000을 넣어 줍니다.
(5) [우리 프로젝트] 프론트엔드 · API · DB를 한 파일로
이제 우리 서비스의 세 컨테이너(web · api · db)를 한 파일에 담습니다. 파일은
vote-app 폴더의 맨 위(루트)에 docker-compose.yml이라는 이름으로
둡니다. Claude Code에게 만들어 달라고 합시다. (vote-app에서 claude 실행 후.)
만들어진 파일은 대략 이런 구조입니다(세부는 조금 다를 수 있습니다).
services:
db: # 데이터베이스 컨테이너
image: postgres:16 # 공개된 postgres 이미지를 그대로 사용
environment: # DB 초기 설정 (실제로는 .env 값으로 채웁니다)
POSTGRES_DB: voteapp
POSTGRES_USER: voteuser
POSTGRES_PASSWORD: 비밀번호
healthcheck: # DB가 접속을 받을 준비가 됐는지 검사
test: ["CMD-SHELL", "pg_isready -U voteuser -d voteapp"]
interval: 5s
timeout: 3s
retries: 5
api: # API 서버 컨테이너
build: ./backend # backend 폴더의 Dockerfile로 빌드
ports:
- "3000:3000" # 내 컴퓨터 3000 → 컨테이너 3000
env_file: .env # .env의 환경변수를 넣어 준다
environment:
DB_HOST: db # DB 접속 host는 db 컨테이너(서비스 이름)
depends_on:
db:
condition: service_healthy # db가 healthy해진 뒤에 api를 띄운다
web: # 프론트엔드 컨테이너 (Nginx)
build: ./frontend # frontend 폴더의 Dockerfile로 빌드
ports:
- "8080:80" # 내 컴퓨터 8080 → 컨테이너 80(Nginx)
environment:
API_URL: http://api:3000 # Nginx가 /api 요청을 넘길 대상(compose에선 api 서비스)
depends_on:
- api # api 다음에 web을 띄운다
주목할 점 몇 가지만 짚습니다.
- services 아래에
db,api,web세 컨테이너가 정의돼 있습니다. - build는 "이 폴더의 Dockerfile로 이미지를 만들어 쓰라"는 뜻이고,
image는 "이미 만들어진 이미지를 가져다 쓰라"는 뜻입니다. DB는 우리가
만들 게 아니라 공개된
postgres이미지를 그대로 씁니다. - depends_on은 실행 순서입니다.
db가 준비돼야api가 붙을 수 있으니, "db가 healthy해진 뒤 api"처럼 순서를 지정합니다. (healthcheck가 이 "준비 됐는지"를 검사합니다.) - DB 접속 host가 왜
db인가: 아래 [용어]를 보세요.
한 번에 실행하기
vote-app 루트에서(새 터미널을 열면 여기서 시작합니다) 아래를 실행합니다.
docker compose up --build
- --build는 "띄우기 전에 이미지를 새로(다시) 빌드하라"는 뜻입니다. 처음 띄우거나, 코드를 고친 뒤에는 이걸 붙입니다.
- 이 명령 하나로 세 컨테이너가 순서대로 빌드 · 실행됩니다. 터미널에는 세 컨테이너의 로그가 함께 흘러갑니다.
멈출 때는 이 터미널에서 Ctrl + C를 누르거나, 다른 터미널을 열어
vote-app에서 아래를 입력합니다.
docker compose down # 세 컨테이너를 한꺼번에 멈추고 정리
7.4 전체 서비스 동작 확인
이제 진짜 확인입니다. 4장에서는 컨테이너 없이 로컬에서 직접 실행했지만, 이번에는 전부 컨테이너(상자) 안에서 돌고 있습니다.
docker compose up --build가 정상적으로 떴다면(터미널 로그가 오류 없이
흐르고 API가 요청을 기다린다는 메시지가 보이면), 브라우저를 열어 프론트엔드
주소 http://localhost:8080으로 접속합니다. 그리고 3장의 완료 기준을
하나씩 확인합니다.
- □ 설문을 만들 수 있는가
- □ 선택지를 누르면 득표수가 늘어나는가
- □ 결과가 막대그래프로 보이는가
- □ 새로고침해도 결과가 유지되는가 (DB 컨테이너에 저장되는가)
모두 통과했다면, 정말 중요한 이정표를 지난 것입니다. 우리 서비스가 이제 어느 컴퓨터에서든 똑같이 실행될 수 있는 상자에 담겼습니다. 2부에서 이 상자(이미지)를 AWS로 옮기면, 그대로 인터넷에 서비스할 수 있습니다.
왜 이게 대단한가 5장에서 이야기한 "내 컴퓨터에선 되는데" 문제를, 방금 여러분이 해결했습니다. 이 컨테이너들은 동료의 컴퓨터에서도, AWS 서버에서도 똑같이 돕니다. Node.js 버전이나 PostgreSQL 설치 여부를 신경 쓸 필요가 없습니다. 전부 상자 안에 들어 있으니까요.
확인이 끝났으면 컨테이너를 정리해 둡니다. up을 실행한 터미널에서
Ctrl + C를 누르거나, 다른 터미널(vote-app)에서 docker compose down을
실행합니다. (연습용으로 만든 docker-hello 폴더도 이제 필요 없으니 지워도
됩니다.)
7.5 1부 정리 & 자주 나는 오류
1부에서 한 일 되짚기
1부에서 우리는 아무것도 없는 노트북에서 시작해 여기까지 왔습니다.
- 개발 환경을 갖추고 Claude Code로 첫 대화를 나눴다 (1~2장)
- 라이브 투표 서비스를 기획하고, AI에게 시켜서 만들었다 (3~4장)
- Docker가 왜 필요한지 이해하고, 설치했다 (5장)
- 서비스를 이미지로 만드는 설명서(Dockerfile)를 이해했다 (6장)
- 이미지를 빌드하고, 세 컨테이너를 compose로 함께 띄워 상자째로 동작시켰다 (7장)
자주 나는 오류와 대처
실습 중 아래와 비슷한 문제를 자주 만납니다. 대부분은 오류 메시지를 그대로 Claude Code에 붙여넣으면 해결되지만, 흔한 것들은 원인을 알아 두면 좋습니다.
-
Cannot connect to the Docker daemon(Docker에 연결할 수 없음)- 원인: Docker Desktop이 실행 중이 아닙니다.
- 대처: Docker Desktop을 켜고, 고래 아이콘이 안정될 때까지 기다린 뒤 다시 시도합니다. (이번 장 "사전 조건" 참고.)
-
port is already allocated/address already in use(포트가 이미 사용 중)- 원인: 그 포트(예: 8080, 3000)를 다른 프로그램이나 아까 띄운 컨테이너가 이미 쓰고 있습니다. 4장에서 로컬로 띄운 서버가 아직 켜져 있는 경우도 많습니다.
- 대처: 앞서 띄운 것을
docker compose down이나docker stop으로 정리 합니다.docker ps로 무엇이 떠 있는지 확인할 수 있습니다. 정 안 되면docker-compose.yml의 포트 번호(앞쪽, 내 컴퓨터 쪽)를 다른 값으로 바꿉니다.
-
DB 연결 실패 (API가 db에 못 붙음)
- 원인: DB 컨테이너가 준비되기 전에 API가 접속을 시도했거나, 접속 주소 ·
비밀번호가 맞지 않습니다. 특히 host를
localhost로 두면 compose 안에서는 실패합니다. - 대처: API의 DB host가
db(서비스 이름)인지,.env의 사용자 · 비밀번호가db서비스 설정과 같은지 확인합니다.depends_on에 healthcheck 조건이 걸려 있으면 순서 문제는 대부분 해결됩니다.
- 원인: DB 컨테이너가 준비되기 전에 API가 접속을 시도했거나, 접속 주소 ·
비밀번호가 맞지 않습니다. 특히 host를
-
화면은 뜨는데 저장이 안 되거나 "테이블이 없다" 오류
- 원인: compose의
db가 새 빈 데이터베이스라, 4장에서 만든 테이블이 아직 없습니다. - 대처: 7.3의 [참고]대로 초기화 SQL을 db 컨테이너에 연결합니다.
- 원인: compose의
-
코드를 고쳤는데 바뀐 게 반영이 안 됨
- 원인: compose가 예전에 빌드한 이미지를 그대로 씁니다.
- 대처:
docker compose up --build로 다시 빌드하며 띄웁니다.
-
빌드가 아주 오래 걸리거나 용량이 큼
- 원인:
.dockerignore가 없어node_modules등이 통째로 복사되고 있습니다. - 대처: 6장의
.dockerignore가backend와frontend에 제대로 있는지 확인합니다.
- 원인:
컨테이너 · 이미지 정리 명령
실습을 반복하면 안 쓰는 컨테이너와 이미지가 쌓입니다. 아래로 정리할 수
있습니다(무엇을 지우는지 확인하고 쓰세요).
- docker ps : 실행 중인 컨테이너 목록
- docker compose down : compose로 띄운 것들 정리
- docker system prune : 안 쓰는 컨테이너 · 이미지 등을 한꺼번에 정리
[확인] 7장 & 1부 체크리스트
여기까지 됐는지 확인해 보세요. 하나라도 안 됐다면 해당 절로 돌아가 다시 진행합니다.
- □ Docker Desktop이 켜져 있고
docker ps가 오류 없이 나온다 - □
docker build -t로vote-api,vote-web이미지를 만들고docker images로 확인했다 - □
docker run의 포트 매핑(-p)과 환경변수 주입(--env-file)이 무슨 뜻인지 안다 - □ docker compose로 web · api · db 세 컨테이너를
docker compose up으로 한 번에 띄웠다 - □ 브라우저
http://localhost:8080에서 컨테이너로 도는 투표 서비스가 동작하는 걸 확인했다 - □
docker compose down으로 컨테이너를 정리할 수 있다 - □ 자주 나는 오류 몇 가지의 원인과 대처를 안다
수고했습니다. 1부를 마치며, 여러분은 서비스를 "어디서든 똑같이 돌아가는 상자"에 담는 데 성공했습니다.