Dockerfile 技巧——镜像的多阶段构建

这一节来聊聊多阶段构建,以及为什么要使用它。

C语言例子

假如有一个C的程序,我们想用Docker去做编译,然后执行可执行文件。

  1. #include <stdio.h>
  2. void main(int argc, char *argv[])
  3. {
  4. printf("hello %s\n", argv[argc - 1]);
  5. }

本地测试(如果你本地有C环境)

  1. $ gcc --static -o hello hello.c
  2. $ ls
  3. hello hello.c
  4. $ ./hello docker
  5. hello docker
  6. $ ./hello world
  7. hello world
  8. $ ./hello friends
  9. hello friends
  10. $

构建一个Docker镜像,因为要有C的环境,所以我们选择gcc这个image

  1. FROM gcc:9.4
  2. COPY hello.c /src/hello.c
  3. WORKDIR /src
  4. RUN gcc --static -o hello hello.c
  5. ENTRYPOINT [ "/src/hello" ]
  6. CMD []

build和测试

  1. $ docker build -t hello .
  2. Sending build context to Docker daemon 5.12kB
  3. Step 1/6 : FROM gcc:9.4
  4. ---> be1d0d9ce039
  5. Step 2/6 : COPY hello.c /src/hello.c
  6. ---> Using cache
  7. ---> 70a624e3749b
  8. Step 3/6 : WORKDIR /src
  9. ---> Using cache
  10. ---> 24e248c6b27c
  11. Step 4/6 : RUN gcc --static -o hello hello.c
  12. ---> Using cache
  13. ---> db8ae7b42aff
  14. Step 5/6 : ENTRYPOINT [ "/src/hello" ]
  15. ---> Using cache
  16. ---> 7f307354ee45
  17. Step 6/6 : CMD []
  18. ---> Using cache
  19. ---> 7cfa0cbe4e2a
  20. Successfully built 7cfa0cbe4e2a
  21. Successfully tagged hello:latest
  22. $ docker image ls
  23. REPOSITORY TAG IMAGE ID CREATED SIZE
  24. hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB
  25. gcc 9.4 be1d0d9ce039 9 days ago 1.14GB
  26. $ docker run --rm -it hello docker
  27. hello docker
  28. $ docker run --rm -it hello world
  29. hello world
  30. $ docker run --rm -it hello friends
  31. hello friends
  32. $

可以看到镜像非常的大,1.14GB

实际上当我们把hello.c编译完以后,并不需要这样一个大的GCC环境,一个小的alpine镜像就可以了。

这时候我们就可以使用多阶段构建了。

  1. FROM gcc:9.4 AS builder
  2. COPY hello.c /src/hello.c
  3. WORKDIR /src
  4. RUN gcc --static -o hello hello.c
  5. FROM alpine:3.13.5
  6. COPY --from=builder /src/hello /src/hello
  7. ENTRYPOINT [ "/src/hello" ]
  8. CMD []

Note
上面的Dockerfile中第一阶段用于构建,第二阶段用于执行。第一阶段通过AS来取别名,在第二阶段的COPY中就可以使用--from=builder来引用第一阶段编译出来的hello 文件

测试

  1. $ docker build -t hello-apline -f Dockerfile-new .
  2. Sending build context to Docker daemon 5.12kB
  3. Step 1/8 : FROM gcc:9.4 AS builder
  4. ---> be1d0d9ce039
  5. Step 2/8 : COPY hello.c /src/hello.c
  6. ---> Using cache
  7. ---> 70a624e3749b
  8. Step 3/8 : WORKDIR /src
  9. ---> Using cache
  10. ---> 24e248c6b27c
  11. Step 4/8 : RUN gcc --static -o hello hello.c
  12. ---> Using cache
  13. ---> db8ae7b42aff
  14. Step 5/8 : FROM alpine:3.13.5
  15. ---> 6dbb9cc54074
  16. Step 6/8 : COPY --from=builder /src/hello /src/hello
  17. ---> Using cache
  18. ---> 18c2bce629fb
  19. Step 7/8 : ENTRYPOINT [ "/src/hello" ]
  20. ---> Using cache
  21. ---> 8dfb9d9d6010
  22. Step 8/8 : CMD []
  23. ---> Using cache
  24. ---> 446baf852214
  25. Successfully built 446baf852214
  26. Successfully tagged hello-apline:latest
  27. $ docker image ls
  28. REPOSITORY TAG IMAGE ID CREATED SIZE
  29. hello-alpine latest 446baf852214 2 hours ago 6.55MB
  30. hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB
  31. demo latest 079bae887a47 2 hours ago 125MB
  32. gcc 9.4 be1d0d9ce039 9 days ago 1.14GB
  33. $ docker run --rm -it hello-alpine docker
  34. hello docker
  35. $ docker run --rm -it hello-alpine world
  36. hello world
  37. $ docker run --rm -it hello-alpine friends
  38. hello friends
  39. $

可以看到这个镜像非常小,只有6.55MB