第2章:Docker 镜像 - 应用的标准化封装

第2章:Docker 镜像 - 应用的标准化封装

在上一章,我们像逛超市一样,从 Docker Hub(仓库)上拿了一个 hello-world(镜像),并用它运行了一个容器。我们真正的目标是为自己的应用程序穿上 Docker 这件"标准制服"。

要做到这一点,我们必须学会制作自己的镜像。本章就是你的"裁缝入门指南",教你如何使用 Dockerfile 这张"图纸",为你的应用量体裁衣,打造一个完美的 Docker 镜像。

2.1 深入理解镜像:分层的艺术

在动手之前,我们先来揭秘 Docker 镜像一个极其重要的特性------分层 (Layered) 结构

想象一下你在用 Photoshop 或 Figma 画画,你的作品是由多个图层叠加而成的:背景层、人物层、文字层... 每一层只包含一部分内容,但叠加在一起就构成了完整的图像。修改时,你通常也只会修改某一个图层,而不是整个画布。

Docker 镜像也是如此!

一个 Docker 镜像并非一个巨大的、单一的文件,而是由一堆只读的层 (read-only layers) 堆叠而成。

  • 基础镜像 (Base Image) :任何镜像都始于一个基础层,比如 ubuntu:20.04node:16。这为你提供了一个基本的操作系统环境。
  • 指令层 (Instruction Layers) :你在此之上所做的每一个修改------比如安装一个软件、拷贝一段代码、设置一个环境变量------都会创建一个新的图层

当你基于这个镜像启动一个容器时,Docker 会在所有只读图层的最顶上,再添加一个可写的容器层 (writable container layer)。你在容器内做的所有修改(如创建新文件、修改已有文件),都发生在这个顶层,而不会影响到底下任何一个只读的镜像层。

这种分层结构带来了巨大的好处:

  • 高效存储 :如果你的多个镜像都基于同一个 ubuntu 基础镜像,那么在你的电脑上,这个巨大的基础层只需存储一份
  • 快速分发:当你更新应用代码时,你只需要重新构建并推送那一个包含新代码的图层,而不是整个镜像。这使得镜像的上传和下载速度大大加快。
  • 版本控制:每一层都记录了一次变更,这使得镜像的版本管理和回滚变得非常容易。

2.2 Dockerfile:给应用安个"家"

我们已经知道了镜像是分层的,那么,我们该如何定义这些层,告诉 Docker 如何一步步地构建出我们想要的镜像呢?

答案就是 Dockerfile

Dockerfile 是一个纯文本文件,里面包含了一系列指令 (Instructions) 。它就像一份"装修施工单",你按照顺序在里面写下每一条指令,docker 就会严格按照这个顺序,一步步地执行,最终"装修"出一个完整的镜像。

一个最基本的 Dockerfile 通常包含以下几个核心指令:

指令 作用 解释
FROM FROM <image>:<tag> 指定基础镜像。这是你所有装修工作的起点,必须是第一条指令。
WORKDIR WORKDIR /path/to/workdir 设置工作目录 。后续的指令(如 RUN, COPY)都会在这个目录下执行。
COPY COPY <src> <dest> 拷贝文件。将主机上的文件或目录,拷贝到镜像的指定路径中。
RUN RUN <command> 执行命令 。在镜像构建过程中执行任意 shell 命令,如 RUN npm install
EXPOSE EXPOSE <port> 声明端口。告诉外界这个容器内的应用会监听哪个端口,但不起实际映射作用。
CMD CMD ["executable", "param1", "param2"] 容器启动命令。指定当容器启动时,默认要执行的命令。

别被这些指令吓到,它们都非常直观。接下来,我们将通过一个实战项目把它们串起来。

2.3 [实战] 为一个简单的 Node.js Web 应用编写 Dockerfile

是时候动手了!我们将为我们的小册子主线项目------一个极简的 Node.js Express Web 应用------编写它的第一个 Dockerfile。

1. 准备应用代码

首先,在你的工作目录下创建一个名为 my-app 的文件夹。在 my-app 文件夹中,创建两个文件:

  • app.js (我们的应用主文件)
  • package.json (项目描述与依赖文件)

package.json

json 复制代码
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "A simple Node.js app for our little book.",
  "main": "src/app.js",
  "scripts": {
    "start": "node src/app.js"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}

app.js

javascript 复制代码
const express = require('express');

// Constants
const PORT = 8080;
const HOST = '0.0.0.0';

// App
const app = express();
app.get('/', (req, res) => {
  res.send('Hello, Docker World! This is our first image.');
});

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

这个应用非常简单:使用 Express 框架启动一个 Web 服务器,监听在 8080 端口,当有访问根路径 / 的请求时,返回一句问候。

2. 编写 Dockerfile

my-app 文件夹的根目录下,创建一个名为 Dockerfile (没有后缀名!) 的文件,并写入以下内容:

dockerfile 复制代码
# 1. 选择一个官方的 Node.js 运行时作为我们的基础镜像
# 我们选择了 16 这个长期支持版本
FROM node:16

# 2. 在镜像中创建一个目录来存放我们的应用代码
# 并将其设置为工作目录
WORKDIR /usr/src/app

# 3. 拷贝 package.json 和 package-lock.json (如果存在)
# 我们将这两个文件分开拷贝,是为了更好地利用 Docker 的缓存机制
COPY package*.json ./

# 4. 安装我们的应用依赖
# 只有当 package.json 文件发生变化时,这一层缓存才会失效
RUN npm install

# 5. 将我们项目目录下的所有文件,都拷贝到工作目录中
COPY . .

# 6. 告诉 Docker,我们的应用在容器内会监听 8080 端口
EXPOSE 8080

# 7. 定义容器启动时要执行的命令
CMD [ "node", "app.js" ]

让我们逐行拆解一下这份"装修施工单":

  • FROM node:16: 找一个已经装好了 Node.js 16 的"毛坯房"。
  • WORKDIR /usr/src/app: 在房子里开辟一个 /usr/src/app 的房间作为我们的主战场。
  • COPY package*.json ./: 先把"依赖清单" (package.json) 搬进去。
  • RUN npm install: 按照清单,把所有需要的"家具"(项目依赖)都安装好。
  • COPY . .: 把我们写的"电器"(应用代码 app.js)搬进去。
  • EXPOSE 8080: 在墙上贴个标签,写上"这个房间的电话分机号是 8080"。
  • CMD [ "node", "app.js" ]: 写一张"开机指南",告诉别人启动容器后,要运行 node app.js 这个程序。

2.4 docker build 命令详解

有了"图纸" (Dockerfile) 和"原材料" (应用代码),我们就可以请 docker 这个"施工队"来建造镜像了。

打开你的命令行,确保你当前位于 my-app 文件夹内(也就是 Dockerfile 所在的目录),然后执行以下命令:

bash 复制代码
docker build -t my-app:1.0 .
  • docker build: 这是构建镜像的命令。
  • -t my-app:1.0: -t 参数用来给我们的镜像打上一个标签 (Tag) ,格式是 name:tag。这就像给我们的镜像起了个名字叫 my-app,版本是 1.0。这是一个非常好的习惯!
  • .: 最后一个 . 表示构建上下文 (Build Context) 。它告诉 Docker:"嘿,你需要的 Dockerfile 和所有源代码,都在当前这个目录下。"

执行后,你会看到 Docker 严格按照 Dockerfile 里的指令,一步步地执行,并输出日志。当看到 Successfully tagged my-app:1.0 时,就代表你的第一个专属镜像构建成功了!

你可以运行 docker images 来查看你本地的所有镜像,应该能看到我们刚刚创建的 my-app:1.0

2.5 将你的镜像推送到 Docker Hub

现在这个镜像只存在于你自己的电脑上。如果你想分享给同事,或者部署到服务器上,就需要把它推送到一个仓库 (Repository)。我们这里使用官方的 Docker Hub。

  1. 注册并登录

    • Docker Hub 网站注册一个账号。
    • 在你的命令行中,使用 docker login 命令登录。它会提示你输入用户名和密码。
  2. 给镜像打上新标签 : 为了能推送到你自己的账号下,你需要给镜像重新打一个符合 username/repository:tag 格式的标签。

    bash 复制代码
    # 将 my-app:1.0 复制一份,并命名为 <你的用户名>/my-app:1.0
    docker tag my-app:1.0 <your-dockerhub-username>/my-app:1.0

    请务必将 <your-dockerhub-username> 替换成你自己的 Docker Hub 用户名。

  3. 推送镜像

    bash 复制代码
    docker push <your-dockerhub-username>/my-app:1.0

    等待推送完成。现在,任何人(包括你自己未来的服务器)都可以通过 docker pull <your-dockerhub-username>/my-app:1.0 来拉取这个镜像了!

2.6 本章小结 & 避坑指南

干得漂亮!你已经从一个镜像的消费者,成功进阶为一名创造者。

  • 本章回顾

    • 我们理解了镜像是分层的,这带来了高效和复用。
    • 我们学会了使用 Dockerfile 这个"图纸"来定义镜像的构建过程。
    • 我们掌握了 FROM, WORKDIR, COPY, RUN, EXPOSE, CMD 等核心指令。
    • 我们亲手为一个 Node.js 应用构建了镜像,并用 docker build 命令完成了"施工"。
    • 我们学会了如何将自己的成果 push 到 Docker Hub 与世界分享。
  • 避坑指南:减小镜像体积的几个技巧

    • 选择更小的基础镜像 :不要动不动就用 ubuntu。对于 Node.js 应用,node:16-alpine 这样的 alpine 版本会比默认的 node:16 小得多。Alpine Linux 是一个极简的 Linux 发行版,非常适合做容器基础镜像。

    • 利用好 .dockerignore 文件 :在你的项目根目录下创建一个 .dockerignore 文件(语法和 .gitignore 一样),把不需要拷贝到镜像里的文件和目录(如 node_modules, .git, *.log)都写进去。这能有效防止不必要的文件被打包进镜像,既减小了体积,也避免了敏感信息泄露。

    • 合并 RUN 指令 :Dockerfile 中的每一个 RUN 指令都会创建一个新的图层。你可以使用 && 将多个命令合并到一条 RUN 指令中,以减少图层数量。例如:

      dockerfile 复制代码
      # 不推荐
      RUN apt-get update
      RUN apt-get install -y curl
      
      # 推荐
      RUN apt-get update && apt-get install -y curl

在下一章,我们将学习如何运行我们自己创建的这个镜像,并深入探索 docker run 命令的更多强大功能。

相关推荐
狮子也疯狂2 小时前
【保姆级】| 基于Docker的dify部署流程
运维·docker·容器
KubeSphere 云原生2 小时前
云原生周刊:MetalBear 融资、Chaos Mesh 漏洞、Dapr 1.16 与 AI 平台新趋势
人工智能·云原生
东方佑3 小时前
docker 部署gitlib
运维·docker·容器
ZNineSun3 小时前
第一章:Go语言的起源-云原生时代的C位语言
云原生·golang
£漫步 云端彡3 小时前
docker常用命令
java·docker·eureka
key_Go3 小时前
03.镜像
运维·服务器·网络·docker
Anthony_2313 小时前
Dockerfile构建镜像以及网络
linux·运维·服务器·网络·docker
会飞的小蛮猪5 小时前
Jenkins运维之路(初次调试共享库)
运维·经验分享·docker·容器·jenkins
Java.慈祥5 小时前
Docker + IDEA 一键部署!
docker·容器·intellij-idea