Docker 镜像构建

Dockerfile

以一个镜像为基础,通过指令构建其他镜像。

构建Docker镜像

在Dockerfile所在目录执行docker build -t 镜像名:标签 .(上下文路径)创建新的镜像。

默认Docker在构建的时候会从上下文路径中查找名字为Dockerfile的文件,来执行构建。

Dockerfile中所有针对宿主机文件的指令均以上下文路径作为工作目录。

可以使用-f 指定Dockerfile的路径和context路径,例如

docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

使用标准输入(STDIN)方式传入Dockerfile和context的内容并构建镜像

docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

使用当前目录作为context,标准输入(STDIN)方式传入Dockerfile构建镜像

docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt .
RUN cat /somefile.txt
EOF

使用远程的context,标准输入(STDIN)方式传入Dockerfile构建镜像

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c .
EOF

.dockerignore 文件

.gitignore文件作用类似,.dockerignore文件用来排除build context中的特定文件或目录。

.dockerignore文件的语法和.gitignore文件相同。

Dockerfile 指令

FROM

Dockerfile的第一个指令。用于指定镜像从什么基础镜像开始构建。

常用的基础镜像有:

  • busybox:一个集成了常用Linux命令的基础工具箱。
  • alpine:推荐使用,具备完整的Linux功能,体积非常小,只有5M左右,使用apk工具安装软件包。
  • centos:CentOS系统。
  • ubuntu:Ubuntu系统。
  • jdk:自带指定版本JDK的基础镜像。可以作为Java项目的基础镜像。
  • maven:自带maven的基础镜像。可以作为编译环境构建的基础镜像。
  • scratch:没有任何基础镜像,从零开始构建。

LABEL

LABEL和注释类似,负责给镜像添加一些额外的key-value信息。

使用例子:

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

注意:字符串的value如果带有空格,必须要用引号引起来。字符串内部的引号同样需要引号引起来。

Docker1.10之前不推荐使用多个LABEL,因为每一个LABEL指令会生成一个新的镜像层。但是Docker1.10之后可以不用刻意这么做。

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

LABEL一行指令还可以分到多行来写,例如:

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

RUN

构建镜像时执行Linux命令。RUN执行使用sh -c来运行命令。

注意:

  1. RUN执行每执行一次会产生一个新的镜像层,尽量不要产生过多的镜像层。
  2. 构建镜像每一层都会生成一个缓存。例如:
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl

运行apt-get update之后的结果会生成一个缓存。
如果之后修改了这个Dockerfile,增加一个安装nginx包,Docker仍会使用之前apt-get update之后的缓存继续构建。这样会存在问题。新构建的镜像使用的包不是构建时最新的。要解决这个问题,需要将update和安装命令写到同一行。

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo=1.3.*
  1. 使用命令pipeline,只要最后一个命令运行正常,这条RUN命令会被认为执行成功。例如:
RUN wget -O - https://some.site | wc -l > /number

如果wget运行失败,这条RUN执行仍会执行成功。
如果必须要求pipeline前后命令都执行成功,可以使用如下命令:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

如果使用的是Debian系Linux镜像,默认使用的dash不支持set -o pipefail。需要明确指定bash去执行命令,如下所示:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

用于指定运行镜像中用户程序的入口。可以带有启动参数。

CMD指令的使用形式如下:

CMD ["executable", "param1", "param2"…]

注意:用户进程绝对不能以后台形式或者服务形式运行,否则container会认为主线程执行完毕退出。必须使用前台形式运行用户进程。

Docker运行时候用户可以指定入口命令。CMD中的入口命令会被覆盖。

一个例子如下

CMD ["/bin/bash", "-c", "Hello World"]

默认启动container,会打印出"Hello World"

如果启动container的时候使用参数echo "Blabla",CMD会被覆盖,结果打印出"Blabla"。

ENTRYPOINT

ENTRYPOINT指令和CMD指令基本相同。使用形式如下:

ENTRYPOINT ["/bin/bash", "-c", "Hello World"]

但是ENTRYPOINT和CMD命令还有区别。如果用户在启动container时候指定了自定义命令,只会追加到ENTRYPOINT的命令之后,不会完整替换掉ENTRYPOINT命令。

例如:

ENTRYPOINT ["echo"]

启动container传入自定义命令Hello World,container启动的时候会执行echo Hello World打印出"Hello World"字样。

ENTRYPOINT通常和CMD配合使用。ENTRYPOINT指定用户程序入口命令,CMD指定命令的默认执行参数。例如:

ENTRYPOINT ["echo"]
CMD ["Hello World"]

默认运行会执行

echo Hello World

如果运行时指定了自定义命令,例如docker run -it example-image Hola,此时"Hola"会替换掉CMD中的"Hello World",然后追加到ENTRYPOINT之后,相当于执行了:

echo Hola

编写入口脚本的时候最好使用exec命令执行用户程序,这样用户程序在container中的PID为1,方便接收到Unix信号。

EXPOSE

用于声明需要暴露端口,大多数情况用于增加可读性。

使用方式如下:

EXPOSE 8080

如果运行的时候使用了-P参数,Docker会映射镜像所有EXPOSE的端口到宿主机任意端口。

docker run -it example-image -P

如果使用手工指定宿主机和容器的端口映射,EXPOSE将不起作用。

docker run -it example-image -p 80:8080

ENV

指定运行时container中的环境变量。

ENV设置的变量既可以在Dockerfile中使用,也可以在container内使用。

使用例子如下:

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

注意:ENV也会创建一个新的镜像层。这样会导致ENV设置的环境变量,无法被后续的RUN命令清除掉。
例如:

FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER

尽管执行了unset,ADMIN_USER环境变量依然存在。

为了避免这种情况,需要把设置环境变量和使用它的地方写到同一层镜像的构建中。例如:

FROM alpine
RUN export ADMIN_USER="mark" \
    && echo $ADMIN_USER > ./mark \
    && unset ADMIN_USER
CMD sh

COPY

COPY用于构建时从宿主机向镜像中复制文件。

如果需要复制多组文件,尽量使用多个COPY指令,这样每次Docker缓存中只有一组文件的复制操作。还有COPY操作尽量在RUN指令之后,减少以后修改Dockerfile文件Docker缓存失效的影响返回,加快构建速度。

ADD

和COPY作用一样也是用于向镜像中添加文件。ADD还有额外的功能,比如说添加一个tar压缩包时会自动解压。ADD后面跟URL会自动从那里下载文件。但是,仅仅推荐在需要解压tar压缩包到镜像的时候才推荐使用。

使用方式:

ADD rootfs.tar.xz /

强烈建议不要用ADD执行下载URL指向的文件,应使用curl或者wget命令。

VOLUME

VOLUME指令用来标识需要挂载数据卷的目录。

使用方式如下:

VOLUME ["/data"]

运行这个镜像的时候会创建出来一个volume,挂载到/data目录。

可以在运行命令中指定-v参数覆盖,比如说将/data目录映射到宿主机某个目录。

USER

默认RUN指令用root用户执行命令。如果需要切换到其他用户来执行,使用USER指令。

USER指令的使用方法为:

USER <user>[:<group>] or
USER <UID>[:<GID>]

注意:

  1. 在切换用户前需要先创建用户。例如执行:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
  1. 因为Go语言的一个bug,在container内创建一个UID比较大的用户会导致container包含/var/log/faillog的一层填满NULL(\0),磁盘会耗尽。规避这个问题的方法是创建用户的时候useradd命令加上--no-log-init参数。Debian/Ubuntu不支持这个参数。
  2. 不要在构建镜像时使用sudo,应当使用gosugosu的安装和使用方式在https://github.com/tianon/gosu
  3. 使用USER切换用户也会创建一个新镜像层,避免过多使用USER来回切换用户。

WORKDIR

切换当前工作目录。例如:

WORKDIR /data
RUN ls config-*

相当于

RUN ls /data/config-*

ARG

和ENV使用方法类似,但是ARG设定的变量只能在Dockerfile中访问,无法在container内部使用。

使用方法:

ARG <name>[=<default value>]

在构建镜像(docker build)的时候使用--build-arg <varname>=<value>形式来给ARG赋值,或者说覆盖Dockerfile中ARG的默认值。

ONBUILD

ONBUILD指令在构建当前镜像的时候不会执行。但是,在其他镜像基于这个镜像构建(即 FROM 这个镜像)的时候,ONBUILD会执行。

ONBUILD的使用例子:

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

注意:专用于ONBUILD的镜像,最好给一个以onbuild结尾的tag,例如ruby:1.9-onbuild

MAINTAINER

注明镜像的维护者信息。

使用方法:

MAINTAINER <name>

注意:该指令已经废弃,建议使用label来替代。例子如下所示:

LABEL maintainer="SvenDowideit@home.org.au"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容