Dockerfile 技巧——镜像的多阶段构建
这一节来聊聊多阶段构建,以及为什么要使用它。
C语言例子
假如有一个C的程序,我们想用Docker去做编译,然后执行可执行文件。
#include <stdio.h>
void main(int argc, char *argv[])
{
printf("hello %s\n", argv[argc - 1]);
}
本地测试(如果你本地有C环境)
$ gcc --static -o hello hello.c
$ ls
hello hello.c
$ ./hello docker
hello docker
$ ./hello world
hello world
$ ./hello friends
hello friends
$
构建一个Docker镜像,因为要有C的环境,所以我们选择gcc这个image
FROM gcc:9.4
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
ENTRYPOINT [ "/src/hello" ]
CMD []
build和测试
$ docker build -t hello .
Sending build context to Docker daemon 5.12kB
Step 1/6 : FROM gcc:9.4
---> be1d0d9ce039
Step 2/6 : COPY hello.c /src/hello.c
---> Using cache
---> 70a624e3749b
Step 3/6 : WORKDIR /src
---> Using cache
---> 24e248c6b27c
Step 4/6 : RUN gcc --static -o hello hello.c
---> Using cache
---> db8ae7b42aff
Step 5/6 : ENTRYPOINT [ "/src/hello" ]
---> Using cache
---> 7f307354ee45
Step 6/6 : CMD []
---> Using cache
---> 7cfa0cbe4e2a
Successfully built 7cfa0cbe4e2a
Successfully tagged hello:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB
gcc 9.4 be1d0d9ce039 9 days ago 1.14GB
$ docker run --rm -it hello docker
hello docker
$ docker run --rm -it hello world
hello world
$ docker run --rm -it hello friends
hello friends
$
可以看到镜像非常的大,1.14GB
实际上当我们把hello.c编译完以后,并不需要这样一个大的GCC环境,一个小的alpine镜像就可以了。
这时候我们就可以使用多阶段构建了。
FROM gcc:9.4 AS builder
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
FROM alpine:3.13.5
COPY --from=builder /src/hello /src/hello
ENTRYPOINT [ "/src/hello" ]
CMD []
测试
$ docker build -t hello-alpine -f Dockerfile-new .
Sending build context to Docker daemon 5.12kB
Step 1/8 : FROM gcc:9.4 AS builder
---> be1d0d9ce039
Step 2/8 : COPY hello.c /src/hello.c
---> Using cache
---> 70a624e3749b
Step 3/8 : WORKDIR /src
---> Using cache
---> 24e248c6b27c
Step 4/8 : RUN gcc --static -o hello hello.c
---> Using cache
---> db8ae7b42aff
Step 5/8 : FROM alpine:3.13.5
---> 6dbb9cc54074
Step 6/8 : COPY --from=builder /src/hello /src/hello
---> Using cache
---> 18c2bce629fb
Step 7/8 : ENTRYPOINT [ "/src/hello" ]
---> Using cache
---> 8dfb9d9d6010
Step 8/8 : CMD []
---> Using cache
---> 446baf852214
Successfully built 446baf852214
Successfully tagged hello-alpine:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-alpine latest 446baf852214 2 hours ago 6.55MB
hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB
demo latest 079bae887a47 2 hours ago 125MB
gcc 9.4 be1d0d9ce039 9 days ago 1.14GB
$ docker run --rm -it hello-alpine docker
hello docker
$ docker run --rm -it hello-alpine world
hello world
$ docker run --rm -it hello-alpine friends
hello friends
$
可以看到这个镜像非常小,只有6.55MB
Go语言例子
同样的,假如有一个Go的程序,我们想用Docker去做编译,然后执行可执行文件。
package main
import (
"log"
"net/http"
)
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello golang"))
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("start server on [localhost:8080] ...")
http.HandleFunc("/", test)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
本地测试(如果你本地有Golang环境)
$ go build
$ ls
app df dockerfile go.mod main.go
$ ./app
2023/02/15 02:28:18 main.go:14: start server on [localhost:8080] ...
另一个终端
$ curl localhost:8080
Hello golang
构建一个Docker镜像,因为要有Go的环境,所以我们选择golang这个image
FROM golang:alpine3.17 AS builder
COPY main.go /src/app.go
WORKDIR /src
RUN go build app.go
EXPOSE 8080
ENTRYPOINT [ "/src/app" ]
build和测试
$ docker build -t hello-go .
Sending build context to Docker daemon 6.512MB
Step 1/6 : FROM golang:alpine3.17 AS builder
---> 3257bc8ee9f7
Step 2/6 : COPY main.go /src/app.go
---> b0156e003e2d
Step 3/6 : WORKDIR /src
---> Running in 7976422fe214
Removing intermediate container 7976422fe214
---> 122042396c76
Step 4/6 : RUN go build app.go
---> Running in f321f6a73147
Removing intermediate container f321f6a73147
---> 21236778ceee
Step 5/6 : EXPOSE 8080
---> Running in d47b6e2fb836
Removing intermediate container d47b6e2fb836
---> 133988261356
Step 6/6 : ENTRYPOINT [ "/src/app" ]
---> Running in 7f19bd8952b4
Removing intermediate container 7f19bd8952b4
---> 2ccb4f220a22
Successfully built 2ccb4f220a22
Successfully tagged hello-go:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go latest 2ccb4f220a22 19 minutes ago 321MB
golang alpine3.17 3257bc8ee9f7 3 days ago 254MB
$ docker run -p 8080:8080 -it hello-go
2023/02/14 18:16:11 app.go:14: start server on [localhost:8080] ...
$ curl localhost:8080
Hello golang
可以看到镜像也很大,321MB,同样的,我们使用多阶段构建。
FROM golang:alpine3.17 AS builder
COPY main.go /src/app.go
WORKDIR /src
RUN go build app.go
FROM alpine:3.17.0
COPY --from=builder /src/app /src/app
EXPOSE 8080
ENTRYPOINT [ "/src/app" ]
测试
$ docker build -t hello-go-alpine -f ./df .
Sending build context to Docker daemon 6.512MB
Step 1/8 : FROM golang:alpine3.17 AS builder
---> 3257bc8ee9f7
Step 2/8 : COPY main.go /src/app.go
---> 167672dc57ce
Step 3/8 : WORKDIR /src
---> Running in a53f0f84c92d
Removing intermediate container a53f0f84c92d
---> cc8ee771cdbd
Step 4/8 : RUN go build app.go
---> Running in 9e8e575af675
Removing intermediate container 9e8e575af675
---> e8e7c7219cd5
Step 5/8 : FROM alpine:3.17.0
3.17.0: Pulling from library/alpine
c158987b0551: Pull complete
Digest: sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4
Status: Downloaded newer image for alpine:3.17.0
---> 49176f190c7e
Step 6/8 : COPY --from=builder /src/app /src/app
---> 8121bedd9a21
Step 7/8 : EXPOSE 8080
---> Running in 93a02551712d
Removing intermediate container 93a02551712d
---> e91f0c467511
Step 8/8 : ENTRYPOINT [ "/src/app" ]
---> Running in aef94175c85d
Removing intermediate container aef94175c85d
---> f3ee197cba4f
Successfully built f3ee197cba4f
Successfully tagged hello-go-alpine:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go-alpine latest f3ee197cba4f 31 seconds ago 13.6MB
<none> <none> e8e7c7219cd5 46 seconds ago 321MB
hello-go latest 2ccb4f220a22 24 minutes ago 321MB
golang alpine3.17 3257bc8ee9f7 3 days ago 254MB
$ docker run --rm -p 8080:8080 -it hello-go-alpine
2023/02/14 18:42:29 app.go:14: start server on [localhost:8080] ...
现在镜像只有13.6MB
$ curl localhost:8080
Hello golang