
译自: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 内部网络通信。说起来简单,踩起坑来可不少。
写 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,代码里一个密码都没有。
# 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 过来,最终镜像里没有开发工具链。典型的前后端分离构建思路,很稳。
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 地址。
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