常用命令
FROM
: 基础镜像MAINTAINER
: 构建者USER
: 设置后面指令运行用户- 该用户必须事先已经存在
COPY <src> <dest>
: 拷贝宿主机文件或目录到容器- 如果目录不存在则自动创建
<dest>
需要是绝对路径或者相对于workdir
- 当
<src>
是文件时- 如果
<dest>
以/
结尾,则视为复制到<dest>
目录,否则视为改名为<dest>
文件
- 如果
- 当
<src>
是目录时- 只是把
<src>
中的所有文件复制到<dest>
目录下 - 如果需要把复制目录,需要在
<dest>
路径后创建该目录- 复制
./dir
目录到容器/test
路径下:COPY ./dir /test/dir
- 复制
- 只是把
- 当
<src>
必须是上下文根目录的相对路径,可以使用通配符- 可以指定多个
<src>
- 可以指定多个
WORKDIR
: 指定后面指令的工作目录,也是我们进入容器后的目录- 可以多次设置,会覆盖前面的
- 但是如果是相对路径,它将相对于先前
WORKDIR
指令的路径
- 但是如果是相对路径,它将相对于先前
- 可以多次设置,会覆盖前面的
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 输出/a/b/c
VOLUME
: 创建容器卷,匿名映射到宿主机- 为了保持环境无关性,不能绑定指定宿主机目录
- 可以在启动时被同名路径覆盖
- 通过
VOLUME
挂载目录后,如果尝试对这个volume进行修改,这些修改都不会生效- 因为每一层的临时镜像都是通过commit构建的,而commit时不会对挂载的volume进行保存
- 解决方法
- 先创建并修改要挂载的目录,此后在挂载时会把已经存在的内容复制到volume中
- 也可以把修改命令放在
ENTRYPOINT
或者CMD
中- 因为这两个指令是在容器启动时执行的
# 法1
RUN mkdir /data && touch /data/file
RUN chmod +x /data/file
VOLUME /data
# 法2
VOLUME /data
CMD touch /data/file && chmod +x /data/file
RUN
: 构建镜像过程中执行的操作- shell格式:
RUN echo hello
- 使用
/bin/sh
执行命令 - 实际上是执行了一个shell进程,环境变量会被shell解析
- 使用
- exec格式:
RUN [“echo”, “hello”]
- 执行时不会引用环境变量
- exec格式必须使用双引号
- 因为被当成json解析
- 使用不同的shell
RUN [“bin/bash”, “-c”, “echo hello”]
- 此时会引用环境变量
- 如果想要运行一个守护进程,应该以exec格式执行
- 如果以shell格式执行,shell进程在执行完启动命令后就退出了,容器也就结束了
- shell格式执行时的init进程(pid=1))是shell,而不是要执行的指令
docker stop
无法暂停,因为其发送的SIGTERM
信号只被init进程接受
- shell格式:
ENV <k1>=<v1> <k2>=<v2>
: 设置环境变量- 环境变量被设置完后,后面的命令(例如
RUN
)可以直接使用 - 还可以使用外部的环境变量
- 环境变量被设置完后,后面的命令(例如
EXPOSE <p1> <p2>
:暴露端口- 需要运行时使用
-P
参数
- 需要运行时使用
HEALTHCHECK [选项] CMD <命令>
:健康检查,1.12版本引入- 命令返回0成功,1失败
- 之前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常
- 如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了
- 健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看:
docker inspect --format '{{json .State.Health}}' <docker>
- 例子:每60秒检查一次,3秒无响应则视为失败,失败3次后容器状态变为unhealthy
HEALTHCHECK --interval=60s --timeout=3s --retries=3 CMD curl -fs http://localhost/ || exit 1
CMD
: 默认启动命令- 可以在
docker run
时覆盖 - 如果存在多个
CMD
,则只有最后一个CMD
将生效 - 有shell和exec两种格式
- 可以在
ENTRYPOINT
: 默认启动命令- 不可以在
docker run
时覆盖 - 如果同时存在
ENTRYPOINT
和CMD
命令,则CMD
视为ENTRYPOINT
的参数 - 有shell和exec两种格式
- 不可以在
注意点
-
不常修改的层应该在经常修改的层前面,以利用缓存
-
层数是有最大限制的,因此最好不要每个shell命令都使用RUN前缀,最好合并为一个
- 使用
&&
将各个所需命令串联起来 - 行尾添加
\
的命令换行方式
- 使用
-
ADD
命令和COPY
命令最大的不同在于会自动解压压缩文件以及获取远程文件- 更推荐使用
RUN wget
而不是ADD
获取
- 更推荐使用
-
推荐使用volume共享文件,而不是把文件添加到镜像中
-
想要通过
--link
参数连接容器,必须在Dockerfile中暴露端口 -
容器的每一行命令和上一行命令不是同一个执行环境
- 因此如果后面的命令对前面的有依赖应该写在同一行
# 正面例子
RUN apt update && apt install -y ssh
# 反面例子
# 容器构建完成后会发现不存在 /app/world.txt文件
RUN cd /app
RUN echo "hello" > world.txt
多阶段构建
- Docker v17.05 开始支持
- 目的:镜像体积更小
FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 避免安全风险
RUN adduser -u 10001 -D runner
USER runner
COPY --from=builder /go/src/github.com/go/helloworld/app .
CMD ["./app"]
- 为了提高安全性,应该避免root用户启动
- 但此时应该提前授予目录权限,否则会
permission denied
- 但此时应该提前授予目录权限,否则会
- 一个Dockerfile中可以存在多个
FROM
, 最后一个FROM生成最终镜像 - 使用
as
来为某一阶段命名,例如上面的builder
- 只想构建
builder
阶段的镜像时,go bulid
增加--target=builder
参数即可 COPY --from
不但可以从前置阶段中拷贝,还可以直接从一个已经存在的镜像中拷贝- 例如
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
- 例如
构建镜像
docker build -t wwt/ubuntu:git .
# 使用Dockerfile.debug在当前目录构建镜像
docker build -f Dockerfile.debug .
# 把/home/me/myapp用作构建上下文的根
docker build -f /home/me/myapp/dockerfiles.debug /home/me/myapp
-t
指定要创建的镜像名- 最后的
.
表示使用当前目录构建上下文(context)- 可以使用别的路径
- 默认在context的根目录寻找Dockerfile
- 也可以使用
-f
参数指定Dockerfile 的绝对路径 - 构建新镜像时,每一层指令都会构建一个临时镜像
- 每个指令都会利用上一层的临时镜像创建出一个临时容器,并在临时容器中执行指令,然后通过commit生成临时镜像
- 如果构建失败,可以利用上一层的临时镜像创建容器,并手动执行接下来的命令来调试错误
- 构建由Docker守护程序运行,而不是由CLI运行。构建过程所做的第一件事是将整个context (递归地)复制给守护进程
- 即使文件没有被使用也会被复制
- 因此如果context有到大文件的话构建会很慢
常用镜像
busybox
- 含有许多常用Linux工具,体积很小
alpine
- 如果想要在该镜像运行go程序,在builder镜像中build时,必须加上
CGO_ENABLED=0
前缀 - 推荐使用 Alpine 替代Ubuntu 做为基础镜像环境
- 镜像下载速度加快,镜像安全性提高,主机之间的切换更方便,占用更少磁盘空间等
- alpine提供了自己的包管理工具 apk
- 通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息
- 如果需要的安装包不在主索引内,但是在测试或社区索引中,那么可以按照以下方法使用这些安装包
- alpine中没有
/bin/bash
,只有/bin/sh
echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
apk --update add --no-cache <package>
- alpine中没有
curl
,只有wget
- 需要预先使用
RUN apk update && apk upgrade && \ apk add --no-cache bash git curl
命令安装
- 需要预先使用
- apk常用命令
apk update #更新最新本地镜像源
apk upgrade #升级软件
apk add [--no-cache] # 添加软件
apk add --upgrade busybox #指定升级部分软件包
apk search #查找所以可用软件包
apk search -v #查找所有可用软件包及其描述内容
apk search -v 'acf*' #通过软件包名称查找软件包
apk search -v -d 'docker' #通过描述文件查找特定的软件包
apk info #列出所有已安装的软件包
ubuntu
- 当试图直接使用
apt
安装一个软件的时候,会提示E: Unable to locate package
- Docker 镜像在制作时为了精简清除了 apt 仓库信息,因此需要先执行
apt update
命令来更新仓库信息
- Docker 镜像在制作时为了精简清除了 apt 仓库信息,因此需要先执行