volumes: - hera_pgdata:/var/lib/postgresql/data 설정 시 host pc에 해당 디렉토리가 자동 생성되는가?
Named Volume 구문의 구조는 <볼륨 이름>:<컨테이너 내부 경로> 이다.
Docker가 자동으로:
hera_pgdata 볼륨이 없으면 생성/var/lib/docker/volumes/hera_pgdata/_data 디렉토리 자동 생성/var/lib/postgresql/data와 연결확인 명령어:
docker volume inspect hera_pgdata
# Mountpoint 항목이 실제 호스트 경로Mac 주의사항: Docker Desktop은 Linux VM 위에서 동작하므로 /var/lib/docker/volumes/가 Mac 파일시스템에 직접 보이지 않는다.
Bind Mount 설정 방법과 "디버깅 상황에서만 유용하다"는 의미는?
services:
db:
image: postgres
volumes:
- ./data:/var/lib/postgresql/data
# ↑ 호스트 경로 ↑ 컨테이너 내부 경로| Named Volume | Bind Mount | |
|---|---|---|
| 호스트 경로 | Docker가 자동 결정 | 사용자가 직접 지정 |
| 선언 방식 | 볼륨이름:/컨테이너경로 | ./호스트경로:/컨테이너경로 |
| 용도 | DB, 영속 데이터 | 소스코드, 설정 파일, 디버깅 |
| 이식성 | 높음 | 낮음 (경로 종속) |
| 직접 관찰 | 어려움 | 쉬움 |
Bind Mount는 호스트 경로가 프로젝트 폴더 안에 있어 파일을 바로 열어볼 수 있다. 반면 Named Volume은 Docker가 관리하는 경로라 직접 접근이 어렵다.
다른 개발자가 이미지를 pull해서 실행하면 볼륨이 생성되는데 어차피 데이터가 없는 거 아닌가?
Named Volume의 목적은 개발자 간 데이터 공유가 아니다.
컨테이너를 재시작/재생성해도 데이터가 유지되는 것이 목적이다.
docker compose down # 컨테이너 삭제
docker compose up # 컨테이너 재생성
# → 볼륨은 살아있으므로 DB 데이터 그대로 유지볼륨 없이 실행하면 docker compose down 후 재실행 시 DB 데이터가 전부 초기화된다.
개발자 간 데이터 공유는 별도 방법 사용:
핵심 철학: 컨테이너는 언제든 삭제/재생성 가능한 일회용이어야 하고, 데이터만 영속적으로 남아야 한다.
멀티 스테이지 빌드는 지시어가 많아 레이어 수가 증가할 것 같은데, 그럼에도 장점이 있는가?
이전 스테이지의 레이어는 최종 이미지에 포함되지 않는다.
# 스테이지 1 (builder) — 최종 이미지에 포함 안됨
FROM node:20 AS builder
COPY . .
RUN npm install
RUN npm run build
# 스테이지 2 (최종) — 이 레이어만 최종 이미지에 포함됨
FROM node:20-alpine
COPY /app/dist ./dist| 단일 스테이지 | 멀티 스테이지 | |
|---|---|---|
| Dockerfile 지시어 수 | 적음 | 많음 |
| 최종 이미지 레이어 수 | 많음 | 적음 |
| 최종 이미지 크기 | 큼 (1GB+) | 작음 (200MB 내외) |
| 보안 | 빌드 도구 포함 | 실행에 필요한 것만 |
소스코드 변경 시 이후 레이어(npm ci, npm run build)도 무조건 새로 만드는가?
한 레이어의 캐시가 무효화되면, 그 이후 모든 레이어도 캐시가 무효화된다.
FROM node:14
WORKDIR /app
COPY . . # 소스코드 변경 → 캐시 무효화
RUN npm ci # 무조건 새로 실행
RUN npm run build # 무조건 새로 실행FROM node:14
WORKDIR /app
COPY package.json package-lock.json ./ # 의존성 파일만 먼저 복사
RUN npm ci # package.json 안 바뀌면 캐시 사용
COPY . . # 소스코드 복사
RUN npm run build # 소스코드 바뀌면 여기서만 무효화| 상황 | 나쁜 순서 | 좋은 순서 |
|---|---|---|
| 소스코드만 변경 | npm ci + build 전부 재실행 | build만 재실행 |
| package.json 변경 | npm ci + build 전부 재실행 | npm ci + build 전부 재실행 |
황금 규칙: 변경 빈도가 낮은 것을 위에, 높은 것을 아래에 배치한다. Docker는 레이어 캐시 유효성을 파일 내용의 체크섬으로 판단한다.
ARG NEXT_PUBLIC_API_BASE_URL 처럼 값 없이 선언만 하면 어떤 값이 들어가는가?
값은 .env.docker 파일에서 온다. 흐름은 다음과 같다:
.env.docker 파일 (git 비공개)
NEXT_PUBLIC_API_BASE_URL=https://prod.api.com
↓
dotenv -e .env.docker -- docker build ...
→ .env.docker를 현재 shell 환경변수로 로드
↓
--build-arg NEXT_PUBLIC_API_BASE_URL (값 없이 키만)
→ Docker가 현재 shell 환경변수에서 자동으로 값을 찾아 주입
↓
ARG NEXT_PUBLIC_API_BASE_URL (Dockerfile에서 수신)--build-arg KEY (값 없이 키만) 구문은 현재 shell 환경변수에서 해당 키를 찾아 자동으로 주입한다.
ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} 은 무슨 의미인가? ARG 없이 바로 쓸 수는 없나?
Dockerfile의 ${} 는 이미 선언된 ARG 또는 ENV를 참조하는 변수 치환이다.
| ARG | ENV | |
|---|---|---|
| 유효 범위 | 선언된 스테이지 내부 | 이미지 레이어에 저장, 이후 모든 RUN + 컨테이너 실행 시까지 유지 |
| 멀티스테이지 | 스테이지 경계에서 소멸 | 스테이지 경계에서 소멸 (다음 FROM 이후엔 없음) |
| 이미지 검사 | docker inspect에 안 보임 | docker inspect에 보임 |
ARG만으로는 충분하지 않은 이유:
RUN에서는 ARG도 참조 가능하나, 명시성이 떨어짐docker inspect로 어떤 값으로 빌드됐는지 추적 불가Next.js는 빌드 시점에 NEXT_PUBLIC_* 변수를 JS 번들에 문자열로 직접 치환(인라이닝)한다.
// 빌드 전
fetch(process.env.NEXT_PUBLIC_API_BASE_URL + "/api")
// 빌드 후 번들
fetch("https://prod.api.com/api")따라서 runner 스테이지에 NEXT_PUBLIC_* ENV가 없는 것은 의도적 설계다. 이미 번들에 구워져 있으므로 런타임 ENV는 무의미하다.
3단계(deps → builder → runner)가 모두 실행된다면 단일 스테이지보다 리소스 낭비가 심하지 않은가?
낭비처럼 보이지만 실제로는 낭비가 아니다. Docker 레이어 캐시 덕분이다.
소스코드만 수정했을 때:
deps 스테이지: package.json 안 바뀜 → 캐시 hit (0초)builder 스테이지: node_modules 캐시 hit, pnpm build만 재실행runner 스테이지: 빌드 결과물 복사만최종 이미지는 마지막 스테이지(runner)만 남는다. deps와 builder는 중간 과정에서 사용 후 버려진다.
deps 스테이지 분리의 목적은 이미지 크기가 아닌 캐시 최적화다:
# deps 없이 builder에서 작성했을 경우
COPY . . # 소스코드가 바뀌면 아래 pnpm install도 재실행
RUN pnpm install # 불필요한 재실행 발생deps를 분리하면 package.json이 바뀌지 않는 한 pnpm install 캐시가 항상 유효하다.
.dockerignore에 .env.docker 미제외 (보안 — 수정 필요).gitignore는 .env* 패턴으로 전부 제외하지만, .dockerignore는 .env*.local만 제외한다. COPY . . 시 .env.docker(실제 API 키 포함)가 이미지에 포함될 수 있다.
# .dockerignore에 추가 필요
.env*Dockerfile.dev에 API 키 하드코딩 (보안 — 수정 필요)ARG NEXT_PUBLIC_KAKAOMAP_KEY="67e2193950f3313798d669596a26b9f6"카카오맵 키가 git에 올라가는 파일에 평문으로 노출되어 있다. Dockerfile처럼 기본값 없이 --build-arg로 주입받는 방식으로 통일해야 한다.
next.config.ts의 output: "standalone" 설정과 runner 스테이지의 COPY 경로가 일치deps 스테이지로 pnpm install 캐시 분리runner 스테이지에 불필요한 NEXT_PUBLIC_* ENV 없음 (의도적 설계)