Docker 改变了我们构建、交付和运行软件的方式。Docker 不再是"在我的机器上能用",而是保证你的应用在任何地方都以相同的方式运行 - 在你的笔记本电脑上、在同事的机器上、在 CI/CD 中以及在生产环境中。在本指南中,我们将从零开始,一直到部署一个真实的应用。
什么是 Docker?
Docker 是一个将你的应用及其所有依赖打包成标准化单元(称为容器)的平台。容器是一个隔离的、轻量级的进程,它共享宿主操作系统的内核,但拥有自己的文件系统、网络和进程空间。
容器 vs 虚拟机
| 方面 | 容器 | 虚拟机 |
|---|---|---|
| 启动 | 秒级 | 分钟级 |
| 大小 | MB 级别 | GB 级别 |
| 操作系统 | 共享宿主内核 | 完整的客户操作系统 |
| 隔离 | 进程级别 | 硬件级别 |
| 性能 | 接近原生 | 有 hypervisor 开销 |
| 密度 | 每台主机数百个 | 每台主机数十个 |
安装 Docker
# macOS brew install --cask docker # Ubuntu/Debian curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Verify installation docker --version docker run hello-world
核心概念
镜像
镜像是一个只读模板,包含创建容器的指令。可以把它看作应用及其环境的快照。
# Pull an image from Docker Hub docker pull node:20-alpine # List local images docker images # Remove an image docker rmi node:20-alpine
容器
容器是镜像的运行实例。你可以创建、启动、停止和删除容器。
# Run a container docker run -d --name my-app -p 3000:3000 node:20-alpine # List running containers docker ps # List all containers (including stopped) docker ps -a # Stop a container docker stop my-app # Remove a container docker rm my-app # View logs docker logs my-app # Execute a command inside a running container docker exec -it my-app sh
编写 Dockerfile
Dockerfile 是一个文本文件,包含构建镜像的指令。每条指令创建一个层。
Node.js 应用的基本 Dockerfile
# Use an official Node.js runtime as base image FROM node:20-alpine # Set working directory WORKDIR /app # Copy package files first (better caching) COPY package.json package-lock.json ./ # Install dependencies RUN npm ci --only=production # Copy application code COPY . . # Expose the port the app runs on EXPOSE 3000 # Command to run the application CMD ["node", "server.js"]
构建和运行
# Build the image docker build -t my-node-app . # Run the container docker run -d -p 3000:3000 my-node-app # Visit http://localhost:3000
多阶段构建
多阶段构建通过将构建环境与运行时环境分离来保持生产镜像的精简。
# Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production FROM node:20-alpine AS runner WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"]
这将生成一个仅包含编译输出和生产依赖的镜像 - 没有源代码、没有开发依赖、没有构建工具。
Next.js 多阶段构建示例
FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 CMD ["node", "server.js"]
卷:持久化数据
默认情况下,容器内的数据在容器被删除时会丢失。卷解决了这个问题。
# Create a named volume docker volume create my-data # Run with a volume docker run -d -v my-data:/app/data my-app # Bind mount (map host directory to container) docker run -d -v $(pwd)/data:/app/data my-app # List volumes docker volume ls
网络
Docker 创建隔离的网络,让容器之间可以通信。
# Create a custom network docker network create my-network # Run containers on the same network docker run -d --name api --network my-network my-api docker run -d --name db --network my-network postgres:16 # Containers can reach each other by name # From "api" container: postgres://db:5432
Docker Compose
Docker Compose 让你用一个 YAML 文件定义和运行多容器应用。
docker-compose.yml
services: api: build: ./api ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db db: image: postgres:16-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
命令
# Start all services docker compose up -d # View logs docker compose logs -f # Stop all services docker compose down # Rebuild and restart docker compose up -d --build # Scale a service docker compose up -d --scale api=3
.dockerignore
和 .gitignore 类似,这个文件防止不必要的文件被复制到镜像中。
node_modules .git .env *.md .next dist coverage
生产环境最佳实践
1. 使用小型基础镜像
# Bad: 1GB+ FROM node:20 # Good: ~180MB FROM node:20-alpine
2. 不要以 Root 身份运行
FROM node:20-alpine RUN addgroup -S app && adduser -S app -G app USER app WORKDIR /home/app COPY . .
3. 使用特定的镜像标签
# Bad: can change unexpectedly FROM node:latest # Good: pinned version FROM node:20.11-alpine3.19
4. 利用构建缓存
将 Dockerfile 指令按变化频率从低到高排序:
FROM node:20-alpine WORKDIR /app # These change rarely - cached COPY package.json package-lock.json ./ RUN npm ci --only=production # This changes often - not cached COPY . .
5. 健康检查
HEALTHCHECK \ CMD wget -qO- http://localhost:3000/health || exit 1
6. 使用环境变量
ENV NODE_ENV=production ENV PORT=3000
常用 Docker 命令速查表
# Images docker build -t name:tag . # Build image docker images # List images docker rmi image_name # Remove image docker image prune # Remove unused images # Containers docker run -d -p 3000:3000 image # Run detached docker ps # List running docker stop container_name # Stop docker rm container_name # Remove docker logs -f container_name # Follow logs docker exec -it container sh # Shell into container # Compose docker compose up -d # Start services docker compose down # Stop services docker compose logs -f # Follow all logs docker compose ps # List services # Cleanup docker system prune -a # Remove everything unused
从 Docker 到 Kubernetes
Docker 管理单个容器。当你需要在多台服务器上编排数百个容器时,你需要 Kubernetes。Docker 和 Kubernetes 是互补的:
- Docker:构建和运行容器
- Kubernetes:大规模编排容器(调度、扩展、自愈)
如果你对下一步感兴趣,请查看我关于 Kubernetes 入门的文章。
总结
Docker 是现代开发者的基本技能。它消除了环境不一致性,简化了部署,并且是使用 Kubernetes 进行容器编排的基础。从简单的 Dockerfile 开始,转向 Docker Compose 处理多服务应用,并随着你的成长采用多阶段构建和安全最佳实践。
学习 Docker 的最好方法是将你正在进行的项目容器化。今天就开始吧。