Skip to content

Dockerfile用于定义单个容器镜像的构建过程,例如安装JDK、复制代码、设置环境变量等。它是镜像构建的蓝图。仅关注镜像构建(如安装依赖、编译代码)。 文件内容分为四个部分,基础镜像信息、维护者信息、镜像操作指令(RUN)、容器启动时执行指令(CMD)。

一、基础镜像信息

使用FROM指令指明所基于的镜像名称,大部分情况下,生成新的镜像都需要通过FROM指令指定父镜像。父镜像是生成镜像的基础,会直接影响到生成镜像的大小和功能。任何dockerfile中第一条指令必须为FROM指令(除了ARG)。并且如果在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次).

ROM

FROM命令格式如下:

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

例:
# 使用官方镜像
FROM ubuntu:20.04
# 多阶段构建中,可以为阶段命名
FROM golang:1.16-alpine AS builder
# 指定平台
FROM --platform=linux/amd64 node:16

参数说明:

  • --platform:指定平台,如 linux/amd64, linux/arm64
  • image:基础镜像名称
  • tag:镜像标签,默认为latest
  • AS name:为构建阶段命名(多阶段构建)

二、维护者信息

LABEL

MAINTAINER指令已过时,推荐使用LABEL。使用LABEL指令说明维护者信息。可以为生成的镜像添加元数据标签信息,这些信息可用来辅助过滤出特定镜像。LABEL命令格式如下:

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

例:
# 单标签
LABEL maintainer="devops@company.com"

# 多标签(推荐)
LABEL version="1.0.0" \
      description="Production API Service" \
      vendor="My Company Inc."

# 标准标签
LABEL org.opencontainers.image.title="My App" \
      org.opencontainers.image.version="2.1.0" \
      org.opencontainers.image.created="2023-10-01T12:00:00Z" \
      org.opencontainers.image.source="https://github.com/company/repo"

# 分类标签
LABEL com.company.team="platform-engineering" \
      com.company.project="customer-api" \
      com.company.sla="99.9"

参数说明:

  • key:标签键,推荐使用反向DNS表示法
  • value:标签值

三、镜像操作指令

用于安装软件、复制文件、设置环境变量和工作目录等构建过程中的操作。核心命令:RUN, COPY, ADD, ENV, ARG, WORKDIR。

RUN

在镜像构建过程中执行命令创建新层。合并多条RUN指令可以减少镜像层数,从而减小镜像体积。RUN命令语法如下:

# Exec 形式
RUN ["executable", "param1", "param2"]

# Shell 形式(默认 /bin/sh -c)
RUN <command>


# Shell 形式 - 单命令
RUN apt update

# Shell 形式 - 多命令合并(推荐)
RUN apt update && \
    apt install -y \
        curl \
        git \
        wget && \
    rm -rf /var/lib/apt/lists/* && \
    apt clean

# Exec 形式
RUN ["/bin/bash", "-c", "echo 'Building application'"]
RUN ["npm", "ci", "--only=production"]

# 条件执行
RUN if [ "$NODE_ENV" = "production" ]; then \
        npm ci --only=production; \
    else \
        npm ci; \
    fi
# 创建目录和设置权限
RUN mkdir -p /app/logs && \
    chown -R app:app /app && \
    chmod -R 755 /app

# 更新软件包列表并安装软件,最后清理缓存
RUN apt update && apt install -y \
    nginx \
    php-fpm \
    && rm -rf /var/lib/apt/lists/*

COPY & ADD

这两个指令都用于将文件/目录从构建上下文复制到镜像中。

COPY

推荐用于简单的文件复制。命令格式如下:

#复制本机的src(dockerfile所在目录的相对路径,文件或目录)下内容到镜像中的desc,目标路径不存在时,会#自动创建。功能类似add,当使用本地目录为源目录时,推荐使用copy。
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]


COPY ./src/ /app/src/
COPY requirements.txt /app/
COPY ./app /usr/src/app

# 基本文件复制
COPY package.json /app/
COPY requirements.txt /app/

# 复制目录
COPY src/ /app/src/
COPY config/ /app/config/

# 使用通配符
COPY *.js /app/
COPY data/*.json /app/data/

# 设置文件权限
COPY --chown=node:node app.js /app/
COPY --chown=1001:1001 files/ /app/data/

# 多文件复制
COPY file1.txt file2.txt /app/
COPY ["file with spaces.txt", "/app/"]

参数说明:

  • --chown:设置文件所有者
  • src:源文件或目录,支持通配符
  • dest:目标路径

ADD

除了复制,还支持从URL下载文件或自动解压压缩包(如tar、gzip等)到目标路径。除非需要这些额外功能,否则建议使用COPY。ADD语法格式如下:

#将复制指定的src路径下内容到容器中的dest路径下。src可以是dockerfile所在目录的一个相对路径(文件或目录),也可以是一个URL。还可以是一个tar文件。dest可以是镜像内绝对路径,或者相对于工作目录workdir的相对路径
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]


ADD https://example.com/file.tar.gz /tmp/

# 复制本地文件(类似 COPY)
ADD application.jar /app/

# 从URL下载文件
ADD https://example.com/tools/calculator /usr/local/bin/

# 自动解压tar文件
ADD application.tar.gz /opt/
ADD data.tar.xz /app/data/

# 设置权限并从多个源复制
ADD --chown=app:app \
    file1.txt \
    https://example.com/file2.txt \
    /app/

# 解压到特定目录
ADD dataset.tar.gz /app/datasets/

ENV & ARG

两者都用于定义变量,但有重要区别:

ARG

定义的是构建时使用的变量,定义构建参数,其值在镜像构建成功后不存在于镜像中。可以通过docker build --build-arg <varname>=<value>覆盖,语法格式如下:

ARG <name>[=<default value>]

例:
ARG APP_VERSION=1.0
RUN echo "Building version $APP_VERSION"

# 基础用法
ARG APP_VERSION=1.0.0
ARG BUILD_NUMBER

# 多阶段构建中的 ARG
FROM alpine AS build
ARG NPM_TOKEN
ARG REGISTRY_URL=https://registry.npmjs.org/

# 条件构建
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "amd64" ]; then \
        echo "Building for AMD64"; \
    else \
        echo "Building for $TARGETARCH"; \
    fi

# 与 ENV 配合使用
ARG GIT_COMMIT
ENV GIT_COMMIT=$GIT_COMMIT

ENV

设置的是容器运行时的环境变量,在容器启动后依然存在,语法格式如下:

ENV <key>=<value> ...

例:
# 单变量
ENV NODE_ENV=production

# 多变量
ENV APP_HOME=/app \
    APP_USER=app \
    APP_PORT=3000

# 使用变量引用
ENV PATH=$APP_HOME/bin:$PATH
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk

# 应用配置
ENV DB_HOST=localhost \
    DB_PORT=5432 \
    REDIS_URL=redis://cache:6379

# 语言和区域设置
ENV LANG=C.UTF-8 \
    LC_ALL=C.UTF-8 \
    TZ=Asia/Shanghai

WORKDIR

设置后续指令(如RUN、CMD、COPY)的工作目录。如果目录不存在,会自动创建。推荐使用绝对路径。语法格式如下:

WORKDIR /path/to/workdir

例:
# 绝对路径
WORKDIR /app

# 相对路径(基于前一个 WORKDIR)
WORKDIR src
WORKDIR api
# 当前路径:/app/src/api

# 多级目录创建
WORKDIR /usr/local/bin

# 使用环境变量
ENV APP_DIR=/opt/application
WORKDIR $APP_DIR

USER

指定运行容器时的用户名或uid,后续的run等指令也会使用指定的用户身份。命令格式如下:

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

例:
# 创建用户并切换
RUN groupadd -r app && useradd -r -g app app
USER app

# 使用用户名和组
USER node:node
USER nginx:nginx

# 使用 UID/GID(推荐用于生产)
USER 1001:1001
USER 999

# 在特定命令中使用不同用户
USER root
RUN apt-get update && apt-get install -y curl
USER app

# Dockerfile 中切换用户
RUN useradd -m -s /bin/bash developer
USER developer
WORKDIR /home/developer

VOLUME

创建一个数据卷挂载点,运行容器时可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保存数据等。命令格式如下:

VOLUME ["/path/to/dir"]
VOLUME /path/to/dir

例:
# JSON 数组格式
VOLUME ["/var/lib/mysql", "/var/log/mysql"]

# 多参数格式
VOLUME /data /logs /config

# 应用数据卷
VOLUME ["/app/data", "/app/logs", "/app/uploads"]

# 数据库卷
VOLUME /var/lib/postgresql/data

# 组合使用
RUN mkdir -p /data && chown app:app /data
VOLUME /data

HEALTHCHECK

用于定义健康检查,配置所启动容器如何进行健康检查,命令格式如下:

#根据所执行命令返回值是否为0来判断
HEALTHCHECK [OPTIONS] CMD command
#禁止基础镜像中健康检查
HEALTHCHECK NONE

例:
# HTTP 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

# 脚本健康检查
HEALTHCHECK --interval=1m --timeout=10s \
    CMD /healthcheck.sh

# 数据库健康检查
HEALTHCHECK --interval=15s \
    CMD pg_isready -U $POSTGRES_USER -d $POSTGRES_DB || exit 1

# 端口检查
HEALTHCHECK --interval=30s \
    CMD netstat -lnt | grep -q :8080 || exit 1

# 禁用健康检查
HEALTHCHECK NONE

选项说明:
--interval:检查间隔(默认 30s)
--timeout:检查超时(默认 30s)
--start-period:启动等待时间(默认 0s)
--retries:失败重试次数(默认 3)

SHELL

更改默认shell,格式如下:

SHELL ["executable", "parameters"]

例:
# Linux - 使用 bash
SHELL ["/bin/bash", "-c"]

# Windows - 使用 PowerShell
SHELL ["powershell", "-Command"]

# 影响后续的 RUN 命令
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -sSL https://get.docker.com/ | sh

# 临时改变 shell
SHELL ["/bin/sh", "-c"]
RUN echo "Using default shell"
SHELL ["/bin/bash", "-c"]
RUN echo "Now using bash"

ONBUILD

用于延迟执行指令,指定当基于所生成镜像创建子镜像时,自动执行的操作指令,在创建专门用于自动编译,检查等操作的基础镜像时,十分有用。命令格式如下:

ONBUILD <INSTRUCTION>

例:
# 基础镜像中的 ONBUILD 指令
FROM node:16-alpine
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
ONBUILD RUN npm run build
ONBUILD CMD ["npm", "start"]

# 复杂场景
ONBUILD ARG BUILD_ENV=production
ONBUILD ENV NODE_ENV=$BUILD_ENV
ONBUILD RUN if [ "$BUILD_ENV" = "development" ]; then \
        npm install; \
    else \
        npm ci --only=production; \
    fi

# 多指令组合
ONBUILD WORKDIR /app
ONBUILD USER app

STOPSIGNAL

用于设置停止信号,指定所创建镜像启动的容器接受退出的信号值。命令格式如下:

STOPSIGNAL signal

例:
# 使用信号名称
STOPSIGNAL SIGTERM
STOPSIGNAL SIGINT
STOPSIGNAL SIGKILL

# 使用信号编号
STOPSIGNAL 15
STOPSIGNAL 9

# 优雅关闭应用
STOPSIGNAL SIGTERM
CMD ["node", "server.js"]

四、容器启动时执行指令

指定容器启动时默认执行的命令及其参数。核心命令:CMD, ENTRYPOINT,EXPOSE。

CMD & ENTRYPOINT

这两个指令都用于指定容器启动时默认执行的命令,但各有侧重:

CMD

提供容器默认的执行命令,可以被docker run后面指定的命令覆盖。如果定义了多个CMD,只有最后一个生效。命令格式如下:

# Exec 形式(推荐)
CMD ["executable", "param1", "param2"]

# Shell 形式,在默认的shell中执行,提供给需要交互的应用
CMD command param1 param2

# 作为 ENTRYPOINT 的参数,提供给entrypoint的默认参数
CMD ["param1", "param2"]

例:
# Exec 形式 - 直接运行应用
CMD ["node", "server.js"]
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]

# Shell 形式 - 通过 shell 执行
CMD npm start
CMD java -jar app.jar

# 为 ENTRYPOINT 提供默认参数
FROM alpine
ENTRYPOINT ["echo"]
CMD ["Hello World"]

# 多参数命令
CMD ["/bin/bash", "-c", "echo $HOME && ls -la"]

# 环境变量扩展
CMD ["sh", "-c", "echo $NODE_ENV && node server.js"]

ENTRYPOINT

用于配置容器作为一个可执行程序。用户主程序,指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。docker run后面的参数会作为参数传递给ENTRYPOINT。每个dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起作用。在运行时可以被--entrypoint参数覆盖 组合使用:CMD可以为ENTRYPOINT提供默认参数,命令格式如下:

# Exec 形式(推荐)
ENTRYPOINT ["executable", "param1", "param2"]

# Shell 形式
ENTRYPOINT command param1 param2

例:
# Exec 形式 - 固定应用
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

# 入口点脚本
ENTRYPOINT ["/docker-entrypoint.sh"]

# 与 CMD 配合使用
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]

# 调试工具入口点
ENTRYPOINT ["/bin/bash"]

# 包装脚本
ENTRYPOINT ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678"]
CMD ["app.py"]

# 或者使用脚本作为入口点
ENTRYPOINT ["/app/start.sh"]
CMD ["--config", "/app/config/prod.yaml"]

# 简单的CMD形式
CMD ["python", "app.py"]

# Shell形式的CMD(不推荐)
CMD nginx -g 'daemon off;'

EXPOSE

声明容器运行时监听的网络端口。这只是一个元数据,实际端口映射需要在运行容器时通过-p参数设置。格式如下:

EXPOSE <port> [<port>/<protocol>...]

例:
# 单端口 TCP
EXPOSE 80

# 指定协议
EXPOSE 80/tcp
EXPOSE 53/udp

# 多端口
EXPOSE 80 443 8080
EXPOSE 3000/tcp 5000/udp

# 数据库端口
EXPOSE 5432
EXPOSE 27017

# 微服务端口
EXPOSE 8080 8443 9000

Dockerfile命令最佳实践说明

高频使用(每个Dockerfile都应有):

FROM - 必需
RUN - 构建核心
COPY - 文件管理
CMD/ENTRYPOINT - 启动定义

中频使用(生产环境推荐)

ENV - 环境配置
WORKDIR - 目录管理
USER - 安全配置
EXPOSE - 端口声明
LABEL - 元数据管理

低频使用(特定场景)

ARG - 构建参数化
VOLUME - 数据持久化
HEALTHCHECK - 健康监控
ADD - 特殊文件处理
ONBUILD - 基础镜像构建

极少使用(特殊情况)

STOPSIGNAL - 信号定制
SHELL - Shell覆盖
MAINTAINER - 已废弃

生产环境推荐

必需命令

FROM - 每个Dockerfile必须有且只有一个,必须是第一条指令

强烈推荐的生产级命令

LABEL - 元数据管理
RUN - 依赖安装和配置
COPY - 文件复制
ENV - 环境配置
WORKDIR - 工作目录设置
USER - 安全运行
EXPOSE - 端口声明
CMD/ENTRYPOINT - 启动定义

按需使用的命令

ARG - 参数化构建
ADD - 特殊文件处理
VOLUME - 数据持久化
HEALTHCHECK - 健康监控
STOPSIGNAL - 信号处理

特殊场景命令

ONBUILD - 基础镜像构建
SHELL - Shell环境定制
MAINTAINER - 已废弃,用LABEL替代

生产环境分类示例

###############################################
# 模块一:基础镜像信息
###############################################
FROM node:18-alpine AS builder

###############################################
# 模块二:维护者信息
###############################################
LABEL maintainer="platform-engineering@company.com"
LABEL version="2.1.0"
LABEL description="Customer API Service"
LABEL org.opencontainers.image.source="https://github.com/company/api"

###############################################
# 模块三:镜像操作指令
###############################################
# 构建参数
ARG NPM_TOKEN
ARG BUILD_ID=unknown

# 环境配置
ENV NODE_ENV=production
ENV NPM_CONFIG_LOGLEVEL=warn
ENV PORT=3000

# 工作目录
WORKDIR /app

# 安装构建工具
RUN apk add --no-cache \
    python3 \
    make \
    g++ \
    && rm -rf /var/cache/apk/*

# 复制依赖文件
COPY package*.json ./
COPY npmrc .npmrc

# 安装依赖
RUN npm ci --only=production && \
    npm cache clean --force

# 复制应用代码
COPY src/ ./src/
COPY config/ ./config/

# 创建非root用户
RUN addgroup -g 1001 -S app && \
    adduser -u 1001 -S app -G app

# 设置文件权限
RUN chown -R app:app /app

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD node healthcheck.js

# 数据卷
VOLUME ["/app/logs", "/app/uploads"]

###############################################
# 模块四:容器启动时执行指令
###############################################
# 声明端口
EXPOSE 3000

# 切换到非特权用户
USER app

# 启动命令
ENTRYPOINT ["node"]
CMD ["src/server.js"]

Released under the MIT License.