이미지를 처음 만들 때 가장 자주 막히는 것이 둘이다. 컨테이너가 무엇을 실행할지 정하는 CMD 와 ENTRYPOINT 의 관계, 그리고 COPY 가 어디를 기준으로 파일을 찾는지 정하는 빌드 컨텍스트다.
| 명령 | 역할 | 실행 인자로 덮어쓰기 |
|---|---|---|
CMD |
기본 명령 또는 기본 인자 | docker run <이미지> <명령> 으로 바뀐다 |
ENTRYPOINT |
고정된 실행 파일 | --entrypoint 를 줘야 바뀐다 |
둘을 함께 쓰면 ENTRYPOINT 가 실행 파일, CMD 가 기본 인자가 되어 실행할 대상은 고정하면서 인자만 바꿔 쓸 수 있다.
ENTRYPOINT ["python3"]
CMD ["app.py"]
docker run 이미지 는 python3 app.py 를, docker run 이미지 other.py 는 python3 other.py 를 실행한다.
두 명령 모두 ["a", "b"] 형태(exec 형식)로 쓴다. CMD python3 app.py 처럼 문자열(shell 형식)로 쓰면 /bin/sh -c 가 껴들어 PID 1 이 셸이 되고, 그러면 docker stop 이 보내는 SIGTERM 이 실제 프로세스에 전달되지 않아 종료가 10초 타임아웃 뒤 강제 종료로 끝난다.
CMD 나 ENTRYPOINT 를 여러 번 쓰면 마지막 하나만 남는다. 한 컨테이너에서 두 서비스를 띄우려고 CMD 를 두 줄 쓰는 것은 동작하지 않는다. 그런 구성이 정말 필요하면 별도 컨테이너로 나누거나 supervisor 를 PID 1 로 둔다.
환경 변수를 덧붙이거나 준비 작업을 한 뒤 본 프로세스를 띄우려면 entrypoint 스크립트를 두고 마지막을 exec "$@" 로 맺는다. exec 가 있어야 스크립트가 본 프로세스로 대체되어 PID 1 을 넘겨준다.
#!/bin/sh
set -e
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/mylibs"
exec "$@"
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/opt/app/bin/start"]
ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/opt/mylibs" 처럼 Dockerfile 에서 직접 덧붙일 수도 있다. compose 의 environment: 로 주면 기존 값을 덮어쓰므로 덧붙이려면 entrypoint 쪽에서 처리해야 한다.
docker build 의 마지막 인자가 빌드 컨텍스트다. 여기에 있는 파일만 데몬으로 전송되고, COPY 는 그 안에서만 경로를 찾는다. COPY ../app /opt/app 처럼 컨텍스트 바깥을 가리키면 forbidden path outside the build context 로 실패한다.
소스는 app/ 에, Dockerfile 은 docker/ 에 두는 구조라면 컨텍스트를 상위로 잡고 -f 로 Dockerfile 위치를 지정한다.
# 프로젝트 루트에서
docker build -f docker/Dockerfile -t my-app .
# docker/Dockerfile — 경로는 컨텍스트 루트 기준
COPY app /opt/app
두 디렉터리를 한 저장소에 함께 두면 버전이 어긋나지 않는다. 저장소를 나눠야 한다면 CI 단계에서 한 작업 디렉터리로 모은 뒤 빌드한다.
컨텍스트가 크면 전송만으로 빌드가 느려진다. .dockerignore 에 .git, 빌드 산출물, 로컬 가상환경을 넣어 둔다.