开发手册 欢迎您!
软件开发者资料库

Docker 构建自定义镜像和Dockerfile文件

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

1、基本结构

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

2、文件写法

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

常用的指令如下表:

指令

作用

说明

FROM

指定父镜像

指定dockerfile基于那个image构建,

必须为第一个命令。

格式:

FROM
FROM :
FROM @


MAINTAINER

作者信息

用来标明这个dockerfile作者。

格式:

MAINTAINER

LABEL

标签

用来标明dockerfile的标签,

可以使用Label代替Maintainer,

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

格式:

LABEL = = = ...

一条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 ...

COPY ["",... ""] 

注意:

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

ADD

添加文件

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

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

可以来源于远程服务,

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

tar类型文件会自动解压,

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

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

格式:

ADD ...

ADD ["",... ""] 

注意:

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

ENV

环境变量

指定build时候的环境变量,

可以在启动容器时,

通过-e覆盖。

格式:

ENV

ENV =

注意:

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

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

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

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

每个变量为一个"="的键值对,

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

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

另外,\也可以用于续行

ARG

构建参数

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

如有ENV

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

格式:

ARG [=]

VOLUME

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

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

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

格式:

VOLUME ["/path/to/dir"]

注意:

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

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

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

EXPOSE

暴露端口

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

用于容器外部访问。

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

格式:

EXPOSE [...]

例如,

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:latestADD 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 upgradeRUN 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.apkENV JAVA_HOME=/usr/local/jdk1.8.0_211ENV CLASSPATH=$JAVA_HOME/binENV PATH=.:$JAVA_HOME/bin:$PATHCMD ["java","-version"]

2)Python3 镜像

FROM alpine:3.14ENV 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 E3FF2839C048B25C084DEBE9B26995E310250568ENV PYTHON_VERSION 3.9.8RUN 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/2026e1259422d4e0cf92391ca2d3844356c649d0EXTRA_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-configENV PYTHON_PIP_VERSION 21.2.4# https://github.com/docker-library/python/issues/365ENV PYTHON_SETUPTOOLS_VERSION 57.5.0# https://github.com/pypa/get-pipENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.pyENV PYTHON_GET_PIP_SHA256 c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309RUN 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.pyCMD ["python3"]

3)nginx 镜像

## NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"##FROM debian:buster-slimLABEL maintainer="NGINX Docker Maintainers "ENV NGINX_VERSION   1.20.1ENV NJS_VERSION     0.5.3ENV PKG_RELEASE     1~busterRUN 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.dCOPY docker-entrypoint.sh /COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.dCOPY 20-envsubst-on-templates.sh /docker-entrypoint.dCOPY 30-tune-worker-processes.sh /docker-entrypoint.dENTRYPOINT ["/docker-entrypoint.sh"]EXPOSE 80STOPSIGNAL SIGQUITCMD ["nginx", "-g", "daemon off;"]

4、构建生成Docker镜像

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

docker build  -t ImageName:TagName dir

-t :是提及图像的标签

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

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

Dir :Docker 文件所在的目录。

例如,

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