"내 컴퓨터에선 되는데?" - 도커(Docker) 기초와 이미지 최적화

댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
분명 내 로컬에서는 잘 돌아갔다. 그런데 서버에 올리니까 안 된다. 개발하다 보면 누구나 한 번쯤 겪는 상황이다.
원인은 단순하다. 개발 환경과 실행 환경이 다르기 때문이다. Node.js 버전, 운영체제, 설치된 패키지 — 어느 하나만 달라도 문제가 생길 수 있다. 도커는 이 환경 차이를 줄이기 위해 사용하는 도구다.
이 글에서는 도커의 기본 개념부터 이미지 최적화까지, Next.js 예시를 기반으로 정리한다.
로컬에서 개발하고 서버에 배포하는 과정에서 환경 차이는 자연스럽게 발생한다. 팀원 간에도 OS나 런타임 버전이 다를 수 있고, CI/CD 환경은 또 다르다.
이 차이가 쌓이면 "누구는 되고 누구는 안 되는" 상황이 반복된다. 배포 직전에 환경 문제를 잡느라 정작 중요한 시간을 허비하게 된다.
핵심은 코드만 옮기는 게 아니라 실행 환경까지 함께 옮기는 것이다. 도커는 이걸 가능하게 한다. 한 번 정의한 환경을 어디서든 동일하게 재현할 수 있다.
도커는 애플리케이션 실행에 필요한 환경을 하나로 묶어주는 도구다. 코드뿐 아니라 런타임, 라이브러리, 설정 파일까지 함께 패키징한다. "이 앱은 이 환경에서 실행된다"를 파일로 정의하는 방식이라고 보면 된다.
이렇게 정의한 환경을 누구든 그대로 실행할 수 있다. 로컬, 서버, CI — 환경이 달라도 동작이 같아진다.
이미지는 실행 환경을 스냅샷으로 찍어둔 것이고, 컨테이너는 그 이미지를 실제로 실행한 인스턴스다. 이미지 하나로 컨테이너를 여러 개 만들 수 있고, 컨테이너는 필요할 때 생성하고 필요 없으면 삭제하면 된다.
Dockerfile은 이미지를 만드는 레시피다. 베이스 이미지 선택, 파일 복사, 의존성 설치, 실행 명령어 등을 순서대로 정의한다. 실행 환경을 코드로 관리할 수 있기 때문에, 별도의 설치 가이드 없이 Dockerfile 하나로 환경 구성을 파악할 수 있다.
이미지가 크면 레지스트리에 push/pull하는 시간이 길어진다. 배포 주기가 짧을수록 이 차이가 체감된다. 이미지가 가벼우면 배포 파이프라인 전체가 빨라진다.
Dockerfile을 대충 작성하면 테스트 파일, 로컬 설정, 캐시, 문서 파일까지 전부 이미지에 들어간다. 개발에 필요한 것과 실행에 필요한 것은 다르다. 실행에 필요한 것만 남기는 것이 최적화의 시작이다.
빌드 단계와 실행 단계를 분리하는 방식이다. 앞 단계에서 애플리케이션을 빌드하고, 마지막 단계에는 실행에 필요한 결과물만 복사해온다. 빌드 환경의 무거운 도구들이 최종 이미지에 포함되지 않는다.
가장 기본적인 형태는 이렇다.
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
동작은 하지만 문제가 있다. 모든 파일을 통째로 복사하고, devDependencies까지 포함되며, npm install은 lock 파일을 무시할 수 있어서 환경마다 결과가 달라질 수 있다. 일반적으로 npm ci를 사용하는 것이 더 안정적이다.
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "start"]
각 단계의 역할은 다음과 같다.
| 단계 | 역할 |
|---|---|
| deps | 의존성 설치 |
| builder | 애플리케이션 빌드 |
| runner | 프로덕션 실행 환경 |
deps에서 설치한 node_modules를 builder에서 재사용하고, runner에서는 빌드 결과물과 프로덕션 의존성만 가져온다. 빌드 도구, 소스 코드, devDependencies는 최종 이미지에 포함되지 않는다.
Dockerfile만큼 중요하지만 놓치기 쉬운 파일이다. 빌드 컨텍스트에서 불필요한 파일을 제외하면 COPY 단계부터 낭비를 줄일 수 있다.
node_modules
.git
.next
*.log
.env
README.md
이 파일이 없으면 로컬의 node_modules이나 .git 디렉토리까지 이미지에 들어갈 수 있다. 이미지 크기도 문제지만, 불필요한 파일이 포함되면 보안 측면에서도 좋지 않다.
node:20-alpine처럼 경량 이미지를 기본으로 쓴다.**.dockerignore 관리** — 빌드 컨텍스트에 불필요한 파일이 포함되지 않도록 한다.이 세 가지만 적용해도 이미지 크기와 배포 안정성이 눈에 띄게 달라진다.
"내 컴퓨터에선 되는데?"는 환경 차이에서 시작된다. 도커는 그 차이를 없애주는 도구이고, 이미지 최적화는 그 도구를 실용적으로 쓰기 위한 기본기다.
처음부터 Kubernetes나 복잡한 오케스트레이션을 고민할 필요는 없다. Dockerfile 하나를 제대로 작성하는 것부터 시작하면 된다. 지금 작업 중인 프로젝트의 Dockerfile을 열어서, 실행에 꼭 필요한 것만 남아있는지 한 번 확인해보자.