Podman 教程

Containerfile 基本语法

前面了解了 Containerfile 的基本知识,以及与 Dockerfile 的区别,下面将介绍 Containerfile 的基本语法,为后续编写 Containerfile 做铺垫。

注意:Containerfile 语法与 Dockerfile 高度兼容,核心指令和逻辑一致,以下是最常用的基础语法和指令说明,附带示例:

基本结构

Containerfile 是一个文本文件,由指令(大写关键词)+ 参数组成,指令按顺序执行,每一行通常对应一条指令(换行可用 \ 转义)。

例如:

# 注释:基础镜像
FROM 镜像名称:标签

# 维护者信息(可选)
LABEL maintainer="用户名 <邮箱>"

# 容器启动命令
CMD ["可执行文件", "参数1", "参数2"]

上述中,FROM、LABEL 和 CMD 为指令,镜像名称:标签、maintainer="用户名 <邮箱>" 等为参数和参数值。

  

常用指令

FROM(必选)基础镜像

指定基础镜像,所有构建都需基于一个基础镜像(可理解为 “继承”)。

示例:

FROM ubuntu:22.04  	# 基于 Ubuntu 22.04
FROM alpine:latest  # 基于轻量 Alpine 系统
FROM scratch  			# 基于空镜像(最小化构建)

  

RUN 执行命令

在镜像构建过程中执行命令(如安装软件、创建目录等),执行结果会被打包到新的镜像层。

RUN 命令有两种格式:

shell 格式

RUN 命令(默认在 /bin/sh -c 中执行),示例:

RUN apt update && apt install -y python3

上面使用“&&”符号合并命令减少镜像分层。

exec 格式

RUN ["可执行文件", "参数1", "参数2"]

exec 格式的 RUN 指令,可直接调用可执行文件、不经过 Shell 解析。

容器引擎直接找到镜像内的“可执行文件”,并将 “参数 1”“参数 2” 作为独立参数传递给它,全程不启动 Shell 进程。

注意,exec 格式不会让 Shell 处理指令中的特殊字符(如 $ 变量、| 管道、&& 逻辑符等)。例如:

  • Shell 格式:RUN echo $HOME 会让 Shell 解析 $HOME 为当前用户目录;

  • exec 格式:RUN ["/bin/echo", "$HOME"] 则直接输出字符串 $HOME,不会解析变量。

由于 exec 格式不依赖 Shell 解析,可避免因 Shell 特性导致的风险:

  • 防止意外解析环境变量(如敏感信息泄露)

  • 避免特殊字符被 Shell 误处理(如参数中含 | 时,不会被当作管道符执行额外命令,减少注入风险)

示例:

RUN ["pip", "install", "flask"]

  

COPY 复制文件/目录

将宿主机文件 / 目录复制到镜像中。

注意,仅支持复制宿主机本地的文件 / 目录,不支持通过 URL 下载文件,也不自动解压压缩包(若需解压,通常需搭配 RUN 指令手动处理)。

示例:

COPY index.html /usr/share/nginx/html/

运行上述命令,将宿主机的 index.html 文件复制到镜像的 usr/share/nginx/html/ 下,供镜像内的 Nginx 等服务调用。

  

ADD 复制文件/目录

功能与 COPY 类似,都能实现将文件或目录复制到构建中的镜像里,但 ADD 具备 COPY 没有的额外能力:

  • 支持通过 URL 从网络下载文件并复制到镜像内

  • 若复制的是 .tar.gz、.tar.bz2 等格式的压缩包,会自动在镜像内完成解压操作

示例:

ADD https://example.com/app.tar.gz /app

上诉指令,作用是从网络地址 https://example.com/app.tar.gz 下载名为 app.tar.gz 的压缩包文件,然后将其复制到镜像内的 /app 目录下,并且在复制完成后,会自动对该压缩包进行解压处理,将压缩包内的文件释放到 /app 目录中。

注意:虽然 ADD 功能更丰富,但建议优先使用 COPY 指令。因为 COPY 指令功能单一明确,仅用于本地文件 / 目录的复制,避免了 ADD 因支持 URL 下载和自动解压可能带来的意外行为(比如误将非压缩格式文件当作压缩包尝试解压),让 Contianerfile 的构建过程更可控、更清晰。只有在明确需要从网络下载文件或自动解压压缩包的场景下,才考虑使用ADD指令。

  

WORKDIR 设置工作目录

设置后续指令(如 RUN、COPY、CMD、ENTRYPOINT 等)的默认工作目录,效果类似 Linux 中的 cd 命令,但区别在于 WORKDIR 设定后,后续所有相关指令都会基于该目录执行,直到下一个 WORKDIR 重新指定,具有 “持久生效” 的特性(而非仅单次命令生效)。

示例:

WORKDIR /app  		# 后续命令默认在 /app 目录执行
COPY ./src ./  		# 等价于复制到 /app 目录

上述示例中,当执行 WORKDIR /app 后,后续的 COPY ./src ./ 等价于 COPY ./src /app/,即把宿主机的 ./src 目录复制到镜像内的 /app 目录下。如果后续执行 RUN npm install,也会默认在 /app 目录中运行该命令。

注意,如果指定的目录(如 /app)不存在,容器引擎会自动创建该目录,无需额外通过 RUN mkdir 指令手动创建,简化构建步骤。

  

EXPOSE 端口声明

作为镜像的“文档说明”,告知使用者该容器运行时可能会监听的端口,无实际端口映射能力(即不会自动将容器内端口暴露到宿主机)。

示例:

EXPOSE 8080

上述命令,提示该容器可能使用 8080 端口,运行时需用 -p 手动映射。

注意:

  • 即使声明了 EXPOSE 8080,容器运行时也可实际使用其他端口,如:8090。

  • 映射端口需在启动容器时,通过 docker run -p 宿主机端口:容器端口(如 podman run -p 80:8080)手动建立宿主机与容器的端口映射,否则外部无法访问容器内端口。

  

CMD 与 ENTRYPOINT(容器启动命令)

两者均用来定义容器启动时执行的命令。它们的区别如下:

  • CMD  容器启动时的默认命令,可被 podman run 的命令参数覆盖。例如:

CMD ["python3", "app.py"]

上述命令,启动后默认执行 python3 app.py,若运行时加 bash 则覆盖。

  • ENTRYPOINT  固定容器启动的“入口程序”,CMD 的参数会作为其参数传递,且无法被运行时命令直接覆盖(需用 --entrypoint 强制修改),通常需搭配 CMD 传递默认参数。例如:

ENTRYPOINT ["python3"]
CMD ["app.py"]

上述命令等价于启动时执行 python3 app.py,运行时加 "test.py" 则变为 python3 test.py

注意:

  • 如果想让容器启动命令灵活可修改(如随时切换执行脚本、进入终端),用 CMD。

  • 如果想让容器功能固定化(如始终以某个程序为入口,仅调整参数),用 ENTRYPOINT + CMD 组合。

  

LABEL 添加元数据

用于为镜像添加元数据信息,这些信息不会影响镜像的运行功能,主要用于镜像的管理、识别和文档化,方便开发者或运维人员快速了解镜像的关键属性(如作者、版本、用途等)。

注意,可定义的元数据无固定限制,常见的有作者(maintainer,虽有单独 MAINTAINER 指令,但更推荐用 LABEL maintainer 统一管理)、版本(version)、描述(description)、许可证(license)等。

还可在一条 LABEL 指令中通过空格分隔多个键值对,也可分多条指令定义。最终,容器引擎会自动合并所有 LABEL 信息。

通过 docker inspect 命令可查看镜像的所有 LABEL 元数据,便于自动化脚本识别或人工排查。例如:

C:\Users\hxstri> podman inspect my-nginx
[
     {
          //...
          "Config": {
               "Hostname": "3b16d172fcd5",
               "Domainname": "",
               //...
               "Labels": {
                    "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
               },
               //...
          },
          //...
     }
]

示例:单条指令定义多标签

LABEL version="1.0" maintainer="dev@example.com" description="A simple Nginx web app" license="MIT"

示例:多条指令定义(效果同上)

LABEL version="1.0"
LABEL maintainer="dev@example.com"
LABEL description="A simple Nginx web app" license="MIT"

当镜像数量较多或团队协作时,LABEL 能让镜像的归属、版本迭代、用途等信息更清晰,避免因信息缺失导致的管理混乱(例如通过脚本筛选特定作者或版本的镜像)。

  

ENV 设置环境变量

用于设置环境变量,在构建和运行时均生效。

镜像构建过程中(如后续的 RUN 指令),可直接引用该环境变量(例如 RUN echo $VAR_NAME)。

基于该镜像启动的容器中,环境变量会被继承,容器内的应用或命令可读取使用(例如 Python 程序通过 os.getenv("VAR_NAME") 调用)。

基本语法:

ENV 变量名=变量值

注意,等号前后无空格,如果值含空格需用引号包裹,如 ENV APP_NAME="My Docker App"。

示例:

ENV PYTHONUNBUFFERED=1

上述命令,将禁用 Python 输出缓冲,确保容器日志能实时捕获 Python 程序的打印内容,方便调试和日志监控。

ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk

上述命令,为 Java 应用指定 JDK 安装路径,避免后续指令重复写路径,简化配置。

ENV 命令的核心作用:

  • 简化配置:将高频使用的路径、参数等定义为变量,减少 Containerfile 中的重复代码,便于后续修改(如修改 JAVA_HOME 只需改一处)。

  • 统一环境:确保镜像构建和容器运行时使用相同的变量值,避免因环境不一致导致的程序异常。

  

VOLUME 声明匿名卷

用于在镜像中声明匿名卷,核心目的是解决容器内数据易丢失的问题 —— 当容器被删除时,容器内未挂载卷的目录数据会随容器一同消失,而声明为卷的目录,其数据会被持久化存储在宿主机的指定位置,实现容器与数据的解耦。

关键特性:

  • 匿名性:声明的卷没有自定义名称,Docker 会自动生成一串随机字符串作为卷名(区别于 docker run -v 自定义卷名:/data 的「命名卷」)。

  • 自动创建:无需提前在宿主机创建目录,当容器启动时,容器引擎会自动在宿主机的 /var/lib/containers/storage/volumes/ 目录下生成对应卷的存储目录,并将容器内声明的目录(如 /data)与该宿主机目录挂载关联。

  • 数据共享:多个容器可挂载同一个匿名卷,实现容器间的数据共享(需通过命令手动关联,而非仅靠镜像内的 VOLUME 指令)。

示例:

VOLUME ["/data"]

上面命令,容器内 /data 目录会被挂载为卷。当容器运行时,容器引擎会自动创建匿名卷。容器内 /data 目录的所有操作(如写入文件、修改数据),实际都作用于宿主机 /var/lib/containers/storage/volumes/.../_data 目录。

注意:即使删除该容器,宿主机上的匿名卷数据仍保留,可通过 podman volume ls 查看,后续新容器可重新挂载该卷复用数据。

C:\Users\hxstri> podman volume ls
DRIVER      VOLUME NAME
local       rabbitmq_data
local       mysql57_data
local       redis_data
local       nginx-html
local       30b8c155060ca1a884643bb8263b9856ac3e85f849008255bd7096c3efafa71f
local       08c4b9dd2ecd12f379b274e4e4a1f580c2289372d052df476fe4ebb2f0ccfbe3
local       9a2fb84914cb5fa446fbfe21c4712fd331ff076a85457447150b01ce15060099
local       ee970b5d8b12ed620d19ed1a2fd72575b3ca2c3ae8235db227bbd2a14fd87883
local       1ac8cb121cb7ab0a156865603487f1aba50c7b477aa21b492bb597e244e5763f

如果通过 COPY/ADD 向镜像中 VOLUME 声明的目录(如 /data)复制文件,容器启动后,这些文件会被自动同步到宿主机的卷目录中,后续容器对该目录的修改不会影响镜像本身。

  

USER 指定用户

指定镜像构建后续指令(如 RUN、CMD、ENTRYPOINT 等)的运行用户身份,同时也会影响容器启动后默认的运行用户。

如果未显式指定 USER 指令,镜像和容器默认以 root 用户执行命令,root 拥有系统全部操作权限,一旦容器被入侵,攻击者可能通过容器影响宿主机,存在安全隐患。

示例:

USER appuser

上述命令,表示后续命令以 appuser 身份执行。

通常需先通过 RUN 指令创建非 root 用户(如 RUN adduser -D appuser,在 alpine 系统中创建无密码的 appuser),再用 USER 指令切换到该用户。将以 appuser 身份执行后续指令,将权限限制在该用户的可操作范围内,降低安全风险。

到这里,Containerfile 常用指令就介绍完了。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号