面向开发者的 Docker:容器、镜像和 Compose
· 12分钟阅读
目录
理解 Docker:为什么它很重要
Docker 从根本上改变了开发者构建、交付和运行应用程序的方式。在 Docker 出现之前,设置开发环境是一场依赖冲突、版本不匹配和臭名昭著的"在我的机器上可以运行"问题的噩梦。
Docker 通过将应用程序及其运行所需的一切——代码、运行时、系统工具、库和设置——打包到一个称为容器的标准化单元中来解决这个问题。这个容器在你的笔记本电脑、同事的机器和生产服务器上运行完全相同。
好处是立竿见影且实实在在的:
- 一致性: 开发、预发布和生产环境完全相同
- 隔离性: 每个应用程序在自己的容器中运行,互不冲突
- 可移植性: 容器可以在任何安装了 Docker 的地方运行
- 高效性: 容器共享主机操作系统内核,使用的资源远少于虚拟机
- 速度: 容器在几秒钟内启动,而不是几分钟
对于开发者来说,Docker 意味着你可以用一条命令启动完整的应用程序栈——Web 服务器、数据库、缓存、消息队列。不再需要花费数小时安装 PostgreSQL 或调试 Redis 配置问题。
核心概念解释
在深入实际使用之前,理解 Docker 的核心概念至关重要。这些构建块共同创建了 Docker 生态系统。
| 概念 | 它是什么 | 类比 | 关键特征 |
|---|---|---|---|
| 镜像 | 包含应用程序和依赖项的只读模板 | 面向对象编程中的类定义 | 不可变、分层、可共享 |
| 容器 | 镜像的运行实例 | 对象(类的实例) | 隔离、临时、无状态 |
| Dockerfile | 构建镜像的指令 | 配方或蓝图 | 文本文件、版本控制 |
| 卷 | 容器外的持久存储 | 外部硬盘 | 在容器删除后仍然存在 |
| 网络 | 容器之间的通信 | 局域网(LAN) | 隔离、可配置、安全 |
| 注册表 | 镜像存储(Docker Hub) | 容器的 npm/PyPI | 公共或私有、版本化 |
镜像与容器:关键区别
这是许多初学者感到困惑的地方。镜像是静态快照——可以把它想象成一个冻结的模板。容器是运行该镜像时得到的东西——它是一个活动的、运行中的进程。
你可以从单个镜像创建无限个容器,就像你可以从一个类创建多个对象一样。每个容器都与其他容器隔离,即使它们都基于同一个镜像。
专业提示: 镜像是分层构建的。Dockerfile 中的每条指令都会创建一个新层。Docker 会缓存这些层,因此重建镜像只会重建已更改的层。这使得构建速度非常快。
卷:解决持久化问题
容器在设计上是临时的——当你删除一个容器时,其中的所有内容都会消失。这对于无状态应用程序来说很好,但对于数据库或任何需要保留的数据来说就有问题了。
卷通过将数据存储在容器文件系统之外来解决这个问题。即使容器被删除和重新创建,数据也会持久存在。有三种类型的挂载:
- 卷: 由 Docker 管理,存储在 Docker 的存储区域(推荐)
- 绑定挂载: 将主机目录直接映射到容器中(对开发有用)
- tmpfs 挂载: 仅存储在主机内存中,永远不会写入磁盘(用于敏感数据)
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:
- 永远不要以 root 身份运行: 使用
USER node或创建专用用户 - 固定特定版本: 使用
node:22.1.0-alpine而不是node:latest - 扫描漏洞: 使用
docker scan myimage检查已知的 CVE - 最小化安装的包: 软件越少意味着漏洞越少
- 使用官方基础镜像: 它们得到维护并定期更新
- 不要在镜像中存储机密: 使用环境变量或机密管理工具
需要帮助生成优化的 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