上一篇 下一篇 分享链接 返回 返回顶部

Docker 部署少踩坑:从容器配置到 Compose 一键上线

发布人:慈云数据-客服中心 发布时间:19小时前 阅读量:3

Docker 使用避坑指南|一键部署

在现代软件开发与运维体系中,Docker 已经成为非常重要的基础工具。无论是个人开发者搭建本地环境,还是企业团队进行微服务部署、持续集成、持续交付,Docker 都能显著提升环境一致性与部署效率。

不过,Docker 虽然上手简单,但真正用好并不容易。很多人在使用过程中会遇到各种“坑”:镜像越来越大、容器数据丢失、端口冲突、权限异常、网络不通、服务重启后配置失效、生产环境直接使用 latest 标签导致版本不可控等。

本文将围绕 Docker 使用避坑指南一键部署实践 展开,帮助你更稳妥地使用 Docker,避免常见错误,并掌握一套适合日常开发和生产部署的思路。


一、为什么要使用 Docker?

在 Docker 出现之前,部署一个应用通常需要在服务器上手动安装各种依赖。例如:

  • 安装指定版本的 Java、Node.js、Python、Go 运行环境;
  • 配置 Nginx、MySQL、Redis 等基础服务;
  • 修改系统环境变量;
  • 处理不同操作系统之间的兼容问题;
  • 解决“我本地能跑,服务器跑不了”的问题。

Docker 的出现很好地解决了这些痛点。

Docker 的核心思想是:将应用及其依赖打包到一个镜像中,然后以容器的形式运行。

这样一来,只要目标机器安装了 Docker,就可以快速启动相同的应用环境。

Docker 带来的主要好处包括:

  1. 环境一致性强
    开发、测试、生产环境可以使用同一个镜像,减少环境差异。

  2. 部署速度快
    容器启动速度通常远快于传统虚拟机。

  3. 资源占用低
    Docker 容器共享宿主机内核,相比虚拟机更轻量。

  4. 便于扩缩容
    可以快速启动多个容器实例,配合负载均衡实现水平扩展。

  5. 方便回滚
    镜像版本可控,出现问题时可以快速回退到旧版本。


二、Docker 基础概念不要混淆

很多 Docker 使用问题,本质上是对几个基础概念理解不清导致的。

1. 镜像 Image

镜像可以理解为应用运行环境的模板。它包含了应用程序、运行时、依赖库、环境变量、配置文件等内容。

例如:

docker pull nginx:1.25

这条命令会拉取一个 Nginx 镜像。

镜像本身是静态的,不会运行。只有基于镜像创建容器之后,应用才会真正启动。


2. 容器 Container

容器是镜像运行起来之后的实例。

例如:

docker run -d --name my-nginx nginx:1.25

这条命令会基于 nginx:1.25 镜像启动一个名为 my-nginx 的容器。

需要注意的是:

容器不是虚拟机,容器的生命周期可能很短,不能把容器内部当成长期保存数据的地方。

这是 Docker 使用中非常重要的避坑点。


3. 数据卷 Volume

数据卷用于持久化容器中的数据。

例如 MySQL 容器中的数据库文件,如果只保存在容器内部,那么删除容器后数据也会丢失。因此,必须将数据目录挂载到宿主机或 Docker Volume 中。

示例:

docker run -d \
  --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql_data:/var/lib/mysql \
  mysql:8.0

这里的 mysql_data 就是一个 Docker Volume,用于持久化 MySQL 数据。


4. 网络 Network

Docker 容器之间可以通过网络通信。默认情况下,Docker 会创建一个 bridge 网络,但在实际项目中,建议为每个项目创建独立网络。

例如:

docker network create app-network

然后启动容器时指定网络:

docker run -d --name redis --network app-network redis:7

同一个自定义网络中的容器,可以通过容器名互相访问。


三、Docker 使用常见避坑指南

下面是 Docker 使用过程中最常见、也最容易踩的坑。


1. 不要在生产环境使用 latest 标签

很多初学者习惯这样启动容器:

docker run -d nginx:latest

这在本地测试时问题不大,但在生产环境非常危险。

latest 并不代表最新稳定版,它只是镜像维护者默认打的一个标签。今天的 latest 和明天的 latest 可能不是同一个版本。

如果你在生产环境中使用 latest,可能会出现以下问题:

  • 重新部署后应用版本变化;
  • 依赖行为不一致;
  • 配置兼容性问题;
  • 回滚困难;
  • 排查问题时无法确认准确版本。

正确做法是指定明确版本:

docker run -d nginx:1.25.4

对于自己构建的业务镜像,也应使用明确版本号,例如:

my-app:v1.0.0
my-app:v1.0.1
my-app:2024-01-15

如果团队有 CI/CD 流程,也可以使用 Git Commit ID 作为镜像标签:

my-app:8f3a2c1

2. 不要把重要数据只放在容器内部

这是最常见的 Docker 事故之一。

很多人启动 MySQL 容器后,发现服务运行正常,于是直接在里面创建数据库、写入业务数据。过了一段时间,容器出现问题,执行了:

docker rm -f mysql

结果数据库数据全部没了。

原因很简单:容器删除后,容器内部的可写层也会被删除。

正确做法是使用数据卷或目录挂载。

使用 Docker Volume

docker volume create mysql_data

docker run -d \
  --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql_data:/var/lib/mysql \
  mysql:8.0

使用宿主机目录挂载

docker run -d \
  --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:8.0

两种方式都可以,但在生产环境中,更建议明确规划数据目录,例如:

/data/docker/mysql
/data/docker/redis
/data/docker/nginx
/data/docker/app

这样备份、迁移、排查问题都更加方便。


3. 端口映射要规划清楚

Docker 容器内部端口和宿主机端口不是一回事。

例如:

docker run -d -p 8080:80 nginx:1.25

含义是:

宿主机端口 8080 -> 容器端口 80

访问宿主机的 8080 端口时,请求会转发到容器内部的 80 端口。

常见错误包括:

  • 把端口顺序写反;
  • 多个容器映射同一个宿主机端口;
  • 容器内部服务监听地址不是 0.0.0.0
  • 防火墙或云服务器安全组未放行端口。

如果启动容器时报错:

port is already allocated

说明宿主机端口已经被占用。

可以使用以下命令查看端口占用:

ss -tunlp | grep 8080

或者:

lsof -i:8080

4. 容器之间通信不要依赖宿主机 IP

很多人在 Docker 中部署多个服务,例如应用、MySQL、Redis,然后在应用配置中写:

mysql_host=127.0.0.1
redis_host=127.0.0.1

这是错误的。

在容器内部,127.0.0.1 指的是容器自身,不是宿主机,也不是其他容器。

正确方式是使用 Docker 自定义网络,并通过容器名访问服务。

示例:

docker network create app-net

启动 MySQL:

docker run -d \
  --name mysql \
  --network app-net \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

启动应用:

docker run -d \
  --name app \
  --network app-net \
  -e DB_HOST=mysql \
  my-app:v1.0.0

此时应用容器中可以通过 mysql:3306 访问 MySQL。

这种方式比写宿主机 IP 更稳定,也更适合迁移和扩展。


5. Dockerfile 不要随便写

Dockerfile 是构建镜像的核心文件。一个糟糕的 Dockerfile 会导致镜像体积巨大、构建速度慢、安全风险高。

避免使用过大的基础镜像

例如:

FROM ubuntu:latest

如果只是运行一个简单的 Node.js 或 Go 应用,使用完整 Ubuntu 镜像往往没有必要。

可以优先选择官方轻量镜像:

FROM node:20-alpine

或:

FROM nginx:1.25-alpine

Alpine 镜像体积小,但也要注意某些依赖兼容问题。如果应用依赖较复杂,可以选择 Debian slim 版本:

FROM node:20-slim

合理利用缓存

Docker 构建镜像时会使用缓存。如果 Dockerfile 顺序不合理,就会导致每次构建都重新安装依赖。

以 Node.js 项目为例,不推荐这样写:

COPY . .
RUN npm install

因为只要任意代码文件变化,npm install 就会重新执行。

更好的写法是:

COPY package.json package-lock.json ./
RUN npm ci
COPY . .

这样只有依赖文件变化时,才会重新安装依赖。


使用多阶段构建

多阶段构建可以显著减小镜像体积。

以 Go 项目为例:

FROM golang:1.22 AS builder

WORKDIR /app
COPY . .
RUN go build -o server .

FROM debian:bookworm-slim

WORKDIR /app
COPY --from=builder /app/server /app/server

EXPOSE 8080
CMD ["./server"]

编译环境只存在于构建阶段,最终镜像中只保留运行所需文件。


6. 不要忽视 .dockerignore

很多人写了 Dockerfile,却忘记写 .dockerignore

如果没有 .dockerignore,构建镜像时可能会把以下内容一起发送给 Docker:

  • .git 目录;
  • node_modules
  • 日志文件;
  • 本地缓存;
  • 临时文件;
  • 测试数据;
  • 密钥文件;
  • IDE 配置。

这会导致构建变慢、镜像变大,甚至泄露敏感信息。

示例 .dockerignore

.git
node_modules
dist
logs
*.log
.env
.idea
.vscode
.DS_Store

特别注意:不要把 .env、私钥、证书等敏感文件打进镜像。


7. 环境变量不要硬编码在镜像中

很多人会在 Dockerfile 中直接写:

ENV MYSQL_PASSWORD=123456

这种方式非常不安全。镜像一旦被推送到仓库,环境变量信息可能被其他人查看到。

更推荐在运行容器时注入环境变量:

docker run -d \
  -e MYSQL_PASSWORD='your_password' \
  my-app:v1.0.0

如果使用 Docker Compose,可以写在 .env 文件中,并确保 .env 不提交到 Git 仓库。


8. 容器日志要限制大小

Docker 默认会使用 json-file 日志驱动。如果不限制日志大小,容器持续运行后日志文件可能占满磁盘。

这是生产环境中非常常见的问题。

可以在启动容器时指定日志限制:

docker run -d \
  --name app \
  --log-driver json-file \
  --log-opt max-size=100m \
  --log-opt max-file=3 \
  my-app:v1.0.0

也可以在 Docker daemon 配置中统一设置:

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

配置文件通常位于:

/etc/docker/daemon.json

修改后重启 Docker:

systemctl restart docker

9. 容器重启策略要设置

如果容器因为异常退出,默认情况下不会自动重启。生产环境中建议设置重启策略。

常用策略包括:

--restart=always

或:

--restart=unless-stopped

示例:

docker run -d \
  --name nginx \
  --restart=unless-stopped \
  -p 80:80 \
  nginx:1.25

两者区别:

  • always:无论什么情况,只要 Docker 启动,就尝试启动容器;
  • unless-stopped:除非你手动停止容器,否则 Docker 启动时会自动启动容器。

一般更推荐使用 unless-stopped


10. 不要长期不清理无用镜像和容器

Docker 使用久了之后,系统中可能会积累大量无用资源:

  • 停止的容器;
  • 未使用的镜像;
  • 悬空镜像;
  • 无用网络;
  • 构建缓存;
  • 未使用的数据卷。

查看磁盘占用:

docker system df

清理无用资源:

docker system prune

如果要清理未使用镜像:

docker image prune -a

如果要清理未使用数据卷:

docker volume prune

但请务必注意:

清理数据卷前一定要确认是否还有重要数据,否则可能造成不可恢复的数据丢失。

生产环境建议定期巡检,而不是盲目执行清理命令。


四、Docker Compose:更适合一键部署

单独使用 docker run 可以启动容器,但当项目包含多个服务时,命令会变得复杂。

例如一个常见 Web 项目可能包括:

  • 后端应用;
  • MySQL;
  • Redis;
  • Nginx;
  • 消息队列;
  • 定时任务。

如果每个服务都手写 docker run,维护成本很高。

这时推荐使用 Docker Compose

Docker Compose 可以通过一个 docker-compose.yml 文件统一定义多个服务,然后使用一条命令启动整个项目:

docker compose up -d

这就是我们常说的“一键部署”。


五、一键部署示例:Nginx + 应用 + MySQL + Redis

下面给出一个较完整的 Docker Compose 示例,适合中小型项目参考。

目录结构建议如下:

project/
├── docker-compose.yml
├── .env
├── app/
│   ├── Dockerfile
│   └── ...
├── nginx/
│   └── nginx.conf
└── data/
    ├── mysql/
    └── redis/

1. .env 示例

MYSQL_ROOT_PASSWORD=change_this_root_password
MYSQL_DATABASE=app_db
MYSQL_USER=app_user
MYSQL_PASSWORD=change_this_app_password

APP_PORT=8080

注意:.env 文件不要提交到公开仓库。


2. docker-compose.yml 示例

services:
  nginx:
    image: nginx:1.25-alpine
    container_name: demo-nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    networks:
      - demo-net
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "3"

  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    image: demo-app:v1.0.0
    container_name: demo-app
    restart: unless-stopped
    environment:
      DB_HOST: mysql
      DB_PORT: 3306
      DB_NAME: ${MYSQL_DATABASE}
      DB_USER: ${MYSQL_USER}
      DB_PASSWORD: ${MYSQL_PASSWORD}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    expose:
      - "8080"
    depends_on:
      - mysql
      - redis
    networks:
      - demo-net
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "3"

  mysql:
    image: mysql:8.0
    container_name: demo-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - ./data/mysql:/var/lib/mysql
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
    networks:
      - demo-net
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "3"

  redis:
    image: redis:7-alpine
    container_name: demo-redis
    restart: unless-stopped
    volumes:
      - ./data/redis:/data
    command: redis-server --appendonly yes
    networks:
      - demo-net
    logging:
      driver: json-file
      options:
        max-size: "100m"
        max-file: "3"

networks:
  demo-net:
    driver: bridge

3. 一键启动

进入项目目录:

cd project

启动所有服务:

docker compose up -d

查看容器状态:

docker compose ps

查看日志:

docker compose logs -f

停止服务:

docker compose down

如果只想重新构建并启动应用:

docker compose up -d --build app

六、生产环境部署建议

Docker 可以简化部署,但生产环境仍然需要遵守基本规范。

1. 镜像版本要可追踪

业务镜像应使用明确版本号,不建议只使用:

app:latest

推荐:

app:v1.2.3
app:20240115
app:git-commit-id

这样可以快速定位线上运行的代码版本。


2. 配置和代码分离

镜像中尽量只包含代码和运行环境,配置通过环境变量或配置文件挂载注入。

这样可以让同一个镜像运行在不同环境:

  • 开发环境;
  • 测试环境;
  • 预发布环境;
  • 生产环境。

3. 数据必须备份

无论使用宿主机目录挂载,还是 Docker Volume,都必须制定备份策略。

例如 MySQL 可以定期执行:

docker exec demo-mysql mysqldump -u root -p app_db > backup.sql

也可以使用定时任务将备份文件同步到远程存储。

需要注意的是,备份不是目的,可恢复才是目的。因此必须定期测试备份文件能否正常恢复。


4. 监控磁盘和日志

Docker 容器日志、镜像缓存、数据库数据都可能占用大量磁盘空间。

建议监控以下指标:

  • 磁盘使用率;
  • Docker 日志大小;
  • 容器重启次数;
  • CPU 和内存使用率;
  • 数据库连接数;
  • 应用错误日志。

常用命令:

docker stats

查看容器资源占用。


5. 不要直接进入容器修改文件

有些人习惯这样操作:

docker exec -it app sh

然后在容器里手动修改配置文件或代码。

这是一种非常不推荐的做法。

原因包括:

  • 容器重建后修改丢失;
  • 无法追踪修改历史;
  • 多实例环境无法保持一致;
  • 容易造成线上环境和镜像版本不一致。

正确做法是:

  1. 修改代码或配置;
  2. 重新构建镜像;
  3. 使用 Compose 或 CI/CD 重新部署。

七、常用排查命令

Docker 出问题时,可以按以下顺序排查。

查看容器列表

docker ps
docker ps -a

查看容器日志

docker logs -f 容器名

例如:

docker logs -f demo-app

进入容器

docker exec -it 容器名 sh

如果容器中有 bash:

docker exec -it 容器名 bash

查看容器详细信息

docker inspect 容器名

查看网络

docker network ls
docker network inspect 网络名

查看镜像

docker images

查看数据卷

docker volume ls

查看资源占用

docker stats

八、Docker 一键部署检查清单

在正式部署前,可以按照下面的清单逐项检查。

镜像相关

  • 是否使用了明确版本号?
  • 是否避免使用 latest
  • Dockerfile 是否足够精简?
  • 是否使用了 .dockerignore
  • 是否避免把敏感信息打进镜像?

数据相关

  • 数据是否挂载到宿主机或 Volume?
  • 数据目录权限是否正确?
  • 是否有备份策略?
  • 是否测试过数据恢复?

网络相关

  • 服务是否在同一个 Docker 网络中?
  • 容器之间是否通过服务名访问?
  • 宿主机端口是否冲突?
  • 防火墙和安全组是否放行?

安全相关

  • 是否避免使用弱密码?
  • 是否只暴露必要端口?
  • 是否限制容器日志大小?
  • 是否避免把密钥写入镜像?
  • 是否控制镜像仓库权限?

运维相关

  • 是否设置 restart 策略?
  • 是否配置日志轮转?
  • 是否能快速回滚?
  • 是否有监控和告警?
  • 是否记录部署版本?

九、总结

Docker 的价值不仅仅是“能把服务跑起来”,更重要的是让应用具备更好的可移植性、可维护性和可交付能力。

如果只是简单执行几条 docker run 命令,确实可以快速启动服务;但如果想在生产环境中稳定运行,就必须重视版本管理、数据持久化、网络规划、日志控制、安全配置和备份恢复。

本文总结的核心避坑原则可以归纳为以下几点:

  1. 生产环境不要使用 latest 标签;
  2. 重要数据必须挂载并定期备份;
  3. 容器之间通信优先使用自定义网络和服务名;
  4. Dockerfile 要精简,并合理利用缓存;
  5. 必须编写 .dockerignore
  6. 敏感信息不要写入镜像;
  7. 容器日志要限制大小;
  8. 生产服务要设置重启策略;
  9. 不要在容器内部手动修改线上文件;
  10. 推荐使用 Docker Compose 实现一键部署。

掌握这些原则后,Docker 不仅能帮助你快速部署项目,也能让你的系统在长期运行中更加稳定、清晰、可控。对于个人开发者来说,它可以节省大量环境配置时间;对于团队来说,它则是构建标准化交付流程的重要基础。

目录结构
全文