컨텐츠로 건너뛰기

효율적으로 작성하는 Docker 컨테이너의 기본 Dockerfile

실제 작동하는 이미지를 기반으로 설명을 풀어가겠습니다. 가벼운 마음으로 읽어보세요 :) 정답은 온몸 비틀기

도커와 컨테이너 기술

  • 앞서 살펴본 바와 같이 도커(Docker)는 컨테이너 기술을 사용하여 애플리케이션을 패키징하고 배포하는 데 사용되는 플랫폼입니다.
  • 도커의 핵심 개념 중 하나는 Dockerfile입니다. Dockerfile은 도커 이미지를 빌드하기 위한 텍스트 파일로, 애플리케이션의 환경을 정의하고 구성하는데 사용됩니다.
  • 이 파일은 애플리케이션을 실행하는 데 필요한 모든 설정, 종속성 및 명령을 정의합니다.
  • 또한 Dockerfile은 셸스크립트랑 별반 다를 것이 없습니다. 따라서 리눅스에 대한 기본적인 지식이 필요합니다. 권한, 사용자, 좀비 프로세스 처리 등등.
  • 서버를 설정하는 방법 그대로 그 순서로 Dockerfile을 만들고 이미지를 만들고 활용 하는 것 이라고 간단히 이해하시면 편하실 겁니다.

Dockerfile의 구조

1. 베이스 이미지 선택

  • 먼저 어떤 기반 이미지를 사용할지를 지정합니다. 베이스 이미지는 미리 구성된 운영 체제 및 소프트웨어 패키지를 포함하고 있습니다. 보통은 공식적으로 제공되는 베이스 이미지를 사용하거나 직접 만들어 사용할 수 있습니다.

2. 환경 설정

  • 애플리케이션을 실행하는 데 필요한 환경을 설정합니다. 이는 환경 변수 설정, 작업 디렉토리 변경, 사용자 및 그룹 설정 등을 포함할 수 있습니다.

3. 애플리케이션 종속성 설치

  • 애플리케이션이 필요로 하는 종속성을 설치합니다. 이는 패키지 관리자를 통해 소프트웨어를 설치하거나 소스 코드를 다운로드하고 컴파일하는 등의 작업을 포함할 수 있습니다.

4. 애플리케이션 파일 추가

  • 애플리케이션의 소스 코드나 실행 파일 등을 이미지에 추가합니다. 이를 통해 이미지에 애플리케이션을 포함할 수 있습니다.

5. 포트 노출 및 명령 정의

  • 애플리케이션이 사용하는 포트를 노출하고, 실행될 명령을 정의합니다. 이는 컨테이너가 실행될 때 자동으로 실행될 명령을 지정하는 CMD 또는 ENTRYPOINT 지시문을 사용하여 수행됩니다.

6. 빌드 및 이미지 생성

  • 사용해서 도커 이미지를 빌드합니다. 이는 docker build 명령어를 사용하여 수행됩니다. Dockerfile에 정의된 각 단계는 캐시되어 이전에 빌드 한 이미지에서 재사용될 수 있으므로, 이미지 빌드 시간을 최소화할 수 있습니다.

직접 뜯어봅시다

제가 만든 이미지 중에서 아키텍쳐 그리고 멀티스테이지 빌드 까지 사용하는 이미지여서 가져와 봤습니다. 앞으로 천천히 알아보겠습니다. 모든 글을 읽고 나면, 아키텍쳐별로, 멀티스테이지 빌드 등 모든 개념을 이해하고 활용하실 수 있습니다.
NavyStack 깃허브 answer-docker

총 3개의 스테이지로 이루어져 있습니다. 사용되는 언어는 Golang, Node.js(JavaScript)입니다. 또한 아키텍쳐별로, 미세하게 조정이 필요했습니다. 최종 이미지에서는 레이어 수를 줄이기 위해 명령어를 하나로 줄을 세우는 모습까지 보여줍니다.

#
FROM node:lts-bookworm AS git
#
ARG TARGETARCH
ARG PLUGIN_LIST_FILE=/incubator-answer/script/plugin_list
WORKDIR /incubator-answer/
RUN git clone --recurse-submodules -j8 --depth 1 \
https://github.com/apache/incubator-answer.git /incubator-answer/
#
RUN case "$TARGETARCH" in \
"arm") \
PLUGIN_LIST="github.com/apache/incubator-answer-plugins/connector-basic@latest \
github.com/apache/incubator-answer-plugins/connector-github@latest \
github.com/apache/incubator-answer-plugins/connector-google \
github.com/apache/incubator-answer-plugins/storage-s3@latest \
github.com/apache/incubator-answer-plugins/editor-formula@latest \
github.com/apache/incubator-answer-plugins/cache-redis@latest";; \
"amd64"|"arm64") \
PLUGIN_LIST="github.com/apache/incubator-answer-plugins/connector-basic@latest \
github.com/apache/incubator-answer-plugins/connector-github@latest \
github.com/apache/incubator-answer-plugins/connector-google \
github.com/apache/incubator-answer-plugins/storage-s3@latest \
github.com/apache/incubator-answer-plugins/editor-chart@latest \
github.com/apache/incubator-answer-plugins/editor-formula@latest \
github.com/apache/incubator-answer-plugins/cache-redis@latest";; \
*) \
echo "Unsupported architecture: $TARGETARCH" && \
exit 1;; \
esac && \
if [ ! -f "$PLUGIN_LIST_FILE" ]; then \
echo "Plugin list file '$PLUGIN_LIST_FILE' not found. Exiting."; \
exit 1; \
fi && \
{ \
cat "$PLUGIN_LIST_FILE"; \
echo "$PLUGIN_LIST"; \
} | sort | uniq > "$PLUGIN_LIST_FILE"
#
FROM node:lts-bookworm AS golang-builder
#
ARG TARGETARCH
ARG GOLANG_VERSION=1.22.0
ARG CGO_EXTRA_CFLAGS
#
ENV PNPM_HOME="/pnpm"
ENV GOPATH="/go"
ENV GOROOT="/usr/local/go"
ENV PACKAGE="github.com/apache/incubator-answer"
ENV PATH="$PNPM_HOME:$GOPATH/bin:$GOROOT/bin:$PATH"
ENV BUILD_DIR ${GOPATH}/src/${PACKAGE}
ENV ANSWER_MODULE ${BUILD_DIR}
COPY --from=git /incubator-answer/ ${BUILD_DIR}
WORKDIR ${BUILD_DIR}
#
RUN case "$TARGETARCH" in \
"arm") \
GO_PKG="go${GOLANG_VERSION}.linux-${TARGETARCH}v6l.tar.gz" && \
wget https://go.dev/dl/$GO_PKG && \
tar -C /usr/local -xzf $GO_PKG && \
rm $GO_PKG && \
corepack enable && \
export NODE_OPTIONS="--max-old-space-size=8096" && \
pnpm add -D -r @swc/core-linux-arm-gnueabihf @swc/core @swc/cli @swc/wasm swc-loader;; \
"amd64"|"arm64") \
GO_PKG="go${GOLANG_VERSION}.linux-${TARGETARCH}.tar.gz" && \
wget https://go.dev/dl/$GO_PKG && \
tar -C /usr/local -xzf $GO_PKG && \
rm $GO_PKG && \
corepack enable ;; \
*) \
echo "Unsupported architecture: $TARGETARCH" && \
exit 1 ;; \
esac && \
make clean build
RUN chmod +x answer
RUN ["/bin/bash","-c","script/build_plugin.sh"]
RUN cp answer /usr/bin/answer
RUN mkdir -p /data/uploads \
&& chmod 777 /data/uploads \
&& mkdir -p /data/i18n \
&& cp -r i18n/*.yaml /data/i18n
#
FROM debian:bookworm-slim AS final
LABEL maintainer=github.com/NavyStack/answer-docker
#
ENV TIMEZONE="Asia/Seoul" \
USER="answer" \
UID="1001" \
GID="1001"
#
RUN groupadd --gid $GID $USER \
&& useradd --uid $UID --gid $GID --home-dir /data/ --shell /bin/bash $USER \
&& mkdir -p /data/ \
&& chown -R $USER:$USER /data/ \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
apt-get -y --no-install-recommends install \
ca-certificates \
curl \
tini \
gettext \
openssh-client \
sqlite3 \
gnupg \
tzdata \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf /usr/share/zoneinfo/$TIMEZONE /etc/localtime \
&& echo "$TIMEZONE" > /etc/timezone
#
COPY --from=golang-builder --chown=$USER:$USER /usr/bin/answer /usr/bin/answer
COPY --from=golang-builder --chown=$USER:$USER /data /data
COPY --from=git --chown=$USER:$USER /incubator-answer/script/entrypoint.sh /entrypoint.sh
WORKDIR /data
USER $USER
#
VOLUME /data
EXPOSE 80
#
ENTRYPOINT ["tini", "--", "/entrypoint.sh"]

수고 많으셨습니다. 감사합니다.


Askfront.com (에스크프론트)

기존의 댓글 대신, 초보자도 자유롭게 질문할 수 있는 포럼을 만들었습니다.
에스크프론트에서는 가이드뿐만 아니라 모든 종류의 질문을 하실 수 있습니다.
검색해도 오래된 정보나 도움이 되지 않는 정보만 나오는 것 같고, 주화입마에 빠진 것 같은 기분이 들 때가 있습니다.
그럴 때, 부담 없이 질문해 주세요 :) 같이 의논하며 생각해봅시다.
가능하다면, 제가 답변 드리겠습니다. 고맙습니다.