部署这件事,以前有多痛苦大家都懂。装环境、改配置、解决依赖冲突,一台新机器搞半天。Docker 出来之后,这些事情基本变成了一条命令的事。

这篇文章不是 Docker 入门教程(入门教程看我另一篇),而是聚焦在怎么用 Docker 把应用部署到生产环境。从最简单的单容器部署,到多服务编排,再到数据持久化、网络配置、日志监控,一步步来。

一、安装 Docker

Linux(Ubuntu/Debian)

# 官方一键安装脚本
curl -fsSL https://get.docker.com | sh

# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER

# 重新登录终端让组权限生效
newgrp docker

# 验证安装
docker --version
docker compose version
docker run hello-world

CentOS/RHEL

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker

Windows / macOS

下载 Docker Desktop,安装即可。Windows 需要先开启 WSL2,安装过程会自动引导。

配置镜像加速

国内拉取 Docker Hub 镜像很慢,配置镜像加速器:

// /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

log-opts 限制了每个容器的日志大小(最大 10MB,保留 3 个文件),防止日志撑爆磁盘。这在生产环境非常重要。

sudo systemctl daemon-reload
sudo systemctl restart docker

二、单容器部署

最简单的部署方式,适合单个服务。

基本语法

docker run [选项] 镜像名[:标签]

常用选项

选项作用示例
-d后台运行docker run -d nginx
-p端口映射-p 8080:80
--name容器命名--name my-nginx
-v数据卷挂载-v /data:/var/data
-e环境变量-e MYSQL_ROOT_PASSWORD=123
--restart重启策略--restart unless-stopped
--memory内存限制--memory=512m
--cpusCPU 限制--cpus=1.5
--network指定网络--network mynet

实战:部署 Nginx

docker run -d \
  --name nginx \
  -p 80:80 \
  -p 443:443 \
  -v /var/www/html:/usr/share/nginx/html:ro \
  -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro \
  --restart unless-stopped \
  nginx:1.24-alpine
  • :ro 表示只读挂载,容器不能修改宿主机文件
  • nginx:1.24-alpine 用 Alpine 版本,镜像更小(约 20MB vs 约 140MB)
  • --restart unless-stopped 保证服务器重启后容器自动启动

实战:部署 MySQL

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=your_secure_password \
  -e MYSQL_DATABASE=myapp \
  -e MYSQL_USER=app_user \
  -e MYSQL_PASSWORD=app_password \
  -v mysql_data:/var/lib/mysql \
  --restart unless-stopped \
  --memory=1g \
  mysql:8.0
  • 用命名卷 mysql_data 持久化数据
  • 限制内存为 1GB,防止数据库吃光内存
  • 通过环境变量自动创建数据库和用户

实战:部署 Redis

docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis_data:/data \
  --restart unless-stopped \
  redis:7-alpine redis-server --appendonly yes --maxmemory 256mb
  • --appendonly yes 开启 AOF 持久化
  • --maxmemory 256mb 限制内存使用

实战:部署 PostgreSQL

docker run -d \
  --name postgres \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=your_secure_password \
  -e POSTGRES_DB=myapp \
  -e POSTGRES_USER=app_user \
  -v pg_data:/var/lib/postgresql/data \
  --restart unless-stopped \
  postgres:16-alpine

三、Docker Compose:多服务编排

实际项目通常需要多个服务配合(Web + 数据库 + 缓存 + 反向代理)。一个一个手动启动太麻烦,Docker Compose 用一个配置文件搞定。

基本结构

# docker-compose.yml
services:
  服务名:
    image: 镜像名        # 或 build: ./目录(从 Dockerfile 构建)
    ports:              # 端口映射
      - "宿主机:容器"
    volumes:            # 数据卷
      - 卷名:容器路径
    environment:        # 环境变量
      - KEY=VALUE
    depends_on:         # 依赖关系
      - 其他服务
    restart: unless-stopped

实战:Web 应用 + MySQL + Redis

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_PASSWORD=your_password
      - REDIS_HOST=cache
      - REDIS_PORT=6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: your_password
      MYSQL_DATABASE: myapp
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:1.24-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:

healthcheck:健康检查

depends_on 默认只等容器启动,不等服务就绪。数据库可能还没初始化完成,应用就去连接了。加 healthcheck 可以解决这个问题:

db:
  image: mysql:8.0
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 10s      # 每 10 秒检查一次
    timeout: 5s        # 超时时间
    retries: 5         # 重试次数
    start_period: 30s  # 启动等待时间

应用端用 depends_oncondition: service_healthy 等待数据库就绪。

常用命令

# 启动所有服务(后台运行)
docker compose up -d

# 启动并重新构建
docker compose up -d --build

# 查看状态
docker compose ps

# 查看日志
docker compose logs -f           # 所有服务
docker compose logs -f app       # 指定服务
docker compose logs --tail 100 app  # 最后 100 行

# 停止所有服务
docker compose down

# 停止并删除卷(⚠️ 数据会丢)
docker compose down -v

# 只启动某个服务
docker compose up -d db

# 进入容器
docker compose exec app bash

# 重启某个服务
docker compose restart app

# 查看资源占用
docker compose top

四、Dockerfile:构建自己的镜像

基本结构

# 基础镜像
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 先复制依赖文件(利用缓存层)
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制代码
COPY . .

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["python", "app.py"]

多阶段构建:减小镜像体积

# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .

# 运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]

最终镜像只有几 MB,而不是几百 MB。

.dockerignore

.gitignore 类似,排除不需要的文件:

.git
node_modules
__pycache__
*.pyc
.env
.env.local
*.md
tests/

常用指令

指令作用示例
FROM基础镜像FROM python:3.11-slim
WORKDIR工作目录WORKDIR /app
COPY复制文件COPY . .
RUN构建时执行RUN pip install -r requirements.txt
ENV环境变量ENV APP_ENV=production
EXPOSE声明端口EXPOSE 8000
CMD启动命令CMD ["python", "app.py"]
ENTRYPOINT入口点ENTRYPOINT ["python"]
ARG构建参数ARG VERSION=1.0
HEALTHCHECK健康检查HEALTHCHECK CMD curl -f http://localhost/

五、数据持久化

命名卷 vs 绑定挂载

类型存储位置适用场景
命名卷Docker 管理(/var/lib/docker/volumes/)数据库数据、应用数据
绑定挂载宿主机指定目录配置文件、代码、日志
# 命名卷
docker run -v mysql_data:/var/lib/mysql mysql:8.0

# 绑定挂载
docker run -v /home/user/website:/usr/share/nginx/html nginx

# 只读挂载
docker run -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro nginx

Volume 管理

docker volume ls                    # 列出所有卷
docker volume inspect mysql_data    # 查看详情
docker volume rm mysql_data         # 删除卷
docker volume prune                 # 清理未使用的卷

备份数据

# 备份 MySQL 数据
docker exec mysql sh -c 'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases' > backup.sql

# 备份卷数据
docker run --rm -v mysql_data:/data -v $(pwd):/backup alpine tar czf /backup/mysql_backup.tar.gz -C /data .

# 恢复卷数据
docker run --rm -v mysql_data:/data -v $(pwd):/backup alpine tar xzf /backup/mysql_backup.tar.gz -C /data

六、网络配置

默认网络

Docker 默认创建一个 bridge 网络,同一网络内的容器可以用容器名互相访问。

docker network ls                   # 查看网络
docker network inspect bridge       # 查看详情

自定义网络

# 创建自定义网络
docker network create mynet

# 在自定义网络中启动容器
docker run -d --name web --network mynet nginx
docker run -d --name db --network mynet mysql:8.0

# web 容器中可以直接用 "db" 作为主机名访问 MySQL
docker exec web ping db

Compose 中的网络

services:
  web:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend

networks:
  frontend:
  backend:

web 可以访问 frontendbackenddb 只能访问 backend。实现了网络隔离。

七、日志管理

查看日志

# 实时跟踪
docker logs -f container_name

# 最后 100 行
docker logs --tail 100 container_name

# 指定时间范围
docker logs --since 2026-01-01T00:00:00 container_name
docker logs --since 1h container_name  # 最近 1 小时

日志驱动

daemon.json 中配置全局日志驱动:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

或者在 Compose 中为单个服务配置:

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

集中日志

生产环境建议用集中日志方案:

# 用 Loki + Promtail 收集日志
services:
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    volumes:
      - loki_data:/loki

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    command: -config.file=/etc/promtail/config.yml

八、监控和资源管理

查看资源占用

# 实时查看所有容器的资源占用
docker stats

# 只看一次(不实时刷新)
docker stats --no-stream

限制资源

# 运行时限制
docker run -d --memory=512m --cpus=1.5 nginx

# Compose 中限制
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

健康检查

# Dockerfile 中定义
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1
# Compose 中定义
services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

九、生产环境最佳实践

1. 永远指定版本标签

# ❌ 不要这样
docker pull nginx:latest

# ✅ 要这样
docker pull nginx:1.24-alpine

latest 可能随时更新,某天自动拉取后可能不兼容。

2. 使用 Alpine 版本

# 普通版约 140MB
docker pull nginx:1.24

# Alpine 版约 20MB
docker pull nginx:1.24-alpine

大部分官方镜像都有 Alpine 版本,体积小很多。

3. 设置重启策略

--restart unless-stopped

服务器重启后容器自动启动。unless-stopped 意味着手动停止的不会自动启动。

4. 用 Volume 持久化数据

永远不要把数据存在容器里。 容器删了数据就没了。

5. 限制资源

--memory=512m --cpus=1.5

防止单个容器吃光服务器资源。

6. 限制日志大小

"log-opts": {
  "max-size": "10m",
  "max-file": "3"
}

防止日志撑爆磁盘。

7. 定期更新镜像

docker compose pull
docker compose up -d

定期更新获取安全补丁。

8. 不要用 root 运行应用

# Dockerfile 中创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

9. 用 .env 文件管理敏感信息

# .env(不要提交到 git)
MYSQL_ROOT_PASSWORD=your_secure_password
APP_SECRET=your_app_secret
# docker-compose.yml
services:
  db:
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
# .gitignore
.env

10. 安全扫描

# 扫描镜像漏洞
docker scout cves nginx:1.24

十、常见问题

容器启动就退出

docker ps -a          # 查看退出码
docker logs 容器名     # 看日志

主进程结束 = 容器退出。确保应用在前台运行。

端口被占用

# 查看谁占了端口
ss -tlnp | grep 80

# 换个端口映射
docker run -p 8080:80 nginx

容器内无法上网

# 检查 DNS
docker run --rm alpine ping -c 3 google.com

# 手动指定 DNS
docker run --dns 223.5.5.5 alpine ping google.com

磁盘空间不足

# 查看 Docker 占用
docker system df

# 清理无用资源
docker system prune -a --volumes

时区问题

environment:
  - TZ=Asia/Shanghai
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime

总结

Docker 部署的核心就是:Compose 编排 + Volume 持久化 + 资源限制 + 日志管理

掌握这四点,90% 的部署场景都能应对。遇到问题先看 docker logs,大部分都能定位。

记住:不要把数据存在容器里,不要用 latest 标签,不要忘了限制日志大小。 这三条是生产环境的铁律。


部署不应该是一件痛苦的事。