와, 저도 그 심정 너무 잘 알아요.
도커 처음 시작하면 이게 정말 끝이 안 보이는 것 같거든요.
'이거만 이해하면 끝이다' 싶다가도, 막상 실제 환경에서 돌리려고 하면 또 새로운 종류의 에러가 나타나서 좌절할 때가 한두 번이 아니었어요.
질문자님이 말씀하신 '국룰 실수'들, 저도 여러 번 겪어봐서 체감하는 거라 좀 구체적으로 정리해 봤어요.
혹시라도 질문자님이나 다른 분들께 도움이 될 만한 '함정' 같은 것들 위주로 작성해 봤는데, 너무 방대할 수도 있으니 참고만 해주시면 좋을 것 같아요.
일단 크게 몇 가지 카테고리로 나눠서 설명드릴게요.
나중에 나만의 체크리스트로 만드시면 좋을 것 같아요.
--- ###
1.
이미지 빌드(Dockerfile 관련)에서 가장 흔하게 걸리는 함정들 여기서 제일 많이 헷갈리는 부분이 '빌드 시점'과 '런타임 시점'의 차이예요.
이걸 명확히 구분하는 게 중요합니다.
1.1.
환경 변수 처리 문제 (가장 흔함) * 흔한 실수: Dockerfile 내에서 ENV 명령어로 환경 변수를 설정했는데, 실제 docker run 할 때 필요한 값이 누락되는 경우.
- 실제 문제: 개발 환경에서는 로컬 PC에 설정된 환경 변수(예:
.env 파일)를 참조해서 돌렸는데, 컨테이너 내부에서는 그 변수를 못 찾아서 에러가 나는 경우.
- 팁: 컨테이너 내부에서 동작하는 모든 환경 변수(API 키, DB 접속 정보 등)는 절대로
Dockerfile에 하드코딩하지 마세요.
- 해결책:
docker run -e KEY=VALUE 또는 docker compose 파일의 environment 섹션을 통해 외부에서 주입하는 것이 정석입니다.
빌드 시점에 필요한 변수가 있다면 build-args를 사용해야 해요.
1.2.
WORKDIR과 상대 경로 문제 * 흔한 실수: COPY나 RUN 명령에서 경로를 지정할 때, 컨테이너 내부의 현재 작업 디렉토리(WORKDIR)를 제대로 고려하지 않는 경우.
- 예시:
WORKDIR /app으로 설정해 놓고, COPY ./src /app/src 이렇게 하면, 로컬의 현재 디렉토리 구조가 그대로 따라가지만, 만약 COPY 할 때 소스 코드가 특정 하위 폴더에 있다면 경로 지정이 꼬일 수 있어요.
- 체크 포인트:
COPY할 때 소스 코드의 절대 경로를 기준으로 생각하고, WORKDIR을 설정했다면 그 내부에서 모든 작업이 일어난다고 가정하고 테스트해보세요.
- 추가 팁: 빌드할 때 필요한 모든 파일을
COPY하기 전에, 필요한 모든 파일이 빌드 컨텍스트(docker build를 실행하는 폴더)에 있는지 한 번 확인하는 습관을 들이는 게 좋아요.
1.3.
기본 이미지(Base Image) 선택의 함정 * 흔한 실수: 너무 무거운 이미지를 선택하는 경우 (예: ubuntu:latest만 쓰는 경우).
- 실제 문제: 웹 서비스만 돌리는데 운영체제 레벨의 라이브러리나 패키지 관리 시스템까지 다 깔려있으면, 이미지가 커지고, 불필요한 공격 표면(Attack Surface)만 넓혀집니다.
- 추천 기준: * Python/Node.js: 공식 런타임 이미지 사용을 강력 추천합니다.
(예: python:3.11-slim 또는 node:18-alpine).
-slim이나 -alpine 태그를 붙이면 용량이 확 줄어듭니다.
- 정적 파일/Nginx:
nginx:stable-alpine처럼 목적에 맞는 경량 이미지를 쓰는 게 최고예요.
--- ###
2.
실행(docker run / docker compose) 단계에서 부딪히는 벽 빌드가 성공적으로 끝났다고 해서 끝난 게 아니에요.
실행 단계에서 네트워크나 볼륨 문제가 꼬이는 경우가 진짜 많아요.
2.1.
포트 매핑의 오해 (가장 많이 실수하는 부분 중 하나) * 흔한 실수: 로컬에서 8000번 포트로 개발 서버를 띄우고, docker run -p 8000:8000 이렇게 하면 되는 줄 아는 경우.
- 실제 문제: 컨테이너 내부의 서비스가 실제로 몇 번 포트로 바인딩되어 돌아가고 있는지를 모르는 경우.
- 체크 포인트: 애플리케이션 코드(예: Flask, Express 등)에서 서버를 띄울 때,
host와 port를 어떻게 지정했는지 확인해야 해요.
만약 코드가 localhost:3000에서만 바인딩하도록 되어 있다면, 컨테이너 내부에서 0.0.0.0:3000으로 바인딩해주어야 외부에서 접근이 가능합니다.
- 핵심: 컨테이너 내부의 애플리케이션이
0.0.0.0 주소에 바인딩되어 동작하도록 코드를 수정하는 게 가장 근본적인 해결책일 때가 많습니다.
2.2.
볼륨 마운트(Volume Mounting)의 함정 * 흔한 실수: 로컬 개발 디렉토리를 컨테이너 내부의 특정 경로에 -v /local/path:/container/path로 마운트하는 것.
- 실제 문제: 개발할 때는 이게 편하지만, 배포 환경(혹은 CI/CD)에서는 로컬 경로가 존재하지 않거나 접근 권한이 달라져서 빌드/실행이 실패하는 경우.
- 주의점: 볼륨 마운트는 개발 환경에서 디버깅 목적으로만 사용하고, 프로덕션 배포용 이미지에는 포함시키지 않는 것이 좋습니다.
프로덕션에서는 이미지 자체에 필요한 파일들을 완전히 포함시키는 게 안전합니다.
2.3.
네트워크 오해 (브릿지 vs 호스트) * 질문: "왜 다른 컨테이너랑 통신이 안 되죠?" * 답변: 기본적으로 도커는 각 컨테이너를 격리된 네트워크(브릿지)에 띄웁니다.
통신을 하려면 네트워크를 별도로 정의하고, 각 서비스들을 그 네트워크에 연결(docker-compose.yml의 networks: 섹션 활용)해야 합니다.
- 추천: 개인 프로젝트라도, 나중에 여러 서비스(DB, API, Worker 등)를 엮을 계획이라면, 항상
docker-compose.yml을 사용하고, 명시적으로 네트워크를 정의하는 연습을 하세요.
이게 나중에 복잡한 서비스 아키텍처를 만들 때 기반이 됩니다.
--- ###
️ 3.
실무에서 체감하는 '흐름 개선 팁' (Docker Compose 활용) 초보자 시기에는 docker build -> docker run을 반복하다가 CLI 옵션 지옥에 빠지기 쉬워요.
이럴 때는 무조건 docker-compose.yml로 갈아타는 것을 강력히 추천합니다.
3.1.
docker-compose.yml의 역할 이해하기 * 이 파일은 단순히 서비스를 띄우는 명령어 모음이 아니라, **'이 프로젝트가 어떤 구성 요소(DB, 백엔드, 프론트엔드 등)로 이루어져 있고, 이들 간의 의존성이 무엇인지'**를 정의하는 설계도 역할입니다.
docker-compose up --build 한 번만 치면, 의존성 순서에 맞춰 모든 것이 한 번에 빌드되고 실행됩니다.
3.2.
데이터베이스 컨테이너 관리 * DB 컨테이너를 돌릴 때, 데이터를 잃어버리는 경우가 정말 많아요.
- 필수 습관:
docker-compose.yml 파일에 **volumes:**를 이용해 DB 데이터 디렉토리를 반드시 호스트 머신에 영구적으로 바인딩하세요.
environment: # ...
환경 변수 설정 ``` 이렇게 해야 컨테이너를 지웠다가 다시 띄워도 데이터가 남아있습니다.
**3.3.
헬스 체크(Healthcheck) 활용하기** * 서비스가 떴다고 끝이 아니에요.
DB 컨테이너가 떴지만, 내부에서 초기화 스크립트가 돌아가는 데 시간이 걸릴 수 있잖아요?
* `docker-compose`에는 `healthcheck`라는 기능이 있습니다.
이 기능을 사용해서 "이 컨테이너가 정상적으로 요청을 받고 서비스할 준비가 되었는지"를 주기적으로 체크하게 만들면, 서비스 간의 시작 순서나 의존성 문제를 훨씬 줄일 수 있습니다.
(특히 DB나 캐시 서버 같은 경우 필수적입니다.) --- ### 📝 요약 정리 및 마지막 조언 만약 지금 당장 가장 먼저 점검해야 할 3가지 포인트를 뽑자면 이거예요.
1.
**환경 변수:** 모든 민감 정보는 `docker-compose`의 `environment` 섹션에서 주입하고, `Dockerfile`에 쓰지 않는다.
2.
**포트 바인딩:** 애플리케이션 코드가 `0.0.0.0`에 바인딩되어 동작하는지 확인한다.
3.
**영속성:** 데이터가 필요한 모든 서비스(DB, 파일 저장소 등)는 `volumes`를 통해 호스트와 연결한다.
도커는 처음에는 추상적인 개념이라 어려운데, 실제로 무언가 *돌아가게* 만들고 나면 '아, 이렇게 동작했구나'라는 경험적 지식이 쌓이면서 감이 확 잡히더라고요.
에러 메시지 자체에 엄청난 힌트가 들어있으니, 에러 메시지를 통째로 복사해서 구글링하는 걸 습관화하는 것만으로도 학습 속도가 2배 이상 빨라지실 거예요.
너무 좌절하지 마시고, 작은 서비스 하나라도 '완전히' 띄워보고, 그 과정에서 겪은 모든 에러들을 노트에 정리해보세요.
그 노트가 나중에 정말 값진 '나만의 배포 가이드'가 될 겁니다.
응원할게요!
👍