site logo

Marico's space

用 Docker Compose 在 Azure 上部署全栈书店:一个 Capstone 项目的踩坑实录

服务器技术 2026-04-22 14:52:31 8

译自:How I Deployed a Full-Stack Bookstore with Docker Compose on Azure

这是我的一个 Capstone 项目,记录一下部署全栈应用到 Azure 的完整流程。EpicBook 是一个在线书店,后端用 Node.js + Express,数据库用 MySQL,前端用 Handlebars,再用 Nginx 做反向代理。数据是真实的:54 本书、53 位作者。

架构长这样:只有 Nginx 暴露在公网,MySQL 完全在内网,容器之间通过 Docker 内部网络通信。说起来简单,踩起坑来可不少。

一、配置文件:.env 是第一优先级

写 Dockerfile 之前,先把 .env 搞定了:

MYSQL_ROOT_PASSWORD=<strong-password>
MYSQL_DATABASE=bookstore
MYSQL_USER=epicbook
MYSQL_PASSWORD=<strong-password>
NODE_ENV=production
PORT=8080
DB_HOST=db
DB_USER=epicbook
DB_PASSWORD=<strong-password>
DB_NAME=bookstore

原来的 config.json 硬编码了 Heroku 数据库的 URL,这波操作有点骚——直接把凭证写死在代码里肯定不行。改成指向 Docker Compose 的服务名 db,敏感信息全放 .env,代码里一个密码都没有。

二、多阶段 Dockerfile:别在生产镜像里留 dev 工具

# Stage 1 - install production dependencies
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2 - production runtime
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

Builder 阶段装生产依赖,运行时阶段直接 COPY 过来,最终镜像里没有开发工具链。典型的前后端分离构建思路,很稳。

三、Nginx 配置:反向代理就这么简单

upstream epicbook_app {
    server app:8080;
}

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://epicbook_app;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

app 是 Docker Compose 的服务名,Docker 内置 DNS 自动解析,不需要记任何 IP 地址。

四、docker-compose.yml:三服务、双网络、双卷

version: 3.8

networks:
  frontend_network:
    driver: bridge
  backend_network:
    driver: bridge

volumes:
  db_data:
  nginx_logs:

services:
  db:
    image: mysql:8.0
    networks:
      - backend_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5
      start_period: 30s

  app:
    build: .
    depends_on:
      db:
        condition: service_healthy
    networks:
      - frontend_network
      - backend_network

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend_network

这里最关键的是 depends_on: condition: service_healthy——依赖的服务必须健康检查通过了才会启动下一个。这比传统的 depends_on(只等容器起来,不等服务就绪)靠谱多了。

五、踩坑实录:三个问题及修复方法

问题 1:部署后网站显示"没有书籍"

网站能打开,但数据是空的。SQL 种子文件明明执行了没报错,数据呢?

原因:docker exec 用 stdin 重定向读取文件,是从 VM 宿主机读的,但执行是在容器里——文件根本不在容器内!

修复:用 docker cp 把文件拷进容器再执行:

docker cp db/author_seed.sql epicbook_db:/tmp/author_seed.sql
docker exec epicbook_db mysql -u epicbook -pPassword bookstore -e "source /tmp/author_seed.sql"

这个坑很典型,docker exec + stdin 重定向几乎总是踩雷。

问题 2:Nginx 健康检查显示 unhealthy

健康检查用的是 wget,但 nginx:alpine 默认不带这个工具。

修复:直接删掉 Nginx 的健康检查。网站本身已经在正常服务,app 的健康检查已经保证了启动顺序,Nginx 这里多此一举反而徒增烦恼。

问题 3:docker-compose.yml 被 heredoc 粘贴损坏

YAML 里的特殊字符导致 heredoc 粘贴到一半就断了。这个问题在往 Azure VM 里粘贴长配置文件时特别容易遇到。

修复:用 Python 脚本写文件,绕过 shell heredoc:

python3 -c "
content = ..yaml