Docker 镜像制作教程

前言

在容器化技术盛行的当下,Docker 镜像作为应用打包和分发的核心载体,是实现 "一次构建、到处运行" 的关键。本文从基础概念出发,一步步拆解镜像制作的完整流程,结合 Java Spring Boot 和 Node.js 实战案例,补充详细注释和避坑技巧,无论是新手还是有一定经验的开发者,都能跟着快速上手!

一、什么是 Docker 镜像?

Docker 镜像是一个只读的模板文件,可以理解为 "应用运行的最小环境包",它包含了运行某个应用所需的所有依赖资源,具体包括:

  • 底层操作系统基础(如 Linux 内核相关文件)
  • 应用依赖的库(如 Java 的 JDK、Node.js 的运行环境)
  • 应用源代码或编译后的产物(如 JAR 包、JS 文件)
  • 配置文件(如应用的 config.yml、环境变量配置)
  • 运行时所需的命令和参数

简单来说,镜像就是 "打包好的应用环境",通过它可以快速启动容器(容器是镜像的运行实例)。核心特性:

  • 镜像通过 Dockerfile 文本文件定义构建规则
  • 使用 docker build 命令根据 Dockerfile 构建出实际镜像
  • 镜像采用 "分层存储" 机制,每一条构建指令对应一个层,层可复用,加速构建和分发

二、制作 Docker 镜像的核心步骤(通用流程)

✅ 步骤 1:准备应用程序(前置条件)

在制作镜像前,必须确保应用本身可正常运行,并明确其依赖项。常见示例:

  • Java 应用:需要 JDK(编译时)/ JRE(运行时)、打包后的 JAR/WAR 包
  • Node.js 应用:需要 Node.js 环境、package.json(依赖清单)
  • Python Flask 应用:需要 Python 环境、requirements.txt(依赖清单)

📌 关键注释:建议将应用相关的所有文件(代码、依赖清单、配置)统一放在一个独立目录下(如 my-app/),避免无关文件干扰,同时方便后续构建上下文的识别。

✅ 步骤 2:编写 Dockerfile(核心环节)

Dockerfile 是一个纯文本文件,包含一系列按顺序执行的指令,Docker 引擎会根据这些指令自动构建镜像。

示例:Java Spring Boot 应用的 Dockerfile(带详细注释)

dockerfile

复制代码
# 1. 指定基础镜像(必须是 Dockerfile 第一条指令)
# 选择官方 openjdk 17 的 slim 版本(轻量,减少镜像体积)
FROM openjdk:17-jdk-slim

# 2. 设置容器内的工作目录(后续命令如 COPY、RUN 都会在此目录执行)
# 建议使用绝对路径,避免路径混乱
WORKDIR /app

# 3. 复制本地的 JAR 包到容器的 /app 目录下(与工作目录一致)
# 格式:COPY 本地文件路径 容器内目标路径
COPY app.jar /app/app.jar

# 4. 暴露容器监听的端口(仅为文档说明,不实际映射端口)
# 告诉使用者该应用在容器内运行在 8080 端口,方便后续 docker run 时映射
EXPOSE 8080

# 5. 定义容器启动时执行的命令(不可被 docker run 命令覆盖,稳定性更高)
# 采用 JSON 数组格式(exec 形式),避免 shell 解析带来的信号传递问题
ENTRYPOINT ["java", "-jar", "app.jar"]
常用 Dockerfile 指令说明(带使用场景注释)
指令 作用说明 使用场景示例
FROM 指定基础镜像(必须第一条) 基于官方镜像扩展(如 openjdk、node)
WORKDIR 设置工作目录(后续命令的执行目录) 统一文件路径,避免相对路径混乱
COPY 从主机复制文件 / 目录到镜像中 复制应用代码、配置文件
ADD 类似 COPY,但支持 URL 下载和自动解压 tar 包(优先用 COPY,避免功能冗余) 下载远程依赖包并解压
RUN 构建镜像时执行的命令(如安装软件、配置环境) 安装依赖库(apt install)、编译代码
CMD 容器启动时默认执行的命令(可被 docker run 覆盖) 启动应用(如 node app.js)
ENTRYPOINT 容器启动时的主命令(不可覆盖,常与 CMD 配合传参) 固定启动命令,CMD 传递参数
EXPOSE 声明容器监听的端口(文档作用,非实际端口映射) 说明应用端口,方便使用者映射
ENV 设置环境变量(构建时和容器运行时都生效) 配置应用的环境(如 SPRING_PROFILES_ACTIVE=prod)
VOLUME 定义数据挂载点(持久化数据,避免容器删除后数据丢失) 存储应用日志、数据库数据
💡 Dockerfile 最佳实践(带注释说明)
  1. 使用 .dockerignore 文件排除无关文件:避免将 .git、日志、编译缓存(如 target/node_modules/)复制到镜像,减小体积
  2. 优先选择官方轻量镜像:如 alpine(极小体积,适合纯运行环境)、-slim(精简版,平衡体积和功能)
  3. 合并 RUN 命令减少镜像层数:用 && 连接多个命令,最后清理临时文件(如 apt 缓存),减少层数同时减小体积
  4. 按 "变更频率" 排序指令:频繁变更的指令(如 COPY 应用代码)放在后面,利用 Docker 缓存加速构建

✅ 步骤 3:创建 .dockerignore 文件(推荐,减小镜像体积)(可选)

类似 Git 的 .gitignore.dockerignore 用于指定构建镜像时不需要复制到镜像中的文件 / 目录,避免无关文件增大镜像体积。

示例 .dockerignore 文件(带注释)

bash

复制代码
# 排除 Git 版本控制相关文件
.git
.gitignore

# 排除文档类文件(无需打包进镜像)
README.md
LICENSE

# 排除 Java 编译缓存目录(仅本地需要,镜像中用 JAR 包即可)
target/

# 排除日志文件(镜像运行时产生的日志应挂载到主机,而非打包进镜像)
*.log

# 排除环境变量配置文件(本地开发用,镜像中可通过 ENV 指令设置或挂载)
.env

✅ 步骤 4:构建镜像(执行 docker build 命令)

在包含 Dockerfile 的目录下执行 docker build 命令,Docker 引擎会根据 Dockerfile 和构建上下文(指定目录下的文件)构建镜像。

命令示例(带详细注释)

bash

复制代码
# 格式:docker build -t 镜像名称:标签 构建上下文目录
docker build -t my-java-app:1.0 .
  • -t my-java-app:1.0:指定镜像的名称(my-java-app)和标签(1.0),标签用于区分镜像版本,格式为 name:tag(不指定标签默认是 latest
  • .:表示构建上下文为当前目录(. 是当前目录的简写),Docker 会将该目录下所有文件(排除 .dockerignore 中的内容)发送给 Docker 引擎,作为构建的 "原材料"

📌 关键注释:构建上下文非常重要!切勿在系统根目录(/)执行 build 命令 ,否则 Docker 会尝试发送整个根目录的文件,导致构建缓慢甚至失败。始终在应用的独立目录(如 my-app/)下执行。

构建过程说明(帮助理解)
  1. Docker 逐行读取 Dockerfile 中的指令
  2. 每条指令执行后会生成一个 "镜像层"(layer),层是可复用的
  3. 如果某条指令对应的文件 / 命令没有变化,Docker 会直接使用缓存的层,无需重新执行,大幅加速构建

✅ 步骤 5:验证镜像是否构建成功

构建完成后,通过 docker images 命令查看本地镜像列表,确认目标镜像存在。

命令示例与输出解释

bash

复制代码
# 查看本地所有镜像(可加 -a 查看所有,包括中间镜像)
docker images

输出示例(带注释):

plaintext

复制代码
REPOSITORY       TAG    IMAGE ID       CREATED         SIZE
my-java-app      1.0    a1b2c3d4e5f6   2 minutes ago   256MB
# 仓库名(镜像名)  标签  镜像唯一ID    创建时间        镜像体积

📌 注释:如果能看到上述输出,说明镜像构建成功。如果未找到目标镜像,检查 Dockerfile 是否有语法错误,或构建命令是否正确。

✅ 步骤 6:运行容器测试镜像(验证可用性)

镜像构建成功后,需要启动一个容器来测试应用是否能正常运行。

命令示例(带详细注释)

bash

复制代码
# 启动容器并测试镜像
docker run -d --name test-app -p 8080:8080 my-java-app:1.0
  • -d:让容器在后台运行(守护进程模式),避免占用终端
  • --name test-app:指定容器名称为 test-app,方便后续管理(如查看日志、停止容器)
  • -p 8080:8080:将主机的 8080 端口映射到容器的 8080 端口(格式:主机端口:容器端口),外部可通过主机端口访问应用
  • my-java-app:1.0:指定要运行的镜像名称和标签
验证应用是否正常工作(两种常用方式)
  1. 访问应用接口(如 Spring Boot 的健康检查接口):

bash

复制代码
curl http://localhost:8080/health
# 若返回 200 OK 或健康状态 JSON,说明应用正常
  1. 查看容器日志(排查问题常用):

bash

复制代码
docker logs test-app
# 若日志中无报错,且有应用启动成功的提示(如 "Started Application in 2.3 seconds"),说明正常

✅ 步骤 7(可选):推送镜像到仓库(便于分发)

如果需要在其他机器(如服务器、同事的电脑)上使用该镜像,可以将其推送到公共仓库(如 Docker Hub)或私有仓库(如公司内部仓库)。

推送到 Docker Hub 的步骤(带注释)
  1. 登录 Docker Hub(需先注册 Docker Hub 账号):

bash

复制代码
docker login
# 输入用户名和密码,登录成功后会提示 "Login Succeeded"
  1. 重命名镜像(必须符合 Docker Hub 的命名规范:用户名/镜像名:标签):

bash

复制代码
docker tag my-java-app:1.0 your-dockerhub-username/my-java-app:1.0
# 示例:docker tag my-java-app:1.0 zhangsan/my-java-app:1.0
  1. 推送镜像到 Docker Hub:

bash

复制代码
docker push your-dockerhub-username/my-java-app:1.0

📌 注释:推送完成后,其他人可通过 docker pull your-dockerhub-username/my-java-app:1.0 命令拉取该镜像,直接运行,无需重新构建。

三、完整实战:Node.js 应用镜像制作(从零到一)

下面通过一个简单的 Node.js 应用,完整演示镜像制作的全流程,帮助巩固前面的知识点。

1. 准备 Node.js 应用

项目结构(独立目录 my-node-app/

plaintext

复制代码
my-node-app/
├── app.js        # 应用入口文件
├── package.json  # 依赖清单
└── Dockerfile    # 构建规则文件
核心文件内容
  • app.js(简单的 HTTP 服务):

javascript

复制代码
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Node.js 应用容器化成功!');
});

app.listen(port, () => {
  console.log(`应用运行在 http://localhost:${port}`);
});
  • package.json(依赖清单):

json

复制代码
{
  "name": "my-node-app",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.18.2"
  }
}

2. 编写 Dockerfile(带注释)

dockerfile

复制代码
# 选择 Node.js 18 的 alpine 版本(极小体积,仅 50MB 左右,适合生产环境)
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 先复制 package.json 和 package-lock.json(或 yarn.lock)
# 原因:依赖文件变更频率低,可利用 Docker 缓存,避免每次修改代码都重新安装依赖
COPY package*.json ./

# 安装生产环境依赖(--only=production 排除开发依赖,减小镜像体积)
RUN npm install --only=production

# 复制应用代码到容器(代码变更频率高,放在后面,不影响依赖安装的缓存)
COPY . .

# 声明容器端口
EXPOSE 3000

# 启动应用(CMD 可被覆盖,适合简单应用)
CMD ["node", "app.js"]

3. 构建并运行镜像

构建镜像

bash

复制代码
cd my-node-app/  # 进入应用目录
docker build -t my-node-app:latest .  # 构建镜像,标签为 latest
运行容器并测试

bash

复制代码
# 启动容器,映射主机 3000 端口到容器 3000 端口
docker run -p 3000:3000 my-node-app:latest

测试:打开浏览器访问 http://localhost:3000,若看到 "Node.js 应用容器化成功!",说明镜像制作和运行都正常。

四、常见问题与优化技巧(避坑指南)

❓ 问题 1:为什么构建的镜像体积很大?

原因分析(带注释)
  1. 基础镜像选择不当:使用了完整版镜像(如 openjdk:17 而非 openjdk:17-jre-slim),包含大量不必要的工具和依赖
  2. 复制了无关文件:未使用 .dockerignore,将 node_modules/target/ 等目录打包进镜像
  3. 未清理临时文件:构建时安装软件后,未清理 apt/yum 缓存(如 /var/lib/apt/lists/*
优化建议(实战示例)

dockerfile

复制代码
# 以 Debian/Ubuntu 基础镜像为例,安装软件后清理缓存
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*  # 清理 apt 缓存,减少镜像体积

❓ 问题 2:如何进一步减小镜像体积?(针对编译型语言)

解决方案:多阶段构建(Multi-stage Build)

核心思路:将构建过程分为 "构建阶段" 和 "运行阶段",构建阶段使用包含编译工具的镜像(如 Maven、GCC),运行阶段使用轻量的基础镜像(如 JRE、Alpine),最终镜像仅包含运行所需的文件,大幅减小体积。

示例:Java 应用多阶段构建 Dockerfile(带注释)

dockerfile

复制代码
# 第一阶段:构建阶段(使用 Maven 镜像编译代码,生成 JAR 包)
FROM maven:3.8-openjdk-17 AS builder  # 命名为 builder,方便后续引用
WORKDIR /app
COPY pom.xml .  # 先复制 pom.xml,利用缓存加速依赖下载
COPY src ./src  # 复制源代码
RUN mvn package -DskipTests  # 编译打包,跳过测试(加快构建)

# 第二阶段:运行阶段(使用轻量的 JRE 镜像,仅复制 JAR 包)
FROM openjdk:17-jre-slim  # JRE 比 JDK 小很多,适合运行环境
WORKDIR /app
# 从构建阶段(builder)复制编译好的 JAR 包到当前镜像
COPY --from=builder /app/target/app.jar ./app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

📌 注释:多阶段构建后,最终镜像体积可从 500MB+ 减小到 200MB 左右,且不包含源代码和编译工具,更安全、更高效。

五、总结:Docker 镜像制作的标准流程

步骤 核心操作 关键注意点
1️⃣ 准备应用代码和依赖 统一存放目录,明确依赖项
2️⃣ 编写 Dockerfile 遵循最佳实践,按变更频率排序指令
3️⃣ 创建 .dockerignore 排除无关文件,减小镜像体积
4️⃣ 执行 docker build -t name:tag . 避免在根目录执行,利用缓存加速
5️⃣ docker run 测试镜像 映射端口,查看日志验证可用性
6️⃣(可选) 推送到镜像仓库 遵循仓库命名规范,先登录再推送

通过以上流程,你可以快速、高效地制作出高质量的 Docker 镜像,实现应用的跨环境无缝部署。

相关推荐
要加油哦~14 分钟前
nrm | npm 的镜像管理工具
前端·npm·node.js·nrm
tuokuac20 分钟前
SQL中AND和逗号,的区别
java·数据库·sql
zl97989921 分钟前
RabbitMQ-Hello World
java·分布式·rabbitmq
程序员三明治27 分钟前
【Spring进阶】Spring IOC实现原理是什么?容器创建和对象创建的时机是什么?
java·后端·spring·ioc·bean生命周期
程序员西西1 小时前
SpringCloudGateway入门实战
java·spring boot·计算机·程序员·编程
c***93772 小时前
SpringBoot实现异步调用的方法
java·spring boot·spring
e***28292 小时前
Windows 上彻底卸载 Node.js
windows·node.js
青衫码上行2 小时前
【Java Web学习 | 第15篇】jQuery(万字长文警告)
java·开发语言·前端·学习·jquery
凯子坚持 c3 小时前
Docker 容器实战:从镜像管理到私有仓库构建深度解析
java·docker·eureka