간결한 Dockerfile 및 셸 엔트리포인트 작성
다단계 빌드 스크립트와 신호 처리를 지원하는 견고한 엔트리포인트 래퍼 및 구성 템플릿을 작성합니다.
간결한 Dockerfile 및 셸 엔트리포인트 작성은(는) CoddyKit의 무료 Linux Command Line & Bash Scripting Mastery 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Linux Command Line & Bash Scripting Mastery 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 4개의 강의가 포함되어 있습니다.
DevOps에서 경량 Dockerfile이 중요한 이유
프로덕션 환경에서는 Docker 이미지의 모든 메가바이트에 비용이 따릅니다. 다운로드 속도가 느려지고, 공격 표면이 넓어지며, 레지스트리 저장 공간이 낭비됩니다. 견고한 셸 엔트리포인트와 결합한 경량 Dockerfile은 성숙한 DevOps 실천의 상징입니다.
- 다단계 빌드는 빌드 시점의 도구와 최종 실행 이미지를 분리하여 크기를 크게 줄입니다.
- 엔트리포인트 셸 래퍼는 컨테이너를 초기화하는 작은 셸 스크립트입니다. config 파일을 템플릿에서 생성하고, 환경 변수를 검증하며, 신호를 처리한 다음 최종적으로 주 프로세스를 실행합니다.
- 이 둘은 Kubernetes, ECS 및 베어 메탈 환경에서 안정적이고 이식 가능한 컨테이너 워크로드를 구성하는 핵심 기반입니다.
이 레슨에서는 두 가지 분야를 처음부터 끝까지 다루며, 파이프라인에 바로 적용할 수 있는 프로덕션 수준의 패턴을 설명합니다.
다단계 Dockerfile의 구조
다단계 Dockerfile은 여러 FROM 지시문을 사용합니다. 각 단계는 서로 격리된 레이어 집합이며, 필요한 아티팩트만 다음 단계로 복사합니다.
- 0단계(빌더): 컴파일러, 테스트 실행기 및 빌드 종속 항목을 설치합니다.
- 1단계(실행 환경): 최소 구성의 베이스(예:
alpine,distroless)에서 시작하여 컴파일된 바이너리나 앱 번들만 복사합니다. - 명시적으로 복사하지 않는 한 최종 이미지에는
gcc,make또는 소스 코드가 포함되지 않습니다.
COPY에서 --from=<stage>를 사용하면 단계 경계를 넘어 파일을 가져올 수 있습니다. 가독성을 높이고 docker build --target으로 특정 대상을 선택하려면 AS <name>으로 단계 이름을 지정하십시오.
# ---- Stage 0: builder ----
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /app/server ./cmd/server
# ---- Stage 1: runtime ----
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]레이어 최소화와 캐시 무효화
각 RUN, COPY, ADD 지시문은 새 레이어를 만듭니다. 지시문 순서를 잘못 배치하면 빌드 캐시가 불필요하게 무효화되어 CI 파이프라인이 느려집니다.
- 종속 항목 매니페스트(
package.json,go.mod,requirements.txt)를 소스 코드보다 먼저 복사하여 종속 항목 설치가 독립적으로 캐시되도록 하십시오. - 관련 명령을
&&로 연결하고 같은RUN레이어에서 정리하여 중간 레이어에 패키지 캐시가 남지 않도록 하십시오. - 패키지 관리자에서
--no-cache를 사용하고 설치 후 목록 파일을 삭제하십시오.
FROM python:3.12-slim AS builder
WORKDIR /app
# 1. Install deps first (cached until requirements change)
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 2. Copy source (cache busted only when code changes)
COPY src/ ./src/
FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY --from=builder /app/src /app/src
WORKDIR /app
CMD ["python", "-m", "src.main"]BuildKit 보안 비밀과 SSH 전달
비공개 레지스트리, SSH 키 및 API 토큰은 이미지 레이어에 절대 나타나서는 안 됩니다. Docker BuildKit은 두 가지 안전한 메커니즘을 제공합니다.
--secret: 단일RUN단계 내부에 보안 비밀 파일을 마운트하며, 이를 레이어에 포함시키지 않습니다./run/secrets/<id>를 통해 액세스합니다.--ssh: 호스트의 SSH 에이전트 소켓을 빌드에 전달하므로 개인 키를 포함하지 않고도git clone에서 인증할 수 있습니다.
DOCKER_BUILDKIT=1 또는 docker buildx build를 통해 BuildKit을 활성화하십시오. # syntax=docker/dockerfile:1 지시문을 사용하면 이러한 기능을 이용할 수 있습니다.
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
# Mount NPM token as a secret — never stored in the image
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm ci --prefer-offline
# Usage at build time:
# DOCKER_BUILDKIT=1 docker build \
# --secret id=npm_token,src=~/.npmrc-token \
# -t myapp:latest .견고한 엔트리포인트 셸 래퍼 작성
엔트리포인트 셸 래퍼는 Dockerfile에서 ENTRYPOINT로 설정하는 셸 스크립트입니다. 주 프로세스에 제어를 넘기기 전에 실행 환경을 준비하는 역할을 합니다.
구조가 잘 잡힌 셸 래퍼는 다음 순서를 따릅니다.
- 1단계:
set -euo pipefail을 설정하여 오류가 발생하면 즉시 중단되도록 합니다. - 2단계: 필수 환경 변수를 검증하고 이해하기 쉬운 메시지와 함께 즉시 실패하도록 합니다.
- 3단계: 환경 변수에서 config 파일을 템플릿으로 생성합니다.
- 4단계: 정상적인 종료를 위한 신호 처리기를 등록합니다.
- 5단계:
exec "$@"— 셸을 주 프로세스로 교체하여 PID 1이 셸 래퍼가 아니라 애플리케이션이 되도록 합니다.
마지막 exec는 매우 중요합니다. 이 명령이 없으면 Kubernetes나 Docker 실행 환경이 보낸 신호가 자식 프로세스에 전달되지 않습니다.
#!/usr/bin/env bash
set -euo pipefail
# Step 2: validate required env vars
REQUIRED_VARS=(DATABASE_URL APP_SECRET PORT)
for var in "${REQUIRED_VARS[@]}"; do
if [[ -z "${!var:-}" ]]; then
echo "[entrypoint] ERROR: required env var '$var' is not set" >&2
exit 1
fi
done
# Step 3: template config (see next scene)
# Step 4: signal handling (see scene after that)
# Step 5: hand off to CMD
exec "$@"envsubst를 사용한 config 템플릿 생성
envsubst는 GNU gettext 패키지에 포함된 도구이며 Alpine에서는 gettext로 제공됩니다. 이 도구는 템플릿 파일의 ${VAR} 자리 표시자를 현재 환경 변수 값으로 치환합니다.
- 이미지에
*.tmplconfig 템플릿을 포함하고, 엔트리포인트가 시작할 때 이를 렌더링하도록 하십시오. envsubst에 변수 목록을 명시적으로 전달하여 nginx 정규식의 달러 기호처럼 관련 없는 달러 기호가 실수로 확장되지 않도록 하십시오.- 렌더링된 파일은
/tmp와 같은 쓰기 가능한 경로나 전용 config 볼륨에 작성하십시오.
#!/usr/bin/env bash
# Template: /etc/nginx/conf.d/app.conf.tmpl contains:
# server { listen ${NGINX_PORT}; server_name ${SERVER_NAME}; ... }
export NGINX_PORT=${NGINX_PORT:-8080}
export SERVER_NAME=${SERVER_NAME:-localhost}
envsubst '${NGINX_PORT} ${SERVER_NAME}' \
< /etc/nginx/conf.d/app.conf.tmpl \
> /etc/nginx/conf.d/app.conf
echo "[entrypoint] nginx config rendered:"
grep -E 'listen|server_name' /etc/nginx/conf.d/app.conf
exec "$@"신호 처리와 정상적인 종료
Kubernetes, ECS 또는 docker stop으로 컨테이너를 중지하면 SIGTERM을 받습니다. 엔트리포인트 셸 래퍼가 PID 1인데 신호를 전달하지 않으면, 유예 기간이 지난 후 주 프로세스가 SIGKILL로 종료되어 요청이 유실되거나 데이터가 손상될 수 있습니다.
trap을 사용하여 셸 래퍼에서SIGTERM과SIGINT를 가로채십시오.kill -TERM "$child"를 사용하여 자식 PID로 신호를 전달하십시오.wait "$child"를 사용하여 자식 프로세스가 종료될 때까지 대기한 다음 종료 코드를 전달하십시오.- 또는
exec를 사용하여 셸을 완전히 교체할 수 있습니다. 그러면 OS가 자식 프로세스에 직접 신호를 전달하므로 트랩이 필요하지 않습니다. 단순한 경우에 권장되는 패턴입니다.
#!/usr/bin/env bash
set -euo pipefail
# Start main process in background
"$@" &
child=$!
# Forward SIGTERM and SIGINT to the child
trap 'echo "[entrypoint] caught SIGTERM, forwarding..."; kill -TERM "$child"' TERM
trap 'echo "[entrypoint] caught SIGINT, forwarding..."; kill -INT "$child"' INT
# Wait for child to exit and capture its exit code
wait "$child"
exit $?최소 초기화 프로세스로 tini 사용하기
컨테이너가 자식 프로세스를 생성하는 경우(예: 작업자를 포크하는 셸) 좀비 프로세스를 수거할 실제 초기화 프로세스가 필요합니다. tini는 컨테이너를 위해 특별히 설계된 매우 작은 초기화 바이너리입니다.
- 이미지에
tini를 추가하고 엔트리포인트 래퍼로 설정하십시오. - 좀비 자식 프로세스를 수거하고, 신호를 올바르게 전달하며, 자식 프로세스의 상태 코드로 종료합니다.
- Docker에는
docker run --init으로 활성화할 수 있는 tini가 내장되어 있지만, 이미지에 포함하면 실행 환경(Kubernetes, ECS 등)이 달라도 동작을 일관되게 유지할 수 있습니다.
FROM node:20-alpine
# Install tini for proper signal handling and zombie reaping
RUN apk add --no-cache tini
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
# tini wraps CMD; forwards SIGTERM and reaps zombies
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]루트가 아닌 사용자로 실행하기
root(UID 0)로 실행되는 컨테이너는 심각한 보안 위험을 초래합니다. 컨테이너 탈출이 발생하면 호스트에 대한 모든 액세스 권한을 얻게 됩니다. 주 프로세스를 실행하기 전에 항상 권한이 없는 사용자로 전환하십시오.
- Dockerfile에서
addgroup/adduser(Alpine) 또는groupadd/useradd(Debian)를 사용하여 전용 시스템 사용자와 그룹을 생성하십시오. COPY --chown=appuser:appgroup으로 앱 파일의 소유권을 변경하십시오. 별도의RUN chown레이어를 사용하는 것보다 효율적입니다.USER지시문으로 해당 사용자로 전환하십시오. 엔트리포인트와 CMD는 이 사용자를 상속합니다.- Kubernetes의
securityContext.runAsNonRoot: true는 여전히 root로 실행되는 이미지를 시작하지 않습니다.
FROM python:3.12-slim
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app files with correct ownership in a single layer
COPY --chown=appuser:appgroup src/ ./src/
COPY --chown=appuser:appgroup entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER appuser
ENTRYPOINT ["/entrypoint.sh"]
CMD ["python", "-m", "src.main"]이미지의 상태 점검과 준비성 프로브
Kubernetes의 활성 상태 및 준비 상태 프로브는 매니페스트에 정의하지만, 독립 실행형 docker run 및 Docker Compose 환경을 위해 Dockerfile에 HEALTHCHECK를 포함할 수도 있습니다.
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ...- HTTP 서비스에는
curl --fail또는wget -qO-를 사용하고, HTTP가 아닌 데몬에는/dev/tcp/localhost/PORT로 소켓을 점검하십시오. - 필요한 것만 설치하십시오. distroless 이미지에서는 상태 점검만을 위해
curl을 추가하지 말고, 목적에 맞게 만든 프로브 바이너리나 애플리케이션 자체의 상태 점검 바이너리를 사용하십시오.
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/
# Lightweight health check using bash TCP pseudo-device
# (no curl needed — works on any image with bash)
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD bash -c 'exec 3<>/dev/tcp/localhost/80 && echo -e "GET /health HTTP/1.0\r\n" >&3 && cat <&3 | grep -q "200 OK"' || exit 1
EXPOSE 80모든 요소 결합하기: 프로덕션 엔트리포인트
다음은 이 레슨의 모든 패턴을 결합한 완전한 프로덕션 수준의 엔트리포인트 셸 래퍼입니다. 환경 검증, config 템플릿 생성, 신호 전달 및 exec를 통한 제어권 위임을 포함합니다. 이 패턴은 Kubernetes에 배포되는 실제 Node.js, Python 및 Go 마이크로서비스에서 사용됩니다.
- 각 섹션에는 자체적으로 설명하는 템플릿 역할을 하도록 주석이 달려 있습니다.
- 셸 래퍼는 50줄 이내로 유지합니다. 엔트리포인트는 단순하고 감사하기 쉬워야 합니다.
- 마지막
exec "$@"에 주목하십시오. 모든 설정이 끝나면 셸이 애플리케이션 프로세스로 교체되므로 애플리케이션이 PID 1이 되고 모든 OS 신호를 직접 받습니다.
#!/usr/bin/env bash
# entrypoint.sh — production-grade container entrypoint shim
set -euo pipefail
# ── 1. Validate required environment variables ──────────────────
REQUIRED=(DATABASE_URL APP_SECRET PORT LOG_LEVEL)
for var in "${REQUIRED[@]}"; do
[[ -n "${!var:-}" ]] || { echo "[entrypoint] FATAL: $var is not set" >&2; exit 1; }
done
# ── 2. Set safe defaults for optional variables ─────────────────
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-2}
# ── 3. Render config template ───────────────────────────────────
if [[ -f /etc/app/app.conf.tmpl ]]; then
envsubst '${DATABASE_URL} ${PORT} ${LOG_LEVEL} ${HOST}' \
< /etc/app/app.conf.tmpl \
> /etc/app/app.conf
echo "[entrypoint] config rendered at /etc/app/app.conf"
fi
# ── 4. Wait for dependent services (optional, fast) ─────────────
if [[ -n "${WAIT_FOR_HOST:-}" ]]; then
echo "[entrypoint] waiting for ${WAIT_FOR_HOST}:${WAIT_FOR_PORT:-5432}..."
until bash -c "exec 3<>/dev/tcp/${WAIT_FOR_HOST}/${WAIT_FOR_PORT:-5432}" 2>/dev/null; do
sleep 1
done
echo "[entrypoint] dependency ready"
fi
# ── 5. Hand off to CMD (PID 1 becomes the application) ──────────
echo "[entrypoint] starting: $*"
exec "$@"지식 확인: 엔트리포인트의 신호 처리
컨테이너 엔트리포인트 스크립트에서 신호 처리를 얼마나 이해했는지 확인해 보십시오.
복습: 경량 Dockerfile과 셸 엔트리포인트
프로덕션 컨테이너 작성의 전체 구성을 학습했습니다.
- 다단계 빌드는 여러
FROM지시문을 사용하여 컴파일러와 빌드 도구가 최종 이미지에 포함되지 않도록 하고, 가볍고 최소화된 실행 레이어를 만듭니다. - 레이어 순서 — 소스 코드보다 종속 항목 매니페스트를 먼저 복사하면 캐시 적중률이 높아지고 CI 파이프라인이 빨라집니다.
- BuildKit 보안 비밀과 SSH 마운트는 인증된 빌드를 방해하지 않으면서 자격 증명이 이미지 기록에 남지 않도록 합니다.
- 엔트리포인트 셸 래퍼는 환경 변수를 검증하고,
envsubst로 config 파일을 템플릿에서 생성하며,exec "$@"를 통해 애플리케이션에 제어권을 넘깁니다. - 신호 처리에는
exec(앱이 PID 1이 되도록 함)를 사용하거나, 백그라운드 작업이 있을 때 명시적인trap+kill+wait패턴을 사용해야 합니다. - tini는 컨테이너가 여러 프로세스를 생성할 때 좀비 프로세스 수거와 올바른 신호 전달을 제공합니다.
- 루트가 아닌 사용자와
HEALTHCHECK지시문을 추가하면 Kubernetes 프로덕션 워크로드에 적합한 안전하고 관측 가능한 이미지가 완성됩니다.
이 패턴들을 일관되게 결합하면 이미지가 더 작아지고 배포가 빨라지며 프로덕션 환경에서 훨씬 더 견고하게 동작합니다.
자주 묻는 질문
“간결한 Dockerfile 및 셸 엔트리포인트 작성” 강의는 무료인가요?
네 — “간결한 Dockerfile 및 셸 엔트리포인트 작성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Linux Command Line & Bash Scripting Mastery 강의 전체를 잠금 해제할 수 있습니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 4개의 강의가 포함되어 있습니다.
“간결한 Dockerfile 및 셸 엔트리포인트 작성”에서 뭘 배우나요?
다단계 빌드 스크립트와 신호 처리를 지원하는 견고한 엔트리포인트 래퍼 및 구성 템플릿을 작성합니다. 브라우저에서 직접 실행하는 실습 코드로 Linux Command Line & Bash Scripting Mastery을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Linux Command Line & Bash Scripting Mastery을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Linux Command Line & Bash Scripting Mastery은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“간결한 Dockerfile 및 셸 엔트리포인트 작성” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Linux Command Line & Bash Scripting Mastery 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Linux Command Line & Bash Scripting Mastery 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 간결한 Dockerfile 및 셸 엔트리포인트 작성
- envsubst와 heredoc으로 구성 템플릿 만들기
- CLI와 jq를 활용한 클라우드 리소스 스크립팅
- 상태 프로브, 준비 게이트 및 대기 루프