Dockerfile是⼀个创建镜像所有命令的⽂本⽂件, 包含了⼀条条指令和说明, 每条指令构建⼀层, 通过docker build命令,根据Dockerfile的内容构建镜像,因此每⼀条指令的内容, 就是描述该层如何构建.有了Dockefile, 就可以制定⾃⼰docker镜像规则,只需要在Dockerfile上添加或者修改指令, 就可⽣成docker 镜像.
docker build命令会读取Dockerfile的内容,并将Dockerfile的内容发送给 Docker 引擎,最终Docker 引擎会解析Dockerfile中的每⼀条指令,构建出需要的镜像。
为了提⾼构建效率,docker build默认会缓存已有的镜像层。如果构建镜像时发现某个镜像层已经被缓存,就会直接使⽤该缓存镜像,⽽不⽤重新构建。如果不希望使⽤缓存的镜像,可以在执⾏docker build命令时,指定--no-cache=true
参数。
Docker 匹配缓存镜像的规则:
遍历缓存中的基础镜像及其⼦镜像,检查这些镜像的构建指令是否和当前指令完全⼀致,如果不⼀样,则说明缓存不匹配。对于ADD、COPY指令,还会根据⽂件的校验和(checksum)来判断添加到镜像中的⽂件是否相同,如果不相同,则说明缓存不匹配。缓存匹配检查不会检查容器中的⽂件。⽐如,当使⽤RUN apt-get -y update命令更新了容器中的⽂件时,缓存策略并不会检查这些⽂件,来判断缓存是否匹配。最后,可以通过docker history命令来查看镜像的构建历史
FROM 设置镜像使⽤的基础镜像
MAINTAINER 设置镜像的作者
RUN 编译竟像时运⾏的脚步
CMD 设置容器的启动命令
LABEL 设置镜像标签
EXPOSE 设置镜像暴露的端⼝
ENV 设置容器的环境变量
ADD 编译镜像时复制上下⽂中⽂件到镜像中
COPY 编译镜像时复制上下⽂中⽂件到镜像中
ENTRYPOINT 设置容器的⼊⼝程序
VOLUME 设置容器的挂载卷
USER 设置运⾏ RUN CMD ENTRYPOINT的⽤户名
WORKDIR 设置 RUN CMD ENTRYPOINT COPY ADD 指令的⼯作⽬录
ARG 设置编译镜像时加⼊的参数
ONBUILD 设置镜像的ONBUILD 指令
STOPSIGNAL 设置容器的退出信号量
一个简单的http服务器,打印启动参数和一些环境变量
hello目录下
printEnv.go
package hello
import (
"fmt"
"net/http"
"os"
)
type EnvParam struct {
}
func (*EnvParam) ServeHTTP(w http.ResponseWriter, r *http.Request) {
env1 := os.Getenv("env1")
env2 := os.Getenv("env2")
fmt.Printf("env list : env1 = %s and env2 = %s", env1, env2)
fmt.Println()
fmt.Fprintf(w, "env list : env1 = %s and env2 = %s", env1, env2)
}
printStartParam.go
package hello
import (
"flag"
"fmt"
"net/http"
)
const (
defaultStartUpParam = "default"
)
var (
param1 = flag.String("param1", defaultStartUpParam, "param1 to hello world")
param2 = flag.String("param2", defaultStartUpParam, "param2 to hello world")
)
func init() {
flag.Parse()
}
type PrintStartParam struct {
}
func (*PrintStartParam) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Printf("start up params: param1 = %s and param2 = %s ", *param1, *param2)
fmt.Println()
fmt.Fprintf(w, "start up params: param1 = %s and param2 = %s ", *param1, *param2)
}
main.go
package main
import (
"fmt"
"httpServer/hello"
"net/http"
)
func main() {
fmt.Println("into main")
http.Handle("/print/env", new(hello.EnvParam))
http.Handle("/print/startup", new(hello.PrintStartParam))
http.ListenAndServe(":80", nil)
// http.ListenAndServeTLS()
}
FROM golang:1.18
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
#也可以用git拉取代码 RUN git clone https://gitee.com/dongyademo/helloworld.git
#这里从上下文中拷贝文件
COPY ./httpServer /go/src/httpServer
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
docker build -t httphello:1.0.0 -f Dockerfile .
docker run -p 8081:80 -d --name testServer httphello:1.0.0
[root@localhost example3]# curl http://localhost:8081/print/env
env list : env1 = env1value and env2 = env2value
查看镜像
[root@localhost example3]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httphello 1.0.0 8ae621256ce2 23 hours ago 988MB
alpine latest f8c20f8bbcb6 2 weeks ago 7.38MB
这破镜像居然有988MB。最终运行的时候就运行一个二进制文件,所以编译时候的环境我们最终运行的时候是基本不需要的。如何缩小打出来的镜像呢呢,可以考虑用多阶段构建
Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许⼀个Dockerfile 中出现多个 FROM 指令。这样做有什么意义呢?
多个 FROM 指令的意义
多个 FROM 指令并不是为了⽣成多根的层关系,最后⽣成的镜像,仍以最后⼀条 FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM ⼜有什么意义呢?
每⼀条 FROM 指令都是⼀个构建阶段,多条 FROM 就是多阶段构建,虽然最后⽣成的镜像只能是最后⼀个阶段的结果,但是,能够将前置阶段中的⽂件拷⻉到后边的阶段中,这就是多阶段构建的最⼤意义。
最⼤的使⽤场景是将编译环境和运⾏环境分离,⽐如,之前我们需要构建⼀个Go语⾔程序,那么就需要⽤到go命令等编译环境
Dockerfile
FROM golang:1.18
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0 # 这个label 不是打包出来的镜像tag,只是config里的字段
WORKDIR /app/
#--from=0代表从阶段0进行操作
COPY --from=0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
除了用--from=0
指定阶段,还可以通过as关键词,为构建阶段指定别名,也提⾼了可读性
FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
构建镜像
docker build -t httphello:1.0.0 -f Dockerfile .
查看镜像,这个时候我们发现打出来的镜像就小很多了,因为是基于一个轻量级linux alpine打包出来的。多阶段构建基于最后一个from来构建
[root@localhost example3]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
helloserver 1.0.2 3eaa5b73a5b4 4 hours ago 13.9MB
httphello 1.0.0 8ae621256ce2 23 hours ago 988MB
alpine latest f8c20f8bbcb6 2 weeks ago 7.38MB
docker run -p 8081:80 -d --name testServer helloserver:1.0.2
[root@localhost example3]# curl http://localhost:8081/print/env
env list : env1 = env1value and env2 = env2value
COPY语法
COPY //将上下⽂中源⽂件,拷⻉到⽬标⽂件
COPY prefix* /destDir/ //将所有prefix 开头的⽂件拷⻉到 destDir ⽬录下
COPY prefix?.log /destDir/ //⽀持单个占位符,例如 : prefix1.log、
prefix2.log 等
COPY srcDir /destDir/ //只会将源⽂件夹srcDir下的⽂件拷⻉到 destDir ⽬录下
FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
ADD语法
ADD
ADD 命令除了不能⽤在 multistage 的场景下,ADD 命令可以完成 COPY 命令的所有功能,并且还可以完成两类的功能:
例如把nginx打包进镜像里
FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
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/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
启动进容器里查看对应目录即可
docker build --no-cache -t helloserver:1.0.3 -f Dockerfile .
docker run -p 8081:80 -d --name helloserver helloserver:1.0.3
docker exec -it d873894c0493 /bin/sh
注意:ADD⽬标⽂件位置要注意路径后⾯是否带 “/” ,带斜杠表示⽬录,不带斜杠表示⽂件名⽂件名⾥带有空格,需要再 ADD(或COPY)指令⾥⽤双引号的形式标明:
ADD "space file.txt" "/tmp/space file.txt"
# shell 格式
CMD
# exec格式,推荐格式
CMD ["executable","param1","param2"]
# 为ENTRYPOINT 指令提供参数
CMD ["param1","param2"]
CMD 指令提供容器运⾏时的默认值,这些默认值可以是⼀条指令,也可以是⼀些参数。⼀个dockerfile中可以有多条CMD指令,但只有最后⼀条CMD指令有效。CMD参数格式是在CMD指令与ENTRYPOINT指令配合时使⽤,CMD指令中的参数会添加到ENTRYPOINT指令中。使⽤shell 和exec 格式时,命令在容器中的运⾏⽅式与RUN 指令相同。不同在于,RUN指令在构建镜像时执⾏命令,并⽣成新的镜像。CMD指令在构建镜像时并不执⾏任何命令,⽽是在容器启动时默认将CMD指令作为第⼀条执⾏的命令。如果在命令⾏界⾯运⾏docker run 命令时指定命令参数,则会覆盖CMD指令中的命令。
例子,前文也提到
FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]
ENTRYPOINT指令有两种格式
# shell 格式
ENTRYPOINT
# exec 格式,推荐格式
ENTRYPOINT ["executable","param1","param2"]
ENTRYPOINT指令和CMD指令类似,都可以让容器每次启动时执⾏相同的命令,但它们之间⼜有不同。⼀个Dockerfile中可以有多条ENTRYPOINT指令,但只有最后⼀条ENTRYPOINT指令有效。当使⽤shell格式时,ENTRYPOINT指令会忽略任何CMD指令和docker run 命令的参数,并且会运⾏在bin/sh -c
。推荐使⽤exec格式,使⽤此格式,docker run 传⼊的命令参数将会覆盖CMD指令的内容并且附加到ENTRYPOINT指令的参数中。
CMD可以是参数,也可以是指令,ENTRYPOINT只能是命令;docker run 命令提供的运⾏命令参数可以覆盖CMD,但不能覆盖ENTRYPOINT。
# syntax=docker/dockerfile:1
FROM golang:1.18
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
RUN git clone https://gitee.com/nickdemo/helloworld.git
WORKDIR helloworld
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
EXPOSE 80
#ENTRYPOINT ["./app"]
#ENTRYPOINT ./app --param1=p11.0.3 --param2=p2
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]
#CMD ["./app","--param1=p1","--param2=p2"]
#CMD ./app --param1=p1 --param2=p2
CMD ["--param1=p1","--param2=p2"]
#shell形式
docker run <image-name> --cmd "param1 param2 param3"
docker run --entrypoint "command" <image-name> "param1" "param2" "param3"
#exec命令形式
docker run --entrypoint '["command", "param1", "param2", "param3"]' <image-name>
docker run <image-name> --cmd '["command", "param1", "param2", "param3"]'
CMD和ENTRYPOINT一起使用
FROM alpine:latest
ENTRYPOINT ["echo"]
CMD ["Hello, World!"]
参数替换
docker run --entrypoint "echo" <image-name> "Hello, Docker!"
--entrypoint
参数必须在最前面,紧随其后的是镜像名称,然后是要传递的命令及参数