多阶段构建
多阶段构建(multi-stage
)功能是从Docker 17.05
开始的。其有利于提升Dockerfile
的可读性和可维护性
之前的构建模式
没有多阶段构建之前,通常将构建和实现分为两个阶段,利用不同的Dockerfile
文件去实现,这有利于最终实现镜像的小体积。比如,利用c++
代码实现Hello World
输出
// main.cpp
#include <iostream>
#include "test.h"
int main() {
Test test;
test.PrintHello();
std::cout << "Hello, World!" << std::endl;
return 0;
// test.h
#ifndef MULTI_TEST_H
#define MULTI_TEST_H
class Test {
public:
void PrintHello();
};
#endif //MULTI_TEST_H
// test.cpp
#include "test.h"
#include <iostream>
void Test::PrintHello() {
std::cout << "Hello World" << std::endl;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(multi)
set(CMAKE_CXX_FLAGS "-static ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_STANDARD 11)
add_executable(app main.cpp test.cpp test.h)
需要一个构建Dockerfile
,在ubuntu:18.04
中实现可执行文件生成
// Dockerfile.build
FROM ubuntu:18.04
LABEL maintainer "zhujian <zjykzj@github.com>"
RUN set -eux && \
apt-get update && apt-get install -f && apt-get install -y make cmake gcc g++
COPY CMakeLists.txt main.cpp test.cpp test.h /app/
WORKDIR /app
RUN cmake . && make
同时需要一个实现镜像,使用最小的Linux
镜像Alpine
FROM alpine:latest
LABEL maintainer "zhujian <zjykzj@github.com>"
WORKDIR /root/
COPY app .
ENTRYPOINT ["./app"]
编写构建脚本build.sh
如下:
#!/bin/sh
echo Building zjykzj/hello:build
docker build -t zjykzj/hello:build . -f Dockerfile.build
docker container create --name extract zjykzj/hello:build
docker container cp extract:/app/app ./app
docker container rm -f extract
echo Building zjykzj/hello:latest
docker build --no-cache -t zjykzj/hello:latest .
rm ./app
执行构建脚本,最后得到实现镜像zjykzj/hello:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
zjykzj/hello latest 58cfb6bb0f00 9 minutes ago 7.83MB
多阶段构建
使用多阶段构建功能后,只需在单个Dockerfile
文件中进行多个镜像的构建
实现
重新编写Dockerfile
如下
FROM ubuntu:18.04
LABEL maintainer "zhujian <zjykzj@github.com>"
RUN set -eux && \
apt-get update && apt-get install -f && apt-get install -y make cmake gcc g++
COPY CMakeLists.txt main.cpp test.cpp test.h /app/
WORKDIR /app
RUN cmake . && make
FROM alpine:latest
LABEL maintainer "zhujian <zjykzj@github.com>"
WORKDIR /root/
COPY --from=0 /app/app .
ENTRYPOINT ["./app"]
构建镜像zjykzj/ubuntu:latest
并输出
$ docker build -t zjykzj/hello:latest .
Sending build context to Docker daemon 7.168kB
Step 1/11 : FROM ubuntu:18.04
---> 72300a873c2c
Step 2/11 : LABEL maintainer "zhujian <zjykzj@github.com>"
---> Using cache
---> d644a4f6bca5
Step 3/11 : RUN set -eux && apt-get update && apt-get install -f && apt-get install -y make cmake gcc g++
---> Using cache
---> fc165b0b23df
Step 4/11 : COPY CMakeLists.txt main.cpp test.cpp test.h /app/
---> Using cache
---> 0e6ba9808946
Step 5/11 : WORKDIR /app
---> Using cache
---> b091ee1234e9
Step 6/11 : RUN cmake . && make
---> Using cache
---> 6ef73f549cbd
Step 7/11 : FROM alpine:latest
---> e7d92cdc71fe
Step 8/11 : LABEL maintainer "zhujian <zjykzj@github.com>"
---> Using cache
---> 52bbfb4873ae
Step 9/11 : WORKDIR /root/
---> Using cache
---> 99524da2aec0
Step 10/11 : COPY --from=0 /app/app .
---> 0a3e22acc9ae
Step 11/11 : ENTRYPOINT ["./app"]
---> Running in 386089f19ccc
Removing intermediate container 386089f19ccc
---> 7f3d4ded3ddd
Successfully built 7f3d4ded3ddd
Successfully tagged zjykzj/hello:latest
$ docker run zjykzj/hello:latest
Hello World
Hello, World!
最终得到的镜像和之前构建的大小一致
zjykzj/hello latest c73f340b5938 3 minutes ago 7.83MB
解析
使用多阶段构建,可以在同一个Dockefile
中执行多次FROM
指令,每个FROM
指令使用不同的基础镜像,每次都会开始新的构建阶段
后面的构建阶段可以从之前的构建镜像中复制文件,比如
COPY --from=0 /app/app .
标识符--from=0
表示从第一个构建镜像中复制
docker build
命令中输入的参数-t zjykzj/hello:latest
指定了最后一个镜像的标记
Successfully built c73f340b5938
Successfully tagged zjykzj/hello:latest
命名构建阶段
默认从上到下按数字标识每个构建阶段,也可以使用AS <name>
指定
FROM ubuntu:18.04 AS builder
后面阶段复制时可以使用构建阶段命名
COPY --from=builder /app/app .
在特定的构建阶段停止
在构建镜像时,不必构建包括每个阶段在内的整个Dockerfile
,可以指定目标构建阶段。下面的构建命令指定了完成builder
阶段构建后停止
$ docker build --target builder -t zjykzj/hello:latest .
这种实现有如下好处:
- 调试特定生成阶段
- 可以指定调试(
debug
)阶段(启用所有调试符号或工具)和生产(production
)阶段 - 指定测试(
test
)阶段,在这个阶段中,应用程序将被测试数据填充;同时指定生产阶段,使用真实数据进行测试
使用外部镜像
使用多阶段构建时,不局限于从先前在Dockerfile
中创建的阶段进行复制。可以使用COPY --from
指令从单独的镜像复制,可以使用本地镜像名、本地或Docker
注册表上可用的标记或标记ID
。Docker
客户端在必要时提取镜像并从中复制工件。语法如下:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf