build stage는 Dockerfile의 FROM
명령어로 표현됨
FROM
: base image로부터 새로운 build stage를 생성
docker build --tag=buildme .
docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c021c8a7051f 5 seconds ago 150MB
이전 포스트의 multi-stage를 사용하지 않은 Dockerfile로, 컴파일 시에 사용된 resource들의 영향으로 image의 크기가 매우 커진 것을 보여준다
이 이미지를 multi-stage를 사용하여 크기와 빌드 속도를 높혀보자
FROM
명령어를 사용하여 build-time과 run-time stage를 구분할 수 있다
multi-stage build를 사용하면 build-time과 run-time에 서로 다른 base images를 선택할 수 있다.
build-time stage의 결과로 생성된 build artifacts(실행 가능한 파일, 라이브러리, 구성 파일 등을 포함)를 run-time stage로 복사할 수 있다
아래의 Dockerfile에서는 scratch
이미지를 사용하여 run-time stage를 생성한다. 예제의 scratch
stage에서는 COPY --from=0
를 통해 이전 stage에서 빌드된 이진파일이 새로운 stage의 파일시스템에 복사된다. COPY --from=0
는 첫 번째 빌드단계의 stage를 가리킨다
FROM scratch
빈 이미지로 base image 또는 초경량 이미지(only contains a single binary)의 빌드에 사용됨
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
+
+ FROM scratch
+ COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
build된 이미지의 크기를 살펴보면 binaries 만을 포함하여, 크기가 현저하게 줄어듦을 확인할 수 있다
docker build --tag=buildme .
docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 436032454dd8 7 seconds ago 8.45MB
parallelism을 사용하면 이미지 빌드 속도를 높힐 수 있다
stage를 나누는 것으로 binary-building step을 분리할 수 있다
FROM image AS stage_name
을 사용하여 stage에 이름 부여 가능, 다른 FROM
명령어에 있는 stage 이름을 통해 stage를 참조하는 것이 가능.
아래의 예제에서는 FROM base AS build-client
를 통해 base stage를 참조하여 두 번째 build-time stage를 실행
COPY --from=stae_name
명령어에서 볼 수 있듯, binary-building stage에도 stage 이름을 지정 가능
# syntax=docker/dockerfile:1
- FROM golang:1.21-alpine
+ FROM golang:1.21-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
+
+ FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
+
+ FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch
- COPY --from=0 /bin/client /bin/server /bin/
+ COPY --from=build-client /bin/client /bin/
+ COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
위와 같이 Dockerfile을 구성하면 client
측과 server
측 이진파일의 빌드가 하나하나씩 이루어 지지 않고, build-client
와 build-server
이 병렬적으로 실행된다
하나의 Dockerfile로 서로 다른 여러 개의 이미지를 만들 수도 있다
--target
flag를 통해 target stage를 명시한다
여기서는 이름을 지정하지 않았던 FROM scratch
stage를 client
server
라는 이름으로 stage를 분리한다
# syntax=docker/dockerfile:1
FROM golang:1.21-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
- FROM scratch
- COPY --from=build-client /bin/client /bin/
- COPY --from=build-server /bin/server /bin/
- ENTRYPOINT [ "/bin/server" ]
+ FROM scratch AS client
+ COPY --from=build-client /bin/client /bin/
+ ENTRYPOINT [ "/bin/client" ]
+ FROM scratch AS server
+ COPY --from=build-server /bin/server /bin/
+ ENTRYPOINT [ "/bin/server" ]
이 Dockerfile을 실행하면 다음과 같이 2개의 이미지가 생성된다
docker build --tag=buildme-client --target=client .
docker build --tag=buildme-server --target=server .
docker images "buildme*"
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-client latest 659105f8e6d7 20 seconds ago 4.25MB
buildme-server latest 666d492d9f13 5 seconds ago 4.2MB
build target을 사용하면 필요한 stage들만 빌드하고 나머지는 생략할 수 있다
Multi-stage build는 생성되는 이미지가 너무 큰 용량을 차지하지 않도록 도와주고 더 빠르게 동작하도록 해준다
참고자료:
https://docs.docker.com/build/guide/multi-stage/
https://hub.docker.com/_/scratch/