도커파일의 CMD 명령어 라인은 docker run 시에 덮어쓸 수 있다는 특징을 가졌기 때문에(from1) 자칫 optional argument 처럼 사용하기 쉽다. 하지만 이것은 좋은 생각이 아니다. 다음과 같은 상황을 살펴보자.

아래는 main.py 스크립트의 일부이다. 도커 컨테이너는 실행 시 이 파일을 실행하도록 dockerfile 에 ENTRYPOINT ["python3", "main.py"] 와 같이 스립팅되어 있다.

# main.py
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--data-dir', type=str, default='/tmp/mnist_data')
parser.add_argument('--epochs', type=int)
parser.add_argument('--lr', type=float, default=0.001)
parser.add_argument('--use-cuda', action='store_true', default=False)
args = parser.parse_args()

print(args.data_dir)
print(args.epochs)
print(args.lr)
print(args.use_cuda)

위와 같이 argparse 등 명령줄 파싱 라이브러리를 이용해 명령행 인자를 입력받는 형태의 프로그램은 수많은 오픈소스에서 쉽게 찾아볼 수 있다. 위 코드에서 볼 수 있다시피 우리가 사용하려는 스크립트에는 한 개의 필수 인자(--epochs)와 세 개의 선택 인자가 존재한다. 우리의 직관에 따르면 dockerfile 에는 다음과 같은 형태로 CMD 명령어를 사용하게 된다. 아래 CMD 명령어에는 --data-dir 의 기본값인 /tmp/mnist_data 대신 /home/janghoo/dev/data/coco2017/train 를 사용했고, --lr 의 기본값인 0.001 대신 0.005 를 사용했다는 사실에 주목해 보자.

# Dockerfile
ENTRYPOINT ["python3", "main.py"]
CMD ["--data-dir", "/home/janghoo/dev/data/coco2017/train", "--epochs", "100", "--lr", "0.005", "--use-cuda"]
# script
docker run my_container:v0

빌드를 마치고 컨테이너를 실행하면 다음과 같은 출력을 얻을 수 있다.

/home/janghoo/dev/data/coco2017/train
100
0.005
True

여기까지는 아무 문제가 없어 보인다. 다음으로 CMD 의 특징을 이용해 --epochs 인자로 통과시키는 값만을 10000으로 변경해서 실행해 보자.

# script
docker run my_container:v0 --epochs 10000

그 결과는 아래와 같다.

/tmp/mnist_data
10000
0.001
False

CMD 는 어떤 것이 인자 이름이고 어떤 것이 인자 값인지를 구분해낼 수 있는 능력이 없다. 그래서 docker run 을 실행할 때 명령줄을 통해 새로운 값이 들어오는 순간 CMD 의 값을 전부 다 덮어써버리기 때문이다.

조금 더 나은 방법: ENV

이러한 부수현상을 회피하기 위해 환경 변수를 사용하기도 한다(참고1,2). 도커파일에서 ENV 명령어를 이용하면 컨테이너에서 사용할 환경 변수를 등록할 수 있다. 이때 ENV 명령어도 CMD 명령어와 같이 docker run 을 실행할 때 새로운 값으로 덮어쓸 수 있기 때문에 선택적 인자(Optional argument)처럼 사용할 수 있게 된다.

# Dockerfile
# 띄어쓰기에 주의한다.
ENV PYAPP_DATA_DIR="/home/janghoo/dev/data/coco2017/train" \\
    PYAPP_EPOCHS=100 \\
    PYAPP_LR=0.005 \\
    PYAPP_USE_CUDA=1

ENTRYPOINT ["python3", "main.py"]

위와 같이 도커파일에서 환경 변수를 등록하고, 별다른 CMD 명령을 작성하지 않는다. 그리고 파이썬 스크립트에서는 argparse 를 사용하는 대신 환경 변수를 바탕으로 값을 읽어올 수 있도록 코드를 작성한다.

# main.py
import argparse

env_data_dir = os.getenv("PYAPP_DATA_DIR", "/home/janghoo/dev/data/coco2017/train")
env_epochs = os.getenv("PYAPP_EPOCHS", 100)
env_lr = os.getenv("PYAPP_LR", 0.001)
env_use_cuda = bool(int(os.getenv("PYAPP_USE_CUDA", 0)))

parser = argparse.ArgumentParser()
parser.add_argument('--data-dir', type=str, default=env_data_dir)
parser.add_argument('--epochs', type=int, default=env_epochs)
parser.add_argument('--lr', type=float, default=env_lr)
parser.add_argument('--use-cuda', action='store_true', default=env_use_cuda)
args = parser.parse_args()

print(args.data_dir)
print(args.epochs)
print(args.lr)
print(args.use_cuda)
# script
docker run my_container:v0

실행 결과는 다음과 같다.