前言

前面我们在我们的docker容器中安装了 MySQL,Tomcat 和 Nginx 等镜像

这些镜像都是哪里来的,别人能写,我们肯定也能写。所以我们如何自定义镜像

如果我们要研究自己如何做一个镜像,而且把我们写的项目打包上云部署,docker 就是最方便的。把微服务打包成镜像,任何装了 docker 的地方,都可以下载使用,极其方便。

对于开发者和企业而言:

  • 微服务部署:将应用打包成镜像后,任何安装 Docker 的环境都能一键运行,解决 “环境不一致” 问题。
  • 流程标准化:从开发到部署的流程可固化为:开发应用 → 编写 Dockerfile → 构建镜像 → 上传仓库 → 下载运行,极大简化跨环境移植。
  • 可移植性:镜像包含应用及所有依赖(如库、配置),摆脱对底层系统的依赖,实现 “一次构建,到处运行”。

所以,我们想要制作镜像,这就涉及到我们如何编写 Dockerfile

什么是Dockerfile

Dockerfile的介绍

dockerfile 是一种用于定义和构建 docker 镜像的文本文件。它包含一个个的指令和参数,用于描述镜像的构建过程,包括基础映像、软件包安装、文件拷贝、环境变量设置等,它用命令来说明要执行什么操作来执行构建镜像,每一个指令都会形成一层 Layer

所以说本质上 Dockerfile 是一个文本文件,通过一系列指令定义镜像的构建过程,包括:

  • 基础镜像选择(如基于 CentOS 还是 Ubuntu)
  • 软件包安装(如安装 JDK、Python)
  • 文件拷贝(如将应用代码复制到镜像中)
  • 环境变量配置、端口暴露、启动命令等

它是镜像构建的 “源代码”,通过 docker build 命令可生成可运行的镜像。

通过编写 dockerfile,可以将应用程序、环境和依赖项打包成一个独立的容器镜像,使其可以在不同的环境和平台上运行,实现应用程序的可移植性和可扩展性。

Dockerfile的核心结构如下,一个完整的 Dockerfile 由三部分组成,环环相扣形成镜像构建逻辑:

组成部分 作用 核心指令示例
基础镜像 指定构建的起点,所有操作基于此镜像展开(如 FROM centos:7 FROM
构建过程指令 定义镜像构建中的操作(安装依赖、拷贝文件、配置环境等) RUNCOPYADDWORKDIR
容器启动指令 定义容器启动时执行的命令(如启动应用服务器) CMDENTRYPOINT

Dockerfile 构建镜像的流程

当我们使用 Dockerfile 构建镜像,大概的构建完整步骤如下

image-20250729103026085

而 Docker 构建镜像时,按 “分层构建” 原则执行每条指令,过程如下:

  1. FROM 指定的基础镜像启动一个临时容器。
  2. 执行 Dockerfile 中的第一条指令(如 RUN yum install),修改容器内容。
  3. 类似 docker commit 操作,将修改提交为一个新的镜像层
  4. 基于新生成的镜像层启动下一个临时的新容器,执行下一条指令。
  5. 重复步骤 2-4,执行 dockerfile 中的下一条命令,直到所有指令执行完毕,最终生成完整镜像。

关键特性:每个指令对应一个镜像层,层与层之间只读(除最上层容器运行时可写),这种设计让镜像复用(如多个镜像共享基础层)和增量构建(修改某指令仅重新构建该层及后续层)更高效。

从软件的角度来看,dockerfile,docker 镜像与 docker 容器分别代表软件的三个不同阶段。

  • dockerfile 是软件的原材料(代码)
  • docker 镜像则是软件的交付品(.apk)
  • docker 容器则是软件的运行状态(客户下载安装执行)

dockerfile 面向开发,docker 镜像成为交付标准,docker 容器则涉及部署与运维,三者缺一不可

image-20250729103414571
  • dockerfile:需要定义一个 dockerfile,dockerfile 定义了进程需要的一切东西。dockerfile 涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境等等。
  • docker镜像:在 dockerfile 定义了一个文件之后,docker build 时会产生一个 docker 镜像,当运行 docker 镜像时,会真正开始提供服务;
  • docker容器:容器是直接提供服务的。

Dockerfile指令

先看关键字

1
2
3
4
5
6
7
8
9
10
11
12
FROM         # 基础镜像,当前新镜像是基于哪个镜像的
MAINTAINER # 镜像维护者的姓名混合邮箱地址
RUN # 容器构建时需要运行的命令
EXPOSE # 当前容器对外保留出的端口
WORKDIR # 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
ENV # 用来在构建镜像过程中设置环境变量
ADD # 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
COPY # 类似ADD,拷贝文件和目录到镜像中!
VOLUME # 容器数据卷,用于数据保存和持久化工作
CMD # 指定一个容器启动时要运行的命令,dockerFile中可以有多个CMD指令,但只有最后一个生效!
ENTRYPOINT # 指定一个容器启动时要运行的命令!和CMD一样
ONBUILD # 当构建一个被继承的DockerFile时运行命令,父镜像在被子镜像继承后,父镜像的ONBUILD被触发
image-20250729103552506

接下来单独讲一些重要的 dockerfile 命令

指令使用的核心原则

  1. 分层最小化:合并 RUN 命令,减少镜像层数(每层都会增加镜像体积)。
  2. 可读性优先:合理使用 WORKDIR 简化路径,用 # 添加注释说明指令用途。
  3. 安全性考虑:通过 USER 切换为非 root 用户,避免容器内过高权限。
  4. 可维护性:用 ARG 提取可变参数(如版本号),便于后续升级。

基础镜像与构建环境设置

1. FROM

  • 作用:指定基础镜像,是 Dockerfile 的第一条指令,所有后续操作都基于此镜像。

  • 语法FROM <镜像名>:<标签>FROM <镜像ID>

  • 示例

    1
    2
    FROM ubuntu:22.04  # 基于Ubuntu 22.04构建
    FROM openjdk:17-jdk-slim # 基于OpenJDK 17的精简版镜像
  • 注意

    • 若本地无指定镜像,Docker 会自动从仓库拉取。
    • 特殊镜像 scratch 表示空镜像(用于构建基础工具,如 busybox)。

2. WORKDIR

  • 作用:设置后续指令的工作目录(类似 cd 命令,影响 RUNCOPYCMD 等指令的路径)。

  • 语法WORKDIR <路径>

  • 示例

    1
    2
    WORKDIR /app  # 后续指令默认在/app目录下执行
    COPY demo.jar ./ # 等价于复制到/app/demo.jar
  • 注意

    • 目录不存在时会自动创建。
    • 建议使用绝对路径(如 /app),避免相对路径导致的层级混乱。

3. ENV

  • 作用:设置环境变量,可在镜像构建和容器运行时使用。

  • 语法

    • 单变量:ENV <键> <值>
    • 多变量:ENV <键1>=<值1> <键2>=<值2>
  • 示例

    1
    2
    ENV JAVA_HOME /usr/lib/jvm/java-17-openjdk
    ENV PATH $JAVA_HOME/bin:$PATH # 追加环境变量
  • 注意

    • 容器运行时可通过 docker run -e 键=新值 覆盖环境变量。
    • 变量可在后续指令中引用(如 COPY $APP_NAME ./)。

文件操作与依赖管理

4. COPY

  • 作用:从宿主机复制文件 / 目录到镜像中。

  • 语法COPY <宿主机路径> <镜像内路径>

  • 示例

    1
    2
    COPY target/app.jar /app/  # 复制宿主机target目录下的app.jar到镜像的/app目录
    COPY ./config /app/config # 复制宿主机当前目录的config目录到镜像的/app/config
  • 注意

    • 宿主机路径是相对 Dockerfile 所在目录的相对路径(不能用 ../ 访问父目录)。
    • 若镜像内路径不存在,会自动创建父目录。
    • 复制目录时,仅复制目录内的内容(不含目录本身),如需保留目录结构需显式指定。

5. ADD

  • 作用:功能类似 COPY,但支持额外特性:

    • 自动解压压缩包(如 .tar.zip)到镜像内路径。
    • 支持通过 URL 下载文件到镜像中(不推荐,建议用 RUN wget 替代,便于清理缓存)。
  • 示例

    1
    2
    ADD app.tar.gz /app/  # 解压app.tar.gz到镜像的/app目录
    ADD https://example.com/demo.sh /tmp/ # 下载文件到/tmp目录
  • 注意

    • 优先使用 COPY(功能明确,避免意外解压),仅在需要解压时用 ADD

6. RUN

  • 作用:在镜像构建时执行命令(如安装依赖、编译代码),每条 RUN 会创建一个新的镜像层。

  • 语法

    • shell 格式:RUN <命令>(默认在 /bin/sh -c 中执行)
    • exec 格式:RUN ["可执行文件", "参数1", "参数2"](推荐,避免 shell 解析问题)
  • 示例

    1
    2
    3
    4
    5
    6
    7
    # 安装依赖并清理缓存(合并命令减少层数)
    RUN apt-get update && \
    apt-get install -y gcc make && \
    rm -rf /var/lib/apt/lists/* # 清理APT缓存,减小镜像体积

    # 编译代码(exec格式)
    RUN ["gcc", "demo.c", "-o", "demo"]
  • 关键

    • && 合并多条命令,减少镜像层数(每层都会占用空间)。
    • 及时清理缓存文件(如 aptyum 缓存,npm 临时文件)。

容器运行配置

7. EXPOSE
  • 作用:声明容器运行时计划暴露的端口(仅为文档说明,不实际映射端口)。

  • 语法EXPOSE <端口1> <端口2>/<协议>(默认协议为 TCP)

  • 示例

    1
    2
    EXPOSE 8080  # 声明暴露8080端口(TCP)
    EXPOSE 5000/udp # 声明暴露5000端口(UDP)
  • 注意

    • 容器启动时需通过 docker run -p 宿主机端口:容器端口 实际映射端口。
    • 用于告知使用者该镜像需要暴露哪些端口,增强可读性。

8. CMD

  • 作用:定义容器启动时默认执行的命令(可被 docker run 后的命令覆盖)。

  • 语法:exec 格式(推荐):CMD ["可执行文件", "参数1", "参数2"]

    • shell 格式:CMD 命令 参数1 参数2
  • 示例

    1
    2
    CMD ["java", "-jar", "app.jar"]  # 启动Java应用
    CMD ["nginx", "-g", "daemon off;"] # 启动Nginx(前台运行)
  • 注意

    • 一个 Dockerfile 中只能有一条 CMD,多条时仅最后一条生效。
    • docker run 后指定了命令(如 docker run 镜像名 bash),会覆盖 CMD
  • CMD 与 ENTRYPOINT 区别

    两个命令都是指定一个容器启动时要运行的命令,但二者有很大的区别

    • CMD:容器内有多个 CMD 指令时,只有最后一个 CMD 指令会生效,而如果在执行docker run命令时携带了其它命令,将会覆盖掉所有 dockerfile 的 CMD 指令

    • ENTRYPOINT:ENTRYPOINT 的命令不容易被覆盖。在docker run命令中提供的任何参数都会作为 ENTRYPOINT 命令的参数传递。

    当两者组合使用时:那么 CMD 将作为 ENTRYPOINT 的默认参数,如果在docker run命令中提供了参数,它将覆盖 CMD 并作为 ENTRYPOINT 的参数传递。

9. ENTRYPOINT

  • 作用:定义容器启动时的固定命令(不可被 docker run 后的命令覆盖,仅能追加参数)。

  • 语法

    • exec 格式(推荐):ENTRYPOINT ["可执行文件", "参数1"]
    • shell 格式:ENTRYPOINT 命令 参数1
  • 示例

    1
    2
    ENTRYPOINT ["java", "-jar"]  # 固定执行java -jar
    CMD ["app.jar"] # 默认参数,可被 docker run 镜像名 demo.jar 覆盖
  • 场景

    • 用于需要固定启动逻辑的镜像(如工具类镜像,必须执行特定程序)。
    • 结合 CMD 可实现 “固定命令 + 可变参数” 的灵活配置

10. VOLUME

  • 作用:声明匿名数据卷(容器运行时自动创建,用于持久化数据,避免容器消亡时数据丢失)。

  • 语法VOLUME ["<路径1>", "<路径2>"]VOLUME <路径>

  • 示例

    1
    VOLUME ["/data/mysql"]  # 声明/data/mysql为数据卷,存储MySQL数据
  • 注意

    • 容器启动时,若挂载路径已有数据,会被复制到数据卷中。
    • 可通过 docker run -v 宿主机路径:容器路径 覆盖默认数据卷挂载。

一些高级构建的特性命令内容

11. ARG

  • 作用:定义构建时变量(仅在 docker build 过程中有效,容器运行时不可用)。

  • 语法ARG <变量名>[=<默认值>]

  • 示例

    1
    2
    ARG VERSION=1.0  # 定义默认版本号
    RUN wget https://example.com/app-${VERSION}.tar.gz # 构建时引用变量
  • 使用:构建时通过 --build-arg 传递参数:

    1
    docker build --build-arg VERSION=2.0 -t myapp:2.0 .

12. ONBUILD

  • 作用:定义 “触发指令”,当当前镜像被其他镜像作为基础镜像时,自动执行该指令。

  • 语法ONBUILD <其他指令>

  • 示例

    1
    2
    # 在基础镜像中定义
    ONBUILD COPY ./app /app # 当其他镜像FROM此镜像时,自动执行COPY
  • 场景:用于构建 “基础模板镜像”(如开发环境模板),简化子镜像的构建流程。

13. USER

  • 作用:指定后续指令(RUNCMDENTRYPOINT 等)的执行用户(默认用 root)。

  • 语法USER <用户名/UID>

  • 示例

    1
    2
    3
    RUN useradd -m appuser  # 创建普通用户
    USER appuser # 后续命令以appuser身份执行
    CMD ["app"] # 应用程序以非root用户启动,提高安全性
  • 注意

    • 切换用户前需确保该用户已存在(可通过 RUN useradd 创建)。
    • 生产环境中尽量避免用 root 运行应用

如何编写 Dockerfile

以自定义 arch 为例子

由于 Arch Linux 在社区上的版本都是内核级别的,拿过来什么东西都没有,我们就以这个为例子,往Arch中装上我们常用的工具,然后打包发布,以此来学习如何编写 Dockerfile

了解 Arch Linux:Arch 是一个轻量级、滚动更新的 Linux 发行版,适合自定义环境、

目标:构建一个包含vimgitwgetzsh等工具的基础 Arch 镜像

首先我们需要编写dockerfile,理论上可以放在任意目录,但从规范、便捷性角度,建议遵循以下逻辑

  1. 独立目录存放(推荐)

    为 Dockerfile 创建一个专门的项目目录,把构建镜像所需的所有文件(如应用代码、配置文件、脚本等)和 Dockerfile 放在一起。比如要构建自定义 Arch 镜像,可这样组织:

    1
    2
    3
    4
    my-arch-image/
    ├── Dockerfile # 核心构建文件
    ├── scripts/ # 可选,存放构建时需要的脚本(如初始化脚本)
    └── config/ # 可选,存放配置文件(如 pacman 源配置)

    构建上下文(docker build 时传递的目录)明确,避免无关文件被打包进镜像。

  2. 如果是为某个应用(比如 Python 脚本、Java 程序)构建镜像,直接把 Dockerfile 放在应用代码的根目录。例如一个 Flask 项目:

    1
    2
    3
    4
    my-flask-app/
    ├── app.py # 应用代码
    ├── requirements.txt # 依赖清单
    └── Dockerfile # 构建镜像的配置
    • 构建镜像时,能直接通过 COPY . /app 把整个应用代码复制到镜像里,无需额外处理路径。
    • 开发、构建流程自然衔接,团队成员 clone 代码后,在目录里直接执行 docker build 就能构建镜像。

我们选择第一个模式,创建好你的目录之后,使用以下命令进入

1
cd ~/workspace/docker/dockerfileAbout/MyArchDemo

创建dockerfile并且打开 vim 编辑器,按 i 进入插入模式,输入 :wq 并回车

1
vim Dockerfile

然后,其中 Dockerfile 的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 1. 指定基础镜像
FROM archlinux:latest

# 2. 维护者信息(可选,用于标识镜像作者)
LABEL maintainer="yourname <youremail@example.com>"
LABEL description="Custom Arch Linux image with basic tools"

# 3. 切换为root用户(Arch镜像默认就是root,但显式声明更清晰)
USER root

# 4. 更新系统并安装必要工具
# 先更新软件包数据库
RUN pacman -Syu --noconfirm \
# 安装常用工具:文本编辑器、版本控制、下载工具等
&& pacman -S --noconfirm \
vim \
git \
wget \
curl \
zsh \
tree \
unzip \
# 基础网络工具(ping、ifconfig等)
net-tools \
iputils \
# 清理缓存(减小镜像体积)
&& pacman -Scc --noconfirm

# 5. 创建普通用户(安全最佳实践,避免直接使用root)
# 创建用户名为archuser,UID=1000的用户
RUN useradd -m -u 1000 archuser \
# 给用户添加sudo权限(需要输入密码)
&& pacman -S --noconfirm sudo \
&& echo "archuser ALL=(ALL) ALL" >> /etc/sudoers
# 关键:给archuser设置密码,替换yourpassword为你想要的密码(如123456)
&& echo "archuser:yourpassword" | chpasswd

# 6. 设置工作目录
WORKDIR /home/archuser

# 7. 切换到普通用户
USER archuser

# 8. 容器启动时默认进入zsh终端
CMD ["zsh"]

我个人习惯直接链官方的,如果网不太行,可以用这个

1
2
3
4
5
# 替换为国内源(清华大学镜像)
RUN echo "Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist

# 中科大源
RUN echo "Server = https://mirrors.ustc.edu.cn/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist

极少数情况下,更换源后可能出现 “密钥验证失败”,可在更换源后添加密钥初始化命令:

1
2
3
RUN echo "Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist \
&& pacman-key --init \
&& pacman-key --populate archlinux

如果需要配置代理。在 Dockerfile 中添加代理设置(替换为实际代理地址):

1
2
ENV http_proxy=http://your-proxy:port \
https_proxy=http://your-proxy:port

每行命令我们仔细剖析一下,来了解编写 Dockerfile 的逻辑

  1. FROM archlinux:latest
    • 基础镜像声明:基于官方最新的 Arch Linux 镜像构建
    • 官方镜像非常精简,只包含最基础的系统组件
    • 如果本地没有这个镜像,docker build时会自动从 Docker Hub 下载
  2. LABEL指令
    • 用于给镜像添加元数据(如作者、描述)
    • 可以通过docker inspect 镜像名查看这些信息
    • 属于可选指令,但建议添加以提高镜像可维护性
  3. USER root
    • 指定后续命令的执行用户(Arch 镜像默认就是 root)
    • 安装软件需要 root 权限,所以这里显式指定更清晰
  4. RUN指令(核心步骤)
    • pacman -Syu --noconfirm:Arch 的包管理器命令
      • SyuS同步数据库,y更新系统,u升级所有包
      • --noconfirm:自动确认所有操作(非交互式安装)
    • pacman -S --noconfirm工具列表:安装指定软件
      • 列举的都是常用工具:vim(编辑器)、git(版本控制)、wget/curl(下载工具)等
    • pacman -Scc --noconfirm:清理缓存
      • 安装软件后会留下缓存文件,清理后能显著减小镜像体积
  5. 创建普通用户
    • useradd -m -u 1000 archuser:创建用户
      • -m:自动创建用户主目录(/home/archuser
      • -u 1000:指定用户 ID(1000 是 Linux 系统普通用户的常用 ID)
    • 安装 sudo 并配置权限:允许普通用户执行管理员命令
      • 这是安全最佳实践:容器内尽量避免直接使用 root 运行程序
  6. WORKDIR /home/archuser
    • 设置后续命令的工作目录(类似cd命令)
    • 容器启动后会默认进入这个目录
  7. USER archuser
    • 切换到普通用户(后续命令和容器启动都会用这个用户)
    • 再次强调:非 root 用户运行更安全
  8. CMD ["zsh"]
    • 容器启动时默认执行的命令:启动 zsh 终端
    • 当你运行容器时,会直接进入 zsh 交互界面

Dockerfile 文件已经编写完毕,我们在 Dockerfile 所在目录执行以下命令来构建镜像:

1
2
# 构建镜像,标签为custom-arch:v1
docker build -t custom-arch:v1 .
  • -t custom-arch:v1:给镜像命名(custom-arch)和打标签(v1
  • .:表示 Dockerfile 所在的当前目录

构建过程说明:

  1. Docker 会逐行执行 Dockerfile 中的指令
  2. 首次构建会下载基础镜像,可能需要几分钟(取决于网络)
  3. 每一步执行完成后会创建一个镜像层
  4. 看到Successfully built <镜像ID>表示构建成功
image-20250729112645108

欸我去,什么情况,没绷住,我 Docker 没开,没用root,sudo -i起手忘了,希望大家引以为戒

接下来让他搁这慢慢自己构建着玩去吧

image-20250729112903718

构建完成后,运行容器测试:

1
2
# 启动容器并进入交互模式
docker run -it --rm custom-arch:v1
  • -it-i保持标准输入打开,-t分配伪终端(交互模式必备)
  • --rm:容器退出后自动删除(测试用,避免残留无用容器)

第一次启动这其中,会出现如下窗口

image-20250729144342119

这是 Zsh(Z Shell)的首次配置向导,出现的原因是你的容器里的 archuser 用户是第一次启动 Zsh,且家目录(~)下没有 Zsh 的启动配置文件(如 .zshrc.zprofile 等 )。Zsh 作为更强大的 Shell(比默认的 Bash 功能更丰富),首次运行时会检查用户家目录下的启动脚本(.zshrc 等 )。如果这些文件不存在,就会触发 zsh-newuser-install 向导,帮你初始化配置

如果你想配,就选择1,不想配使用最基础的就选择 0

进入容器后可以验证:

  1. 查看当前用户:whoami → 应该显示archuser
  2. 检查工具是否安装:vim --versiongit --version
  3. 尝试使用sudosudo pacman -Ss nginx(需要输入密码,默认无密码直接回车)
  4. 退出容器:exit
image-20250729144708414

到这里是构建成功了,可以参考我的把镜像推送到阿里云这一篇,来试着把镜像推送到阿里云

如果你不想推送,那么这个镜像位置在 /var/lib/docker,你可以选择清理掉,dcoker imagesdocker rmi

如果你下载了一个镜像,报错了或者你想查看一些构建逻辑,使用 docker history,可以查看镜像的变更历史

项目中编写一个 dockerfile 的思路如下

  1. 基于一个空的镜像
  2. 下载需要的环境 ADD
  3. 执行环境变量的配置 ENV
  4. 执行一些Linux命令 RUN
  5. 日志 CMD
  6. 端口暴露 EXPOSE
  7. 挂载数据卷 VOLUMES

以构建java项目为例子

我们基于 Ubuntu 镜像构建一个新的镜像,运行一个 java 项目

我们在本地电脑上的java项目,我希望把它搞到VMWare上,应该怎么搞,就使用VMWare Tools 共享文件,或者SSH也可以,我个人习惯 Xftp

先把我们的 Java 项目进行打包,这里我以一个我之前写的 JavaFx 为例子,因为我在写这篇文章的时候 Linux 上的 tomcat 有点问题,我还没搞好,所以就用这种简单一些的来演示

image-20250729153241444

传输到你的虚拟机中,然后我们开始编写Dockerfile,由于这个就是个普通的JavaFx程序,没啥好多说的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 选择基于 Alpine Linux 的 OpenJDK 21 运行时镜像
FROM openjdk:21-jre-slim

# 维护者信息,可按需修改为你的信息
LABEL maintainer="ErgouTree"
LABEL description="Docker image for running json-parser Java project with Java 21"

# 在镜像内创建工作目录,后续操作都基于此目录
WORKDIR /app

# 将本地的 jar 包复制到镜像内的工作目录,这里重命名为简单的名称方便后续操作,你也可以不改名
COPY json-parser-1.0-SNAPSHOT.jar /app/json-parser.jar

# 暴露端口,假设你的项目是 Web 应用且对外提供服务的端口是 8080,需根据实际情况调整
EXPOSE 8080

# 定义容器启动时执行的命令,用于运行 jar 包
CMD ["java", "-jar", "json-parser.jar"]
  • FROM openjdk:21-jre-alpine:指定基础镜像为
  • LABEL:用于添加镜像的元数据,如维护者信息和镜像描述,方便管理和识别镜像。
  • WORKDIR /app:在镜像内部创建 /app 目录,并将其设置为后续指令的工作目录,这样在使用 COPY 等指令时,路径会相对简洁明了。
  • COPY json-parser-1.0-SNAPSHOT.jar /app/json-parser.jar:将本地的 json-parser-1.0-SNAPSHOT.jar 文件复制到镜像内的 /app 目录,并可选择重命名(也可保持原名),方便后续命令引用。
  • EXPOSE 8080:声明容器在运行时会暴露 8080 端口,这只是一个声明,实际的端口映射需要在 docker run 时通过 -p 参数指定。如果项目不是 Web 应用,没有对外暴露端口的需求,可以省略这一步。
  • CMD ["java", "-jar", "json-parser.jar"]:定义容器启动时默认执行的命令,这里使用 java -jar 命令来运行 json-parser.jar 文件。当使用 docker run 启动容器时,如果没有额外指定其他命令,就会执行此命令。

然后我们构建镜像,在包含 Dockerfilejson-parser-1.0-SNAPSHOT.jar 的目录下,执行以下命令来构建镜像:

1
docker build -t json-parser-java21-app:1.0 .

虽然我的不是,但是我还是在这里讲解一下

可以看到我在上面开放了 8080 端口,因为我把这个当成 Web 应用来讲了,那么运行容器就会有所不同

  • 如果是 Web 应用

    1
    docker run -p 8080:8080 json-parser-java21-app:1.0
  • 如果不是 Web 应用

    1
    docker run json-parser-java21-app:1.0

直接运行容器,查看容器内项目的输出日志等信息。

查看一下

1
docker imgaes

如果你的 Java 项目在启动时需要传递额外参数,比如设置 JVM 内存参数、指定配置文件,可以在Dockerfile中额外指定

如果你从Ubuntu基础镜像来开始构建一个新镜像,然后运行一个java项目,可以参考之前我写的 dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 基于Ubuntu 22.04基础镜像,你也可以根据需求选择其他版本,如ubuntu:20.04
FROM ubuntu:22.04

# 设置镜像的维护者信息(可选,方便他人了解镜像的相关信息)
LABEL maintainer="your_name <your_email@example.com>"
LABEL description="Docker image for running Spring Boot project with JDK 21"

# 配置环境变量,指定JDK的安装目录,这里设置为/usr/local/jdk21
ENV JAVA_DIR /usr/local/jdk21

# 下载并安装JDK 21
RUN apt-get update && \
apt-get install -y wget && \
wget -O /tmp/openjdk-21_linux-x64_bin.tar.gz https://download.java.net/java/GA/jdk21/42176864966f4920811c57e92438f591/35/GPL/openjdk-21_linux-x64_bin.tar.gz && \
mkdir -p $JAVA_DIR && \
tar -xf /tmp/openjdk-21_linux-x64_bin.tar.gz -C $JAVA_DIR --strip-components=1 && \
rm /tmp/openjdk-21_linux-x64_bin.tar.gz && \
apt-get remove -y wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# 配置环境变量,指定Java主目录和将Java可执行文件路径添加到PATH中
ENV JAVA_HOME $JAVA_DIR
ENV PATH $JAVA_HOME/bin:$PATH

# 创建应用工作目录,后续操作都在这个目录下进行
WORKDIR /app

# 拷贝Spring Boot项目的jar包到镜像中的/app目录下,假设你的项目jar包名为app.jar,可根据实际情况修改
COPY app.jar /app/

# 暴露应用运行的端口,假设你的Spring Boot项目监听8080端口,根据实际情况调整
EXPOSE 8080

# 定义容器启动时执行的命令,运行Spring Boot项目的jar包
CMD ["java", "-jar", "/app/app.jar"]

其中 JDK 安装的部分我详细说明一下

它通过一系列 RUN 命令来完成 JDK 21 的安装:

  • apt-get update 更新软件包列表。
  • apt-get install -y wget 安装 wget 工具,用于下载 JDK 安装包。
  • wget -O /tmp/openjdk-21_linux-x64_bin.tar.gz https://download.java.net/java/GA/jdk21/... 从 Java 官方网站下载 JDK 21 的二进制压缩包到 /tmp 目录。
  • mkdir -p $JAVA_DIR 创建 JDK 安装目录。
  • tar -xf /tmp/openjdk-21_linux-x64_bin.tar.gz -C $JAVA_DIR --strip-components=1 解压下载的压缩包到指定的 JDK 安装目录,并去掉一层目录结构。
  • rm /tmp/openjdk-21_linux-x64_bin.tar.gz 删除下载的压缩包,节省镜像空间。
  • apt-get remove -y wgetapt-get clean 移除 wget 软件包并清理软件包缓存,进一步减小镜像体积。
  • rm -rf /var/lib/apt/lists/* 删除软件包列表缓存文件。

只不过,openjdk:21-jre-alpine帮我们把上面这些步骤全部做完了