面向开发者的 Docker:容器、镜像和 Compose

· 12分钟阅读

目录

理解 Docker:为什么它很重要

Docker 从根本上改变了开发者构建、交付和运行应用程序的方式。在 Docker 出现之前,设置开发环境是一场依赖冲突、版本不匹配和臭名昭著的"在我的机器上可以运行"问题的噩梦。

Docker 通过将应用程序及其运行所需的一切——代码、运行时、系统工具、库和设置——打包到一个称为容器的标准化单元中来解决这个问题。这个容器在你的笔记本电脑、同事的机器和生产服务器上运行完全相同。

好处是立竿见影且实实在在的:

对于开发者来说,Docker 意味着你可以用一条命令启动完整的应用程序栈——Web 服务器、数据库、缓存、消息队列。不再需要花费数小时安装 PostgreSQL 或调试 Redis 配置问题。

核心概念解释

在深入实际使用之前,理解 Docker 的核心概念至关重要。这些构建块共同创建了 Docker 生态系统。

概念 它是什么 类比 关键特征
镜像 包含应用程序和依赖项的只读模板 面向对象编程中的类定义 不可变、分层、可共享
容器 镜像的运行实例 对象(类的实例) 隔离、临时、无状态
Dockerfile 构建镜像的指令 配方或蓝图 文本文件、版本控制
容器外的持久存储 外部硬盘 在容器删除后仍然存在
网络 容器之间的通信 局域网(LAN) 隔离、可配置、安全
注册表 镜像存储(Docker Hub) 容器的 npm/PyPI 公共或私有、版本化

镜像与容器:关键区别

这是许多初学者感到困惑的地方。镜像是静态快照——可以把它想象成一个冻结的模板。容器是运行该镜像时得到的东西——它是一个活动的、运行中的进程。

你可以从单个镜像创建无限个容器,就像你可以从一个类创建多个对象一样。每个容器都与其他容器隔离,即使它们都基于同一个镜像。

专业提示: 镜像是分层构建的。Dockerfile 中的每条指令都会创建一个新层。Docker 会缓存这些层,因此重建镜像只会重建已更改的层。这使得构建速度非常快。

卷:解决持久化问题

容器在设计上是临时的——当你删除一个容器时,其中的所有内容都会消失。这对于无状态应用程序来说很好,但对于数据库或任何需要保留的数据来说就有问题了。

卷通过将数据存储在容器文件系统之外来解决这个问题。即使容器被删除和重新创建,数据也会持久存在。有三种类型的挂载:

Dockerfile 剖析和结构

Dockerfile 是一个包含构建 Docker 镜像指令的文本文档。每条指令在镜像中创建一个层,Docker 会缓存这些层以提高效率。

以下是最常见的 Dockerfile 指令的详细说明:

指令 用途 示例 最佳实践
FROM 构建的基础镜像 FROM node:22-alpine 使用特定版本,优先选择 alpine
WORKDIR 设置工作目录 WORKDIR /app 使用绝对路径
COPY 从主机复制文件到镜像 COPY package.json . 先复制依赖项以利用缓存
RUN 在构建期间执行命令 RUN npm install 用 && 链接命令以减少层数
EXPOSE 记录应用程序监听的端口 EXPOSE 3000 仅用于文档,不发布端口
ENV 设置环境变量 ENV NODE_ENV=production 用于配置
USER 为后续命令设置用户 USER node 生产环境中永远不要以 root 身份运行
CMD 容器启动时的默认命令 CMD ["node", "server.js"] 使用 JSON 数组格式

理解层缓存

Docker 从上到下逐层构建镜像。每条指令创建一个新层。如果某一层没有改变,Docker 会重用缓存的版本而不是重建它。

这就是为什么你应该将 Dockerfile 结构化,将很少更改的指令放在顶部,将经常更改的指令放在底部。例如,你的依赖项更改频率低于源代码,因此在复制源文件之前先安装依赖项。

# 不好:先复制所有内容,然后安装
COPY . .
RUN npm install

# 好:先安装依赖项,利用缓存
COPY package*.json ./
RUN npm install
COPY . .

Dockerfile 最佳实践

编写高效的 Dockerfile 是一门艺术。这是一个演示多个最佳实践的生产就绪示例:

# 多阶段构建:构建器阶段
FROM node:22-alpine AS builder
WORKDIR /app

# 先复制依赖文件以获得更好的缓存
COPY package*.json ./
RUN npm ci --production

# 复制源代码并构建
COPY . .
RUN npm run build

# 多阶段构建:生产阶段
FROM node:22-alpine
WORKDIR /app

# 仅从构建器复制所需内容
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

# 安全:以非 root 用户身份运行
USER node

# 记录端口(实际上不发布它)
EXPOSE 3000

# 用于容器编排的健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# 启动应用程序
CMD ["node", "dist/server.js"]

多阶段构建:显著减小大小

多阶段构建允许你在一个 Dockerfile 中使用多个 FROM 语句。每个 FROM 开始一个新阶段,你可以从之前的阶段复制构件。

神奇之处在于只有最后一个阶段成为你的镜像。构建工具、编译器和中间文件留在早期阶段,永远不会进入生产环境。这可以将镜像大小减少 10 倍或更多。

快速提示: 使用 AS builder 命名你的阶段,这样你以后可以使用 COPY --from=builder 引用它们。这使你的 Dockerfile 更具可读性和可维护性。

Alpine 镜像:小而强大

Alpine Linux 是一个最小的 Linux 发行版,大小只有 5MB。相比之下,基于 Ubuntu 的镜像超过 900MB。对于大多数应用程序,Alpine 提供了你需要的一切。

权衡是 Alpine 使用 musl libc 而不是 glibc,这偶尔会导致预编译二进制文件的兼容性问题。对于 95% 的用例,Alpine 工作完美,并显著减少镜像大小、下载时间和攻击面。

.dockerignore 文件

就像 .gitignore 一样,.dockerignore 文件告诉 Docker 在构建镜像时要排除哪些文件。这加快了构建速度并减小了镜像大小。

# .dockerignore 示例
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
dist
coverage
.vscode
.idea
*.log
.DS_Store

安全最佳实践

安全性应该从一开始就融入你的 Dockerfile:

需要帮助生成优化的 Dockerfile?试试我们的 Dockerfile 生成器工具。

基本 Docker 命令

掌握这些命令将涵盖你 90% 的日常 Docker 使用。每个命令都包含实际示例和常用标志。

构建和运行

# 从当前目录的 Dockerfile 构建镜像
docker build -t myapp:1.0 .

# 使用构建参数构建
docker build --build-arg NODE_ENV=production -t myapp:1.0 .

# 不使用缓存构建(强制重建)
docker build --no-cache -t myapp:1.0 .

# 以分离模式运行容器
docker run -d -p 3000:3000 --name myapp myapp:1.0

# 使用环境变量运行
docker run -d -p 3000:3000 -e NODE_ENV=production --name myapp myapp:1.0

# 使用卷挂载运行
docker run -d -p 3000:3000 -v $(pwd)/data:/app/data --name myapp myapp:1.0

# 以交互方式运行并访问 shell
docker run -it --rm myapp:1.0 sh

管理容器

# 列出运行中的容器
docker ps

# 列出所有容器(包括已停止的)
docker ps -a

# 查看容器日志
docker logs myapp

# 实时跟踪日志
docker logs -f myapp

# 查看最后 100 行日志
docker logs --tail 100 myapp

# 在运行中的容器中执行命令
docker exec -it myapp sh

# 运行一次性命令
docker exec myapp npm test

# 优雅地停止容器
docker stop myapp

# 立即终止容器
docker kill myapp

# 删除已停止的容器
docker rm myapp

# 一条命令停止并删除
docker stop myapp && docker rm myapp

# 删除所有已停止的容器
docker container prune

使用镜像

# 列出所有镜像
docker images

# 使用自定义格式列出镜像
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# 删除镜像
docker rmi myapp:1.0

# 删除所有未使用的镜像
docker image prune -a

# 标记镜像
docker tag myapp:1.0 myapp:latest

# 推送到注册表
docker push myregistry.com/myapp:1.0

# 从注册表拉取
docker pull myregistry.com/myapp:1.0

# 将镜像保存到 tar 文件
docker save myapp:1.0 > myapp.tar

# 从 tar 文件加载镜像
docker load < myapp.tar

系统管理

# 查看磁盘使用情况
docker system df

# 删除所有未使用的数据(容器、网络、镜像、缓存)
docker system prune -a

# 查看实时容器统计信息
docker stats

# 检查容器详细信息(JSON 输出)
docker inspect myapp

# 查看容器进程
docker top myapp

专业提示: 在运行容器进行测试时添加 --rm 标志。这会在容器停止时自动删除它,保持系统清洁:docker run --rm -it myapp:1.0 sh

用于多容器应用的 Docker Compose

Docker Compose 是一个用于定义和运行多容器应用程序的工具。你无需运行多个 docker run 命令,而是在单个 YAML 文件中定义所有内容。

这对于通常由多个服务组成的现代应用程序至关重要:Web 服务器、数据库、缓存、消息队列等。

完整的 Docker Compose 示例

# docker-compose.yml
version: '3.8'

services:
  # W