部署这件事,以前有多痛苦大家都懂。装环境、改配置、解决依赖冲突,一台新机器搞半天。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 |
--cpus | CPU 限制 | --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_on 的 condition: 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 可以访问 frontend 和 backend,db 只能访问 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 标签,不要忘了限制日志大小。 这三条是生产环境的铁律。
部署不应该是一件痛苦的事。