Dockerfile 是一个包含创建镜像所有命令的文本文件,通过docker build命令可以根据 Dockerfile 的内容构建镜像。本文主要介绍Docker 构建自定义镜像和Dockerfile文件。

1、基本结构

Dockerfile 文件一般包含基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。

2、文件写法

Docker以从上到下的顺序运行Dockerfile文件中指令。为了指定基本映像,第一条指令必须是FROM。一个声明以#字符开头则被视为注释。可以在Docker文件中使用RUN,CMD,FROM,EXPOSE,ENV等指令。

常用的指令如下表:

指令

作用

说明

FROM

指定父镜像

指定dockerfile基于那个image构建,

必须为第一个命令。

格式:

FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>


MAINTAINER

作者信息

用来标明这个dockerfile作者。

格式:

MAINTAINER <name>

LABEL

标签

用来标明dockerfile的标签,

可以使用Label代替Maintainer,

最终都是在docker image基本信息中查看。

格式:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

一条LABEL指定可以指定一或多条元数据,

指定多条元数据时不同元数据之间通过空格分隔。

推荐将所有的元数据通过一条LABEL指令指定,

以免生成过多的中间镜像。

RUN

执行命令

执行一段命令 默认是/bin/sh,构建镜像时执行的命令。

格式:

RUN command 

RUN ["command" , "param1","param2"] 

注意:

第一种格式是shell执行,

第二种格式是exec执行。

CMD

容器启动命令

提供启动容器时候的默认命令 

和ENTRYPOINT配合使用。

构建容器后调用,

也就是在容器启动时才进行调用。

格式:

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

CMD ["param1","param2"] 

CMD command param1 param2 

注意:

第一种格式是执行可执行文件,

第二种格式是设置了ENTRYPOINT,

则直接调用ENTRYPOINT添加参数。

第三种格式是执行shell内部命令。

CMD不同于RUN,

CMD用于指定在容器启动时所要执行的命令,

而RUN用于指定镜像构建时所要执行的命令。

ENTRYPOINT

入口

一般在制作一些执行就关闭的容器中会使用。

格式:

ENTRYPOINT ["executable", "param1", "param2"] 

ENTRYPOINT command param1 param2

注意:

第一种格式是可执行文件,

第二种格式是shell内部命令。

ENTRYPOINT与CMD非常类似,

不同的是通过docker run执行的命令,

不会覆盖ENTRYPOINT,

而docker run命令中指定的任何参数,

都会被当做参数再次传递给ENTRYPOINT。

Dockerfile中只允许有一个ENTRYPOINT命令,

指定多个时会覆盖前面的设置,

而只执行最后的ENTRYPOINT指令。

COPY

复制文件

build的时候复制文件到image中,

与ADD类似,但不会自动解压文件,

也不能访问网络资源。

格式:

COPY <src>... <dest>

COPY ["<src>",... "<dest>"] 

注意:

第二种格式是用于支持包含空格的路径。

ADD

添加文件

build的时候添加文件到image中,

不仅仅局限于当前build上下文,

可以来源于远程服务,

将本地文件添加到容器中,

tar类型文件会自动解压,

但网络压缩资源不会被解压,

可以访问网络资源,相当于wget。

格式:

ADD <src>... <dest>

ADD ["<src>",... "<dest>"] 

注意:

第二种格式是用于支持包含空格的路径。

ENV

环境变量

指定build时候的环境变量,

可以在启动容器时,

通过-e覆盖。

格式:

ENV <key> <value>

ENV <key>=<value>

注意:

第一种格式中<key>之后的所有内容,

均会被视为其<value>的组成部分,

则一次只能设置一个变量。

第二种格式中可以设置多个变量,

每个变量为一个"<key>=<value>"的键值对,

如果<key>中包含空格,可以使用\来进行转义,

也可以通过""来进行标示;

另外,\也可以用于续行

ARG

构建参数

构建参数只在构建时使用的参数,

如有ENV

ENV的相同名字的值始终覆盖arg的参数。

格式:

ARG <name>[=<default value>]

VOLUME

定义外部可以挂载的数据卷

指定build的image中启动时挂载到文件系统中的目录,

也可以启动容器时可以使用 -v 选项绑定。

格式:

VOLUME ["/path/to/dir"]

注意:

一个卷可以存在于一个或多个容器的指定目录,

卷可以容器间共享和重用,

对卷的修改不会影响镜像。

EXPOSE

暴露端口

定义容器运行时监听的端口,

用于容器外部访问。

启动容器的使用-p选项来绑定暴露端口

格式:

EXPOSE <port> [<port>...]

例如,

EXPOSE 80443
EXPOSE 8080
EXPOSE 11211/tcp 11211/udp

WORKDIR

工作目录

指定容器内部的工作目录,

如没有创建则自动创建,

如指定/ 开关的路径,

则使用的是绝对路径,

如果不是/开头路径,

则是上一条workdir的路径的相对路径。

格式:

WORKDIR /path/to/workdir

例如,

WORKDIR /data (工作目录为/data)

WORKDIR web (工作目录为/data/web)

WORKDIR user (工作目录为/data/user)

USER

指定执行用户

指定build或者启动时,

在RUN、CMD

和ENTRYPONT执行时使用的用户。

 格式:

  USER user

  USER user:group

  USER uid

  USER uid:gid

  USER user:gid

  USER uid:group

注意:

通过docker run运行容器时,

可以通过-u参数来覆盖所指定的用户。

HEALTHCHECK

健康检查

指定监测当前容器的健康监测的命令。

HEALTHCHECK [选项] CMD <命令>

如果基础镜像有健康检查指令,

使用HEALTHCHECK NONE

可以屏蔽掉其健康检查指令。

ONBUILD

触发器

当存在ONBUILD的镜像作为基础镜像时,

当执行FROM完成之后,

会执行 ONBUILD的命令,

但不会影响当前镜像。

格式:

ONBUILD [INSTRUCTION]

例如,

ONBUILD ADD . /data/src

STOPSIGNAL

发送信号量到宿主机

STOPSIGNAL指令设置,

将发送到容器的系统调用信号以退出。

SHELL

指定执行脚本的shell

指定RUN、CMD

和ENTRYPOINT 执行命令时,

使用的shell的类型。

3、Dockerfile文件示例

1)Java JDK镜像

FROM alpine:latest
ADD jdk-8u211-linux-x64.tar.gz /usr/local/
RUN echo http://mirrors.ustc.edu.cn/alpine/v3.9/main > /etc/apk/repositories && \
    echo http://mirrors.ustc.edu.cn/alpine/v3.9/community >> /etc/apk/repositories && \
    apk update && apk upgrade
RUN apk --no-cache add ca-certificates wget && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.29-r0/glibc-2.29-r0.apk && \
    apk add glibc-2.29-r0.apk
ENV JAVA_HOME=/usr/local/jdk1.8.0_211
ENV CLASSPATH=$JAVA_HOME/bin
ENV PATH=.:$JAVA_HOME/bin:$PATH
CMD ["java","-version"]

2)Python3 镜像

FROM alpine:3.14
ENV PATH /usr/local/bin:$PATH
# http://bugs.python.org/issue19846
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
ENV LANG C.UTF-8
# runtime 依赖
RUN set -eux; \
	apk add --no-cache \
# 安装ca-certificates,使HTTPS工作一致
		ca-certificates \
# and tzdata for PEP 615 (https://www.python.org/dev/peps/pep-0615/)
		tzdata \
	;
# Python的其他运行时依赖项稍后安装
ENV GPG_KEY E3FF2839C048B25C084DEBE9B26995E310250568
ENV PYTHON_VERSION 3.9.8
RUN set -ex \
	&& apk add --no-cache --virtual .fetch-deps \
		gnupg \
		tar \
		xz \
	\
	&& wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \
	&& wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \
	&& export GNUPGHOME="$(mktemp -d)" \
	&& gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$GPG_KEY" \
	&& gpg --batch --verify python.tar.xz.asc python.tar.xz \
	&& { command -v gpgconf > /dev/null && gpgconf --kill all || :; } \
	&& rm -rf "$GNUPGHOME" python.tar.xz.asc \
	&& mkdir -p /usr/src/python \
	&& tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
	&& rm python.tar.xz \
	\
	&& apk add --no-cache --virtual .build-deps  \
		bluez-dev \
		bzip2-dev \
		coreutils \
		dpkg-dev dpkg \
		expat-dev \
		findutils \
		gcc \
		gdbm-dev \
		libc-dev \
		libffi-dev \
		libnsl-dev \
		libtirpc-dev \
		linux-headers \
		make \
		ncurses-dev \
		openssl-dev \
		pax-utils \
		readline-dev \
		sqlite-dev \
		tcl-dev \
		tk \
		tk-dev \
		util-linux-dev \
		xz-dev \
		zlib-dev \
# 在移除获取deps之前添加构建deps,以防有重叠
	&& apk del --no-network .fetch-deps \
	\
	&& cd /usr/src/python \
	&& gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
	&& ./configure \
		--build="$gnuArch" \
		--enable-loadable-sqlite-extensions \
		--enable-optimizations \
		--enable-option-checking=fatal \
		--enable-shared \
		--with-system-expat \
		--with-system-ffi \
		--without-ensurepip \
	&& make -j "$(nproc)" \
# 将线程堆栈大小设置为1MB,这样就不会在遇到sys.getrecursionlimit()之前出现段错误。
# https://github.com/alpinelinux/aports/commit/2026e1259422d4e0cf92391ca2d3844356c649d0
		EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" \
		LDFLAGS="-Wl,--strip-all" \
	&& make install \
	&& rm -rf /usr/src/python \
	\
	&& find /usr/local -depth \
		\( \
			\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
			-o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name '*.a' \) \) \
		\) -exec rm -rf '{}' + \
	\
	&& find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \
		| tr ',' '\n' \
		| sort -u \
		| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
		| xargs -rt apk add --no-cache --virtual .python-rundeps \
	&& apk del --no-network .build-deps \
	\
	&& python3 --version
# 制作一些预计会存在的有用符号链接
RUN cd /usr/local/bin \
	&& ln -s idle3 idle \
	&& ln -s pydoc3 pydoc \
	&& ln -s python3 python \
	&& ln -s python3-config python-config
ENV PYTHON_PIP_VERSION 21.2.4
# https://github.com/docker-library/python/issues/365
ENV PYTHON_SETUPTOOLS_VERSION 57.5.0
# https://github.com/pypa/get-pip
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py
ENV PYTHON_GET_PIP_SHA256 c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309
RUN set -ex; \
	\
	wget -O get-pip.py "$PYTHON_GET_PIP_URL"; \
	echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum -c -; \
	\
	python get-pip.py \
		--disable-pip-version-check \
		--no-cache-dir \
		"pip==$PYTHON_PIP_VERSION" \
		"setuptools==$PYTHON_SETUPTOOLS_VERSION" \
	; \
	pip --version; \
	\
	find /usr/local -depth \
		\( \
			\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
			-o \
			\( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \
		\) -exec rm -rf '{}' +; \
	rm -f get-pip.py
CMD ["python3"]

3)nginx 镜像

#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"
#
#
FROM debian:buster-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
ENV NGINX_VERSION   1.20.1
ENV NJS_VERSION     0.5.3
ENV PKG_RELEASE     1~buster
RUN set -x \
# 首先创建nginx用户/组,以在整个docker变体中保持一致
    && addgroup --system --gid 101 nginx \
    && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \
    && apt-get update \
    && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \
    && \
    NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    found=''; \
    for server in \
        ha.pool.sks-keyservers.net \
        hkp://keyserver.ubuntu.com:80 \
        hkp://p80.pool.sks-keyservers.net:80 \
        pgp.mit.edu \
    ; do \
        echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
        apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
    done; \
    test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
    apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \
    && dpkgArch="$(dpkg --print-architecture)" \
    && nginxPackages=" \
        nginx=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \
    " \
    && case "$dpkgArch" in \
        amd64|i386|arm64) \

            echo "deb https://nginx.org/packages/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
            && apt-get update \
            ;; \
        *) \
            echo "deb-src https://nginx.org/packages/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
            \
# 用于存储源文件和.deb文件的新目录
            && tempDir="$(mktemp -d)" \
            && chmod 777 "$tempDir" \
# (777 to ensure APT's "_apt" user can access it too)
            \
# 保存当前安装的包列表,以便稍后可以干净地删除构建依赖项
            && savedAptMark="$(apt-mark showmanual)" \
            \
            && apt-get update \
            && apt-get build-dep -y $nginxPackages \
            && ( \
                cd "$tempDir" \
                && DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" \
                    apt-get source --compile $nginxPackages \
            ) \
# 在这里不删除APT列表,因为它们会被重新下载并删除
            \
            && apt-mark showmanual | xargs apt-mark auto > /dev/null \
            && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
            \
# 创建一个临时的本地APT repo来安装
            && ls -lAFh "$tempDir" \
            && ( cd "$tempDir" && dpkg-scanpackages . > Packages ) \
            && grep '^Package: ' "$tempDir/Packages" \
            && echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list \
            && apt-get -o Acquire::GzipIndexes=false update \
            ;; \
    esac \
    \
    && apt-get install --no-install-recommends --no-install-suggests -y \
                        $nginxPackages \
                        gettext-base \
                        curl \
    && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \
    \
    && if [ -n "$tempDir" ]; then \
        apt-get purge -y --auto-remove \
        && rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; \
    fi \
# 转发请求日志和错误日志到docker日志采集器
    && ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log \
# 创建一个docker-entrypoint.d 目录
    && mkdir /docker-entrypoint.d
COPY docker-entrypoint.sh /
COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d
ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 80
STOPSIGNAL SIGQUIT
CMD ["nginx", "-g", "daemon off;"]

4、构建生成Docker镜像

编写好构建的镜像的Dockerfile文件后,可以docker build 命令构建生成镜像,如下,

docker build  -t ImageName:TagName dir

-t :是提及图像的标签

ImageName :要为镜像命名的名称。

TagName :要为镜像提供的标签。

Dir :Docker 文件所在的目录。

例如,

docker build -t cjavapy/centos7-jdk1.7 .

推荐文档