Dockerfile实践

文章目录

1、多阶段构建

制作docker镜像时,遵循越小越好,尽量剔除不需要的内容。比如编译环境,程序编译完成之后就不需要了;所以引入多阶段构建剔除不需要的内容。

Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许一个Dockerfile中出现多个 FROM 指令。

多个 FROM 指令的意义:

多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条 FROM 为准,之前的FROM 会被抛弃,那么之前的FROM 又有什么意义呢?

每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

最大的使用场景是将编译环境和运行环境分离,比如,之前我们需要构建一个Go语言程序,那么就需要用到go命令等编译环境。

(1)准备工作:

bash 复制代码
# 创建一个目录
mkdir example3
cd example3
# 下载nginx源码包,作为素材
curl https://nginx.org/download/nginx-1.21.6.tar.gz > ./nginx-1.21.6.tar.gz
# 下载app代码
git clone https://gitee.com/nickdemo/helloworld

(2)多阶段构建dockerfile:

bash 复制代码
FROM golang:1.18
WORKDIR helloworld
COPY ./helloworld ./
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
RUN pwd

FROM alpine:latest
MAINTAINER fly
ENV env1=v1
ENV env2=v2
LABEL myhello 1.0.0
LABEL env prod
COPY --from=0 /go/helloworld/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

(3)也可以通过as关键词,为构建阶段指定别名,可以提高可读性:

bash 复制代码
FROM golang:1.18 as s0
WORKDIR helloworld
COPY ./helloworld ./
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
RUN pwd

FROM alpine:latest
MAINTAINER fly
ENV env1=v1
ENV env2=v2
LABEL myhello 1.0.0
LABEL env prod
COPY --from=s0 /go/helloworld/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

(4)构建镜像:

bash 复制代码
docker build -t hello:1.0.0 -f Dockerfile .

执行结果:

bash 复制代码
fly@fly:~/workspace/example01$ docker build -t hello:1.0.0 -f Dockerfile .
Sending build context to Docker daemon  5.477MB
Step 1/15 : FROM golang:1.18
1.18: Pulling from library/golang
32de3c850997: Pull complete 
fa1d4c8d85a4: Pull complete 
c796299bbbdd: Pull complete 
81283a9569ad: Pull complete 
c768848b86a2: Pull complete 
160a777925fe: Pull complete 
1be94824532a: Pull complete 
Digest: sha256:00d63686b480f6dc866e93ddc4b29efa2db03274a687e6495c2cfbfe615d638e
Status: Downloaded newer image for golang:1.18
 ---> fffd0d9a59da
Step 2/15 : WORKDIR helloworld
 ---> Running in 9bdebf03ec48
Removing intermediate container 9bdebf03ec48
 ---> 878591379507
Step 3/15 : COPY ./helloworld ./
 ---> 8815ef01802d
Step 4/15 : RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
 ---> Running in be7311d25498
Removing intermediate container be7311d25498
 ---> fa13f2963dcd
Step 5/15 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
 ---> Running in e21634b2c5d4
go: downloading github.com/gomodule/redigo v1.8.9
go: downloading github.com/spf13/viper v1.12.0
go: downloading github.com/fsnotify/fsnotify v1.5.4
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading github.com/spf13/afero v1.8.2
go: downloading github.com/spf13/cast v1.5.0
go: downloading github.com/spf13/jwalterweatherman v1.1.0
go: downloading github.com/spf13/pflag v1.0.5
go: downloading golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
go: downloading golang.org/x/text v0.3.7
go: downloading github.com/subosito/gotenv v1.3.0
go: downloading github.com/hashicorp/hcl v1.0.0
go: downloading gopkg.in/ini.v1 v1.66.4
go: downloading github.com/magiconair/properties v1.8.6
go: downloading github.com/pelletier/go-toml/v2 v2.0.1
go: downloading gopkg.in/yaml.v3 v3.0.0
go: downloading github.com/pelletier/go-toml v1.9.5
Removing intermediate container e21634b2c5d4
 ---> 6a7e6063f490
Step 6/15 : RUN pwd
 ---> Running in 848c79ebce58
/go/helloworld
Removing intermediate container 848c79ebce58
 ---> 608d482ea60f
Step 7/15 : FROM alpine:latest
latest: Pulling from library/alpine
c158987b0551: Downloading 
latest: Pulling from library/alpine
c158987b0551: Pull complete 
Digest: sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4
Status: Downloaded newer image for alpine:latest
 ---> 49176f190c7e
Step 8/15 : MAINTAINER fly
 ---> Running in d565e1cc271a
Removing intermediate container d565e1cc271a
 ---> 9e9ec63b551d
Step 9/15 : ENV env1=v1
 ---> Running in e0a5042b97c3
Removing intermediate container e0a5042b97c3
 ---> 6dee9ea488b0
Step 10/15 : ENV env2=v2
 ---> Running in da53cf847942
Removing intermediate container da53cf847942
 ---> ddd5b4d87160
Step 11/15 : LABEL myhello 1.0.0
 ---> Running in bba6401b88f1
Removing intermediate container bba6401b88f1
 ---> e1ac5b88bac8
Step 12/15 : LABEL env prod
 ---> Running in 87c738df9400
Removing intermediate container 87c738df9400
 ---> 7ecca9ec9c29
Step 13/15 : COPY --from=0 /go/helloworld/app ./
 ---> ef139abadf70
Step 14/15 : EXPOSE 80
 ---> Running in 47b0d9e3fe01
Removing intermediate container 47b0d9e3fe01
 ---> ca1088ce8cf6
Step 15/15 : CMD ["./app","--param1=p1","--param2=p2"]
 ---> Running in f6e3dae8b214
Removing intermediate container f6e3dae8b214
 ---> 429a1b2d8c89
Successfully built 429a1b2d8c89
Successfully tagged hello:1.0.0

(5)docker images查看镜像。可以看到生成的镜像只有十几兆,不像上一篇中的至少1G多。同时可以看到一个 < none > 的的中间镜像,即s0阶段的镜像;这个镜像是多阶段构建抛弃的不需要的内容。

bash 复制代码
REPOSITORY           TAG       IMAGE ID       CREATED         SIZE
hello                1.0.0     429a1b2d8c89   4 minutes ago   15.9MB
<none>               <none>    608d482ea60f   4 minutes ago   1.08GB
golang               1.18      fffd0d9a59da   2 days ago      965MB
alpine               latest    49176f190c7e   4 weeks ago     7.05MB

(6)运行镜像:

bash 复制代码
docker run -d -p 81:80 --name myhello hello:1.0.0

(7)测试镜像:

bash 复制代码
curl http://localhost:81/ping

测试结果

bash 复制代码
fly@fly:~/workspace/example01$ curl http://localhost:81/ping
ip = 172.17.0.2

2、ADD 与 COPY 指令

(1)ADD 与 COPY 不能拷贝上下文以外的文件。

COPY 命令语法格式:

bash 复制代码
COPY <src> <dest> 			//将上下文中源文件,拷贝到目标文件
COPY prefix* /destDir/ 		//将所有prefix 开头的文件拷贝到 destDir 目录下
COPY prefix?.log /destDir/ 	//支持单个占位符,例如 : prefix1.log、prefix2.log 等

ADD 命令语法:

bash 复制代码
ADD <src> <dest>

ADD 命令除了不能用在 multistage 的场景下,ADD 命令可以完成 COPY 命令的所有功能,并且还可

完成两类的功能:

  1. 解压压缩文件并把它们添加到镜像中,对于宿主机本地压缩文件,ADD命令会自动解压并添加到镜像。
  2. 从 url 拷贝文件到镜像中,需要注意:url 所在文件如果是压缩包,ADD 命令不会自动解压缩。

(2)对于目录而言,COPY 和 ADD 命令具有相同的特点:只复制目录中的内容而不包含目录自身。例如:

bash 复制代码
COPY srcDir /destDir/ //只会将源文件夹srcDir下的文件拷贝到 destDir 目录下

(3)COPY 区别于ADD在于Dockerfile中使用multi-stage。

bash 复制代码
FROM golang:1.18 as stage0
ADD ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
WORKDIR /app/
COPY --from=stage0 /go/src/helloworld/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

示例

(1)下载nginx tar.gz包作为素材。

bash 复制代码
curl https://nginx.org/download/nginx-1.21.6.tar.gz > ./nginx-1.21.6.tar.gz

(2)编写Dockerfile文件。

bash 复制代码
FROM golang:1.18 as stage0
# ADD ./helloworld /go/src/helloworld/
COPY ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
ADD https://nginx.org/download/nginx-1.21.6.tar.gz /soft/
COPY nginx-1.21.6.tar.gz /soft/copy/
ADD nginx-1.21.6.tar.gz /soft/add/
WORKDIR /app/
COPY --from=stage0 /go/src/helloworld/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

(3)构建镜像。

bash 复制代码
docker build -t hello:1.0.0 -f Dockerfile .

(4)运行容器。

bash 复制代码
docker run -d --name hello hello:1.0.0

(5)查看add和copy的文件。

bash 复制代码
docker exec -it hello /bin/sh

注意:目标文件位置要注意路径后面是否带 "/" ,带斜杠表示目录,不带斜杠表示文件名文件名里带有空格,需要再 ADD(或COPY)指令里用双引号的形式标明:

bash 复制代码
ADD "hello world.txt" "/tmp/hello world.txt"

3、CMD指令

CMD 指令有三种格式:

bash 复制代码
# shell 格式
CMD <command>
# exec格式,推荐格式
CMD ["executable","param1","param2"]
# 为ENTRYPOINT 指令提供参数
CMD ["param1","param2"]
  1. CMD 指令提供容器运行时的默认值,这些默认值可以是一条指令,也可以是一些参数。
  2. 一个dockerfile中可以有多条CMD指令,但只有最后一条CMD指令有效。
  3. CMD参数格式是在CMD指令与ENTRYPOINT指令配合时使用,CMD指令中的参数会添加到ENTRYPOINT指令中。一旦dockerfile文件中既存在ENTRYPOINT又存在CMD,那么CMD的作用只是为了提供参数,而不是为了运行指令的,CMD中的参数将作为ENTRYPOINT的默认参数,此外,如果在docker build的时候指定了参数,则ENTRYPOINT最终使用的参数就是docker build指定的参数
  4. 使用shell 和exec 格式时,命令在容器中的运行方式与RUN 指令相同。不同在于,RUN指令在构建镜像时执行命令,并生成新的镜像。
  5. CMD指令在构建镜像时并不执行任何命令,而是在容器启动时默认将CMD指令作为第一条执行的命令。如果在命令行界面运行docker run 命令时指定命令参数,则会覆盖CMD指令中的命令。

示例

bash 复制代码
FROM golang:1.18 as stage0
ADD ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
WORKDIR /app/
COPY --from=stage0 /go/src/helloworld/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

构建镜像和运行:

bash 复制代码
docker build -t hello:1.0.0 .
# 指定启动命令和参数,需要加上./app
docker run -d -p 80:80 hello:1.0.0 ./app --param1=1 --param2=2
# 指定启动命令为sh
docker run -dit -p 80:80 hello:1.0.0 sh

说明:

如果是使用CMD,则在docker run时如果想覆盖dockerfile文件中的参数,必须要加上./app来运行程序

访问:

bash 复制代码
curl http://localhost/print/startup

执行结果:

bash 复制代码
$ curl http://localhost/print/startup
start up params:     param1 = 1 and param2 = 2 

4、ENTRYPOINT指令

ENTRYPOINT指令有两种格式:

bash 复制代码
# shell 格式
ENTRYPOINT <command>
# exec 格式,推荐格式
ENTRYPOINT ["executable","param1","param2"]
  1. ENTRYPOINT指令和CMD指令类似,都可以让容器每次启动时执行相同的命令,但它们之间又有不同。一个Dockerfile中可以有多条ENTRYPOINT指令,但只有最后一条ENTRYPOINT指令有效。
  2. 当使用shell格式时,ENTRYPOINT指令会忽略任何CMD指令和docker run 命令的参数,并且会运行在bin/sh -c中。
  3. 推荐使用exec格式,使用此格式,docker run 传入的命令参数将会覆盖CMD指令的内容并且附加到ENTRYPOINT指令的参数中。
  4. CMD可以是参数,也可以是指令,ENTRYPOINT只能是命令;docker run 命令提供的运行命令参数可以覆盖CMD,但不能覆盖ENTRYPOINT。
  5. 一旦dockerfile文件中既存在ENTRYPOINT又存在CMD,那么CMD的作用只是为了提供参数,而不是为了运行指令的,CMD中的参数将作为ENTRYPOINT的默认参数,此外,如果在docker build的时候指定了参数,则ENTRYPOINT最终使用的参数就是docker build指定的参数。
  6. NTYRPOINT的使用场景是docker run启动程序时需要带上一些参数,使用ENTRYPOINT就不需要在docker run传参数给到ENTRYPOINT时添加./app了。
  7. 可以通过docker run --entrypoint 替换容器的入口程序。

示例

bash 复制代码
FROM golang:1.18 as stage0
ADD ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
WORKDIR /app/
COPY --from=stage0 /go/src/helloworld/app ./
EXPOSE 80
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]

构建和运行:

bash 复制代码
docker build -t hello:1.0.0 .
# 指定启动参数,不需要加上./app
docker run -d -p 80:80 hello:1.0.0 --param1=1 --param2=2
# 指定入口程序为sh
docker run -dit -p 80:80 --entrypoint sh hello:1.0.0

5、build-arg

dockerfile 预定义参数:HTTP_PROXY,http_proxy,HTTPS_PROXY,https_proxy,FTP_PROXY,ftp_proxy,NO_PROXY,no_proxy,ALL_PROXY,all_proxy。预定义参数,可直接在dockerfile中使用,无需使用arg来声明。

bash 复制代码
FROM golang:1.18 as s0
ADD ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
# RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN go env -w GOPROXY=$http_proxy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest as s1
ARG wd label tag
RUN echo $wd,$label,$tag
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL $label $tag
WORKDIR $wd
COPY --from=stage0 /go/src/helloworld/app ./
EXPOSE 80
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]

构建:

bash 复制代码
docker build -t hello:1.0.0 --build-arg "http_proxy=https://proxy.golang.com.cn,https://goproxy.cn,direct" --build-arg "wd=/home/app/" --build-arg "label=myhello" --build-arg "tag=1.0.0" --no-cache .

6、target与cache-from

对于多阶段构建,可以通过--target指定需要重新构建的阶段。--cache-from 可以指定一个镜像作为缓存源,当构建过程中dockerfile指令与缓存镜像源指令匹配,则直接使用缓存镜像中的镜像层,从而加快构建进程。可以将缓存镜像推送到远程注册中心,提供给不同的构建过程使用,在使用前需要先pull到本地。

bash 复制代码
FROM golang:1.18 as s0
ADD ./helloworld /go/src/helloworld/
WORKDIR /go/src/helloworld
RUN go env -w GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest as s1
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
WORKDIR /app
COPY --from=s0 /go/src/helloworld/app ./
EXPOSE 80
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]

构建:

bash 复制代码
# 重新构建s0阶段的镜像,可以将此镜像重新打个tag然后push到远端供多人共享
docker build -t prehello:1.0.0 --target s0 .
# 引用s0阶段的镜像并重新构建s1
docker build -t hello:1.0.0 --cache-from prehello:1.0.0 --target s1 .

说明:

  1. 本地如果不存在s0阶段的镜像的话,引用s0阶段的镜像重新构建s1的时候每次都会重新构建s0,因为本地不存在s0阶段的镜像
  2. 本地如果存在s0阶段的镜像,引用s0阶段的镜像就不需要重新构建s0了

7、onbuild

onbuild指令将指令添加到镜像中,当镜像作为另一个构建的基础镜像时,将触发这些指令执行。触发指令将在下游构建的上下文中执行。注意:onbuild指令不会影响当前构建,但会影响下游构建。

(1)生成自定义基础镜像:

bash 复制代码
FROM golang:1.18
ONBUILD ADD ./helloworld /go/src/helloworld/
ONBUILD WORKDIR /go/src/helloworld
ONBUILD RUN go env -w
GOPROXY=https://proxy.golang.com.cn,https://goproxy.cn,direct
ONBUILD RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

构建:

bash 复制代码
docker build -t mygolang:1.0.0 .

(2)通过自定义基础镜像构建新的镜像:

bash 复制代码
FROM mygolang:1.0.0 as s0
FROM alpine:latest as s1
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
WORKDIR /app
COPY --from=s0 /go/src/helloworld/app ./
EXPOSE 80
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]

构建:

bash 复制代码
docker build -t hello:1.0.0 .
相关推荐
阿里云云原生31 分钟前
深度测评国产 AI 程序员,在 QwQ 和满血版 DeepSeek 助力下,哪些能力让你眼前一亮?
云原生
张晋涛2 小时前
KCD 北京站丨云原生与AI的双向奔赴,超强Speakers阵容公开
云原生·aigc·线下活动
Hellc0073 小时前
使用 Docker 部署 RabbitMQ 并实现数据持久化
docker·rabbitmq·ruby
梦游钓鱼3 小时前
在window终端创建docker容器的问题
运维·docker·容器
钢板兽4 小时前
Java后端高频面经——JVM、Linux、Git、Docker
java·linux·jvm·git·后端·docker·面试
StableAndCalm4 小时前
什么是zookeeper
分布式·zookeeper·云原生
SelectDB技术团队5 小时前
云原生时代的架构革新,Apache Doris 存算分离如何实现弹性与性能双重提升
大数据·数据库·云原生·doris·存算分离
梵法利亚7 小时前
Ubuntu-docker安装mysql
mysql·ubuntu·docker
dessler9 小时前
DeepSeek-进阶版部署(Linux+GPU)
linux·运维·服务器·云原生·大模型·deepseek