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 镜像,实现应用的跨环境无缝部署。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值