Skip to content

Docker容器化部署实战 ​

这篇文档讲的是“一个典型 Web 项目如何落成一套可运行的容器化部署”。重点不是某个具体技术栈,而是应用、数据库、Nginx、Compose 之间怎么配合。

1. 适用场景 ​

这套方案适合:

  • 单机服务器部署
  • 测试环境 / 预发布环境
  • 中小型项目的生产环境
  • 需要快速复制环境的场景

它不解决的事情包括:

  • K8s 集群编排
  • 大规模弹性扩缩
  • 服务网格与多集群治理

如果你现在还在单机或单项目阶段,这篇文档已经足够实用。


2. 一个典型的部署结构 ​

以“应用 + MySQL + Nginx”为例,容器化后的结构通常是:

text
用户请求
   |
   v
Nginx
   |
   v
App Container
   |
   v
MySQL Container

职责拆分如下:

  • App:运行你的业务程序
  • MySQL:持久化业务数据
  • Nginx:统一入口、反向代理、静态资源、HTTPS

这比“把所有东西都塞进一个容器”更容易维护,也更方便单独重启和排障。


3. 发布模式先想清楚 ​

容器化部署大致有两种常见模式:

3.1 服务器上现编译现构建 ​

流程:

  1. 上传源码
  2. 在服务器执行 docker build
  3. 构建镜像并启动

优点:

  • 流程直观
  • 适合早期验证

缺点:

  • 服务器要装构建依赖
  • 构建结果容易受环境影响
  • 慢

3.2 本地或 CI 先发布,再构建运行镜像 ​

流程:

  1. 在本地或流水线先完成发布
  2. 上传发布产物
  3. 用运行时镜像打包

优点:

  • 更符合生产实践
  • 服务器更干净
  • 镜像更小

经验上:

开发测试阶段可以接受“服务器现构建”,长期方案更推荐“先发布再进镜像”。


4. 项目目录建议 ​

text
deploy/
├── app/                    # 应用发布产物
├── nginx/
│   └── default.conf        # Nginx 配置
├── mysql/
│   └── data/               # MySQL 数据目录
├── Dockerfile              # 应用镜像
└── docker-compose.yml      # 整套编排配置

这样做的好处:

  • 部署目录职责清晰
  • 迁移方便
  • 备份时知道该备份什么

5. 应用镜像怎么写 ​

这里给一个通用运行时版本的 Dockerfile 思路,适合“已经有发布产物”的场景。

dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0

WORKDIR /app
COPY . /app

EXPOSE 80
ENTRYPOINT ["dotnet", "YourApp.dll"]

这类 Dockerfile 的特点:

  • 简单
  • 稳定
  • 适合服务器只负责运行

如果你想在镜像内完成 restore / build / publish,则应使用多阶段构建。那部分更适合看工具侧的 ASP.NET项目Docker构建指南。


6. Compose 编排示例 ​

下面是一份单机可落地的 Compose 示例:

yaml
services:
  app:
    build:
      context: ./app
      dockerfile: ../Dockerfile
    container_name: app-service
    restart: unless-stopped
    depends_on:
      - mysql
    networks:
      - app-net

  mysql:
    image: mysql:8.0
    container_name: app-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: YourStrongPassword
      MYSQL_DATABASE: app_db
    volumes:
      - ./mysql/data:/var/lib/mysql
    networks:
      - app-net

  nginx:
    image: nginx:1.25
    container_name: app-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

6.1 这份配置的关键点 ​

  1. 三层职责拆开
  2. 数据库数据挂载到宿主机
  3. Nginx 只负责入口
  4. 服务之间通过容器名访问

7. Nginx 反向代理怎么写 ​

nginx
server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://app:80;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

7.1 这里最重要的一点 ​

proxy_pass 里通常写的是:

nginx
http://app:80

而不是 localhost。

因为这里的 app 是 Compose 中定义的服务名,容器间通过同一网络里的服务名互相访问。


8. 应用连接数据库时要改什么 ​

最常见的错误,是应用配置里数据库地址还写着:

text
127.0.0.1
localhost

容器化后通常要改成:

text
mysql

例如:

json
"ConnectionStrings": {
  "Default": "server=mysql;port=3306;database=app_db;user=root;password=YourStrongPassword;"
}

记住:

容器里写 localhost,通常指向的是当前容器自己,不是 MySQL 容器。


9. 实际部署流程 ​

9.1 启动 ​

bash
docker compose up -d --build

9.2 查看运行状态 ​

bash
docker compose ps

9.3 看日志 ​

bash
docker compose logs
docker compose logs -f app
docker compose logs -f nginx

9.4 停止并保留数据 ​

bash
docker compose down

如果你已经做了 volume 挂载,MySQL 数据不会因为 down 就丢。


10. 这类部署最容易踩的坑 ​

10.1 数据库没挂载 ​

结果:容器一删,数据全没。

解决:

yaml
volumes:
  - ./mysql/data:/var/lib/mysql

10.2 应用先启动,数据库还没就绪 ​

depends_on 只保证启动顺序,不保证数据库已经可以接受连接。

解决思路:

  • 应用层做重试
  • 或数据库加健康检查

10.3 Nginx 配置里写错服务名 ​

例如 Compose 里叫 app,Nginx 里写成 app-service 或 localhost,就会直接 502。

10.4 把敏感信息写死在 Compose ​

例如数据库密码直接硬编码在 docker-compose.yml。

更稳的做法:

  • 用 .env
  • 用环境变量注入
  • 或用单独配置文件挂载

11. 什么时候该从 Compose 升级 ​

如果你出现下面这些需求,就说明单机 Compose 可能开始吃力了:

  • 多台服务器统一编排
  • 自动扩容
  • 滚动发布
  • 服务发现复杂
  • 统一健康检查与自愈

这时候再考虑 K8s,不要过早上集群。


12. 一句话总结 ​

Docker 容器化部署的核心,不是把程序“塞进容器里”,而是把应用、数据库、网关和数据持久化的职责拆清楚,再用 Compose 组织成一套可复制、可迁移、可排障的部署结构。

方舟 · Ark