Skip to content

多阶段构建

多阶段构建(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 .

这种实现有如下好处:

  1. 调试特定生成阶段
  2. 可以指定调试(debug)阶段(启用所有调试符号或工具)和生产(production)阶段
  3. 指定测试(test)阶段,在这个阶段中,应用程序将被测试数据填充;同时指定生产阶段,使用真实数据进行测试

使用外部镜像

使用多阶段构建时,不局限于从先前在Dockerfile中创建的阶段进行复制。可以使用COPY --from指令从单独的镜像复制,可以使用本地镜像名、本地或Docker注册表上可用的标记或标记IDDocker客户端在必要时提取镜像并从中复制工件。语法如下:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

相关阅读