Developer Tools

面向开发者的 Docker:容器、Compose 与真实工作流

一份关于 Docker 核心概念、Dockerfile 最佳实践以及使用 Docker Compose 运行多服务开发环境的实用指南。

9分钟阅读

蓝色灯光下的服务器机房

"在我机器上明明是好的"——这句话已经引发了无数次部署故障。Docker 通过将应用及其所有依赖打包进一个容器来解决这个问题——这是一个隔离的、可复现的环境,无论是在你的笔记本上还是在生产环境中,运行结果完全一致。

容器与虚拟机的对比

虚拟机 容器
隔离方式 完整操作系统 进程级别
启动时间 分钟级 秒级
体积 GB 级 MB 级
开销 高(需要 hypervisor) 接近零
适用场景 完整 OS 隔离 应用打包

容器共享宿主机内核,但隔离了文件系统、进程和网络。这正是它们如此轻量的原因。

Docker 核心概念

镜像(Image) — 容器的只读蓝图,可以将其理解为类的定义。

容器(Container) — 镜像的运行实例,可以将其理解为由类创建的对象。

仓库(Registry) — 镜像的存储和分发系统。Docker Hub 是默认的公共仓库;GitHub Container Registry 和 AWS ECR 也是常见选择。

数据卷(Volume) — 在容器重启后仍然保留的持久化存储。没有挂载数据卷时,写入容器内部的数据会在容器停止后丢失。

编写一份优质的 Dockerfile

# 使用具体版本号——生产环境中永远不要用 "latest"
FROM node:20-alpine

# 设置工作目录
WORKDIR /app

# 先复制依赖文件(利用层缓存)
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 构建应用
RUN npm run build

# 以非 root 用户运行以提高安全性
USER node

# 声明端口(不会主动发布)
EXPOSE 3000

# 使用 exec 格式以正确处理信号
CMD ["node", "server.js"]

层缓存:快速构建的关键

Dockerfile 中的每条指令都会创建一个层。Docker 会缓存这些层——如果某一层及其前置层没有发生变化,Docker 会直接复用缓存。

先复制依赖文件,再复制应用代码。 依赖的变更频率远低于代码本身。如果一次性复制所有文件,index.js 中哪怕只改动一行,也会导致依赖安装缓存失效,从而触发完整的 npm install

多阶段构建

使用多阶段构建来保持生产镜像的精简:

# 第一阶段:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 第二阶段:生产环境(不含开发依赖和源码)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]

最终镜像只包含构建产物——不含源代码、测试文件或开发依赖。

使用 Docker Compose 进行本地开发

Docker Compose 用于编排多容器应用。一个 docker-compose.yml 文件即可定义整个本地开发栈:

version: "3.9"

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - .:/app
      - /app/node_modules

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

使用我们的 Docker Compose Generator 快速生成项目脚手架——选择所需服务,几秒内即可获得一份生产就绪的 docker-compose.yml

Compose 常用命令

# 在后台启动所有服务
docker compose up -d

# 查看日志(跟踪模式)
docker compose logs -f app

# 在运行中的容器内执行命令
docker compose exec app sh

# 停止并移除容器(保留数据卷)
docker compose down

# 停止并移除容器及数据卷
docker compose down -v

# 修改 Dockerfile 后重新构建镜像
docker compose up -d --build

环境变量与密钥管理

永远不要在 Dockerfile 或 Compose 文件中硬编码凭据。请使用 .env 文件:

# .env(记得添加到 .gitignore!)
DATABASE_URL=postgresql://user:pass@db:5432/myapp
REDIS_URL=redis://redis:6379
JWT_SECRET=your-super-secret-key

Compose 会自动从项目目录加载 .env 文件。在 YAML 中使用 ${VARIABLE_NAME} 的方式引用变量。

使用我们的 Env Generator 快速生成带有合理默认值的 .env 文件,以及供团队使用的 .env.example 模板。

Docker 网络

Compose 会为你的服务栈自动创建一个默认网络。各服务可以通过服务名称作为主机名相互访问——这就是为什么应用使用 db 作为数据库主机名,而不是 localhost

app → 连接到 "db:5432"
app → 连接到 "redis:6379"

只有通过 ports: 显式映射的端口,才能从宿主机访问。

健康检查与启动顺序

depends_on 只等待容器启动,而不是等待其就绪。数据库容器可能在几秒内启动,但 Postgres 可能还需要额外几秒才能接受连接。请使用 healthcheck + condition: service_healthy 来确保服务真正就绪后再继续。

生产环境注意事项

  1. 使用具体的镜像标签 — 例如 node:20.11.1-alpine,而非 node:latest
  2. 扫描镜像漏洞docker scout cves myapp:latest
  3. 设置资源限制 — 防止单个容器耗尽其他容器的资源。
  4. 尽量使用只读文件系统 — 在 Compose 中设置 read_only: true
  5. 不要以 root 运行 — 添加 USER node 或对应的用户指令。
  6. 配置 Nginx 作为应用的反向代理 — 使用我们的 Nginx Config Generator 获取经过实战验证的配置。

快速参考

# 镜像操作
docker images                    # 列出本地镜像
docker pull nginx:alpine         # 下载镜像
docker rmi myimage               # 删除镜像

# 容器操作
docker ps                        # 查看运行中的容器
docker ps -a                     # 查看所有容器
docker stop <id>                 # 优雅停止容器
docker rm <id>                   # 删除已停止的容器
docker logs <id> -f              # 实时查看日志

# 清理操作
docker system prune              # 删除所有未使用的资源
docker volume prune              # 删除未使用的数据卷

Docker 彻底消除了一整类与环境相关的 bug,并让新开发者的上手过程变得极为轻松。掌握这里介绍的基础知识,你将为后续学习 Kubernetes、CI/CD 流水线和云端部署打下坚实的基础。