Dockerfile 容器镜像构建

工具教程2周前更新 huiye
87 0

在云原生时代,容器化是应用交付的基石,而 Dockerfile 则是构建容器镜像的蓝图。本文整理自实战文档,详细解析如何编写 Dockerfile、常用指令的深层机制、多架构镜像构建方法以及镜像瘦身的最佳实践。

什么是 Dcokerfile?

Dockerfile 是一个文本文件,它包含了一系列指令。Docker 能够读取这些指令,自动构建出一个定制化的镜像。每一条指令通常对应镜像中的一层。

Dockerfile 核心指令详解

基础与元数据

FROM: 指定基础镜像。构建必须基于一个已存在的镜像(如 centos:7, alpine)。

  • 建议:生产环境建议使用轻量级镜像(如 Alpine, Busybox)或包含基础环境的运行时镜像(如 OpenJDK)。
  • 示例
# 使用官方 Python 3.9 Slim 版本作为基础镜像
FROM python:3.9-slim

LABEL (替代 MAINTAINER): 用于添加镜像的元数据,如作者信息、版本等。

  • 示例
LABEL maintainer="devops@example.com"
LABEL version="1.0"
LABEL description="This is a sample image"

USER: 指定运行容器时的用户名或 UID。

  • 注意:该用户必须在容器内提前存在。
  • 最佳实践:出于安全考虑,避免使用 root,推荐使用 UID(如 1001)。
  • 示例
# 创建用户并切换
RUN useradd -m tomcat -u 1001
USER 1001

-u 指定用户id
-m 创建用户的时候创建家目录

文件操作

WORKDIR: 设置工作目录。

  • 如果目录不存在,Docker 会自动创建。后续的 RUN, CMD, COPY 等指令都会在该目录下执行。
  • 示例
# 设置工作目录为 /app
WORKDIR /app
# 此时 copy 会将文件复制到 /app/config.json
COPY config.json ./

COPY: 将宿主机文件复制到容器内。

  • 特性COPY src dest 若 src 是目录,只会复制目录下的内容,不会复制目录本身。
  • 权限:支持在复制时修改权限(避免先复制再 chmod 导致镜像体积增加)。当前使用什么用户去 build 镜像,那么 COPY 的文件就属于什么用户或者组
  • 示例
#复制文件并修改文件权限与属组
COPY --chown=huiye:huiye file /mnt/file
COPY --chmod=644 file /mnt/file
COPY --chown=huiye:huiye --chmod=644 file /mnt/file

ADD: 与 COPY 类似,但功能更高级。

  • 区别:如果源文件是压缩包(如 tar),ADD 会自动解压
  • 建议:明确仅需复制文件时使用 COPY,需要解压时使用 ADD。
  • 示例:
# 自动解压 nginx.tar.gz 到 /usr/share/nginx/html/
ADD nginx.tar.gz /usr/share/nginx/html/

环境变量

ENV: 设置环境变量。

  • 持久化:该变量在容器运行时依然有效。
  • 优先级:docker run -e 的优先级高于 Dockerfile 中的 ENV。
  • 示例
ENV APP_ENV=production
ENV APP_PORT=8080
# 在后续指令中使用变量
CMD ["java", "-jar", "app.jar", "--port=${APP_PORT}"]

ARG: 构建参数。

  • 仅在 docker build 过程中有效,容器运行时不生效
  • 可以通过 docker build --build-arg name=value 动态传入。
  • 示例
FROM centos:7
LABEL maintainer="huiye"
#设置一个参数为username
ARG username
#设置一个有默认值的参数path1,如果没有传入这个参数的值,则这个参数使用默认值"/home/hiuye"
ARG path1="/home/huiye"
ENV NAME=test1
#使用这个参数username
RUN useradd -m $username -u 1001
USER 1001
WORKDIR $path1
CMD ["/bin/bash","-c","whoami && pwd"]

#构建命令
docker build --build-arg username=huiyex --build-arg path1=/home/huiyex -t centos:arg .

执行命令

RUN: 在构建镜像时执行的 Shell 命令。

  • 每一条 RUN 指令都会生成新的一层。
  • 优化:建议使用 && 连接多条命令,并清理缓存以减小体积。
  • 示例
RUN yum install -y net-tools && \
        yum install -y iproute && \
        yum clean all

容器启动

CMD: 容器启动时的默认命令。

  • 格式:推荐使用 JSON 数组格式 ["executable", "param1", "param2"]
  • 覆盖:docker run 后面的命令会覆盖 Dockerfile 中的 CMD。
  • 注意:如果存在多个 CMD,仅最后一个生效。
  • 示例
FROM centos:7
LABEL maintainer="huiye"
ENV NAME=test1
RUN useradd -m huiye -u 1001
USER 1001
WORKDIR /home/huiye
CMD ["/bin/bash","-c","echo \"NAME=$NAME\""]

ENTRYPOINT: 也可以指定容器默认运行的程序

  • 覆盖:启动容器时会执行的命令,会被docker run –entrypoint XX覆盖
  • 与 CMD 区别:docker run 后面的命令会被追加到 ENTRYPOINT 之后,作为参数传递,而不是覆盖它。例:比如 docker run -it –rm –entrypoint ls centos:latest /tmp ,就是将 CMD 的 /tmp 作为参数传给 –entrypoint ls,那么容器内执行的就是 ls /tmp
  • 示例
# 定义固定命令
ENTRYPOINT ["/bin/ping"]
# 定义默认参数(可被覆盖)
CMD ["--help"]

网络与存储

EXPOSE: 声明端口。仅仅是声明,方便阅读和映射,不会自动开启端口映射。

  • 示例
FROM centos:7
LABEL maintainer="huiye"
ENV NAME=test1
RUN useradd -m huiye -u 1001
USER 1001
WORKDIR /home/huiye
CMD ["/bin/bash","-c","echo \"NAME=$NAME\""]
EXPOSE 80
EXPOSE 443

VOLUME: 定义匿名数据卷。即使启动时不指定 -v,容器也会自动挂载该目录。

  • 示例
# 在创建容器的时候,即使没有指定卷,默认就会有 -v /data 
VOLUME ["/data"]
# 指定多个卷
VOLUME ["/var/log", "/var/db"]

易混淆指令解析

CMD vs ENTRYPOINT

指令作用docker run [command] 覆盖?
CMD指定默认命令,完全覆盖
ENTRYPOINT指定入口程序[command] 会作为参数传给 ENTRYPOINT

实战用法: 将 ENTRYPOINT 设置为固定程序,CMD 设置为默认参数。

ENTRYPOINT ["/bin/my-app"]
CMD ["--help"]
# 运行 docker run my-image /tmp 
# 实际执行:/bin/my-app /tmp

COPY vs ADD

  • COPY:语义清晰,仅用于复制本地文件。
  • ADD:包含解压功能。如果你需要将 nginx.tar.gz 放入容器并解压,使用 ADD nginx.tar.gz /usr/share/nginx/html/ 一步到位。

构建命令与实战

标准构建

docker build -t my-app:v2 -f Dockerfile3 .

# -t: 指定镜像名和标签
# -f: 指定 Dockerfile 文件路径(不指定时默认为当前目录下的 Dockerfile)
# .: 上下文目录(COPY 命令寻找文件的根目录)

动态传参构建

配合 Dockerfile 中的 ARG 指令:

docker build --build-arg username=admin -t my-app:args .

进阶:构建多架构镜像 (ARM/AMD)

随着 ARM 架构服务器的普及,构建支持多平台(x86_64/arm64)的镜像变得尤为重要。这需要使用 docker buildx。但是用来构建镜像的基础镜像也需要是 支持 ARM 架构的。可以在 hub 官方查看该镜像是否支持 ARM

环境准备

查看当前构建器支持的架构:

docker builder ls

建议安装一个新的构建器

docker buildx create --name mybuilder --use --platform linux/amd64,linux/arm64 --driver docker-container

参数解析
--name 自定义构建器的名称
--platform 指定构建器支持的目标平台
--driver docker-container 指定构建器的底层驱动模式为“容器化”
--use 创建完成后,立即切换到这个构建器。省去了单独执行 docker buildx use mybuilder 的步骤。也就是说,接下来的 docker buildx build 命令都会默认使用这个新创建的 builder。

 如需要删除 builder,使用以下命令
 docker buildx rm mybuilder

此时看到使用 docker builder ls 可以看到这个builder是inactive 未启用的状态

激活构建器

docker buildx inspect --bootstrap

安装所有架构的仿真环境,用于构建ARM镜像

docker run --privileged --rm tonistiigi/binfmt --install all

可以看到增加了一些架构类型

构建并推送

构建并推送到镜像仓库

docker buildx build --platform linux/arm64 -t xxxhub/huiye-private/buildx:armv1 --push -f ./Dockerfile .

参数解析
--push 构建好了就直接推送到镜像仓库
若需要同时构建多种架构,可以把 linux/arm64 修改为 linux/arm64,linux/amd64

只构建不推送到镜像仓库

docker buildx build --platform linux/arm64/v8 -t xxxhub/huiye-private/buildx:armv2 -f ./Dockerfile . --load

镜像优化最佳实践

选择合适的基础镜像

  • 避免:直接使用臃肿的 OS 镜像(如完整的 Ubuntu/CentOS)。
  • 推荐:Alpine(极小,是基于 Alpine Linux 发行版构建的轻量级 Docker 基础镜像,特别适合容器化环境)、或着一些官方的基础环境镜像 如 python 特定版本的镜像。

多阶段构建

这是减少镜像体积最有效的方法。将编译环境和运行环境分离。

示例

FROM centos:7
RUN rm -rf /etc/yum.repos.d/*
COPY CentOS-7.repo /etc/yum.repos.d/
RUN yum install -y net-tools && \
    yum install -y iproute 
#第一次构建临时容器,编号为0


FROM centos:7
COPY --from=0 /usr/sbin/ifconfig /sbin/
COPY --from=0 /usr/sbin/ip /sbin/
CMD ["/bin/bash"]
#第二次构建临时容器,编号为1。
#COPY --from=0 意为从第一次构建的临时容器中拷贝文件到本次构建的临时容器内

也可以

FROM centos:7 AS builder
#给第一阶段起名为 builder
RUN rm -rf /etc/yum.repos.d/*
COPY CentOS-7.repo /etc/yum.repos.d/
RUN yum install -y net-tools && \
    yum install -y iproute 

FROM centos:7
COPY --from=builder /usr/sbin/ifconfig /sbin/
COPY --from=builder /usr/sbin/ip /sbin/
CMD ["/bin/bash"]
#COPY --from=builder 意为从命名为builder构建的临时容器中拷贝文件到本次构建的临时容器内

减少镜像层数

合并 RUN 指令,参考如下,将多个命令放到一个 RUN 里面,一个 RUN 就是一层,官方不建议镜像层数过多

FROM registry.cn-beijing.aliyuncs.com/dotbalo/alpine:3.20
#RUN sed -i "s@dl-cdn.alpinelinux.org@mirrors.aliyun.com@g" /etc/apk/repositories
#RUN apk update
#RUN apk add nginx curl
RUN sed -i "s@dl-cdn.alpinelinux.org@mirrors.aliyun.com@g" /etc/apk/repositories && \ 
         apk update && \
         apk add nginx curl

权限与文件操作优化

不要这样做:

COPY file /app/
RUN chmod 755 /app/file  # 导致多一层,文件实际存了两份(一份原权限,一份新权限)

推荐做法:

COPY --chmod=755 file /app/
# 或者
COPY --chown=user:group file /app/

© 版权声明

相关文章