外观
Docker Compose 部署
适合谁
服务器上已经在用 Docker,希望用一个 compose.yml 管理斑马小铺的人。 这一页的 Compose 只包含斑马小铺自己:
| 容器 | 作用 |
|---|---|
zebra | 后端程序,数据库文件和上传图片放在 Docker 卷里 |
web | Caddy:提供前台和后台网页、转发接口、自动申请 HTTPS 证书 |
镜像直接从源码构建,服务器上不需要安装 Rust 或 Node.js。
准备
| 项目 | 要求 |
|---|---|
| 服务器 | 64 位 Linux;构建镜像时需要约 2 GB 内存(内存小可以加 swap,或在别的机器构建后推送) |
| Docker | Docker Engine 24+,带 Compose v2(docker compose version 能输出版本号) |
| 域名 | shop.example.com 的 A 记录指向服务器;80 和 443 端口没有被其他程序占用 |
| 源码 | 斑马小铺的代码仓库 |
还没装 Docker 的话,按 Docker 官方文档 安装,或执行官方脚本 curl -fsSL https://get.docker.com | sh。
步骤
1. 准备目录和源码
bash
mkdir -p /opt/zebra-docker && cd /opt/zebra-docker
git clone <仓库地址> src最终目录结构:
text
/opt/zebra-docker/
├── src/ # 斑马小铺源码
├── Dockerfile.backend # 后端镜像
├── Dockerfile.web # 网页 + Caddy 镜像
├── compose.yml
├── config.yml # 后端配置
└── Caddyfile # Web 服务器配置2. 写两个 Dockerfile
/opt/zebra-docker/Dockerfile.backend:
dockerfile
# 编译阶段:用官方 Rust 镜像编译后端
FROM rust:1-bookworm AS build
WORKDIR /src
COPY backend ./backend
RUN cd backend && cargo build --release -p zs-server \
&& cp target/release/zebra-store /zebra-store
# 运行阶段:只包含程序本身
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tzdata wget \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --home /app zebra \
&& mkdir -p /app/data /app/uploads && chown -R zebra:zebra /app
COPY --from=build /zebra-store /usr/local/bin/zebra-store
WORKDIR /app
USER zebra
VOLUME ["/app/data", "/app/uploads"]
EXPOSE 8081
HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=6 \
CMD wget -q -O /dev/null http://127.0.0.1:8081/api/v1/public/config || exit 1
ENTRYPOINT ["/usr/local/bin/zebra-store", "--config", "/app/config.yml"]
CMD ["serve"]/opt/zebra-docker/Dockerfile.web:
dockerfile
# 打包前台和后台网页
FROM node:20-alpine AS web
WORKDIR /src
COPY storefront ./storefront
COPY admin ./admin
RUN cd storefront && npm ci && npm run build
RUN cd admin && npm ci && npx vite build --base=/admin/
# Caddy + 网页
FROM caddy:2.10-alpine
COPY --from=web /src/storefront/dist /srv/storefront
COPY --from=web /src/admin/dist /srv/admin3. 写 compose.yml
yaml
name: zebra
services:
zebra:
build:
context: ./src
dockerfile: ../Dockerfile.backend
image: zebra-store:local
restart: unless-stopped
environment:
TZ: UTC
volumes:
- ./config.yml:/app/config.yml:ro
- zebra_data:/app/data
- zebra_uploads:/app/uploads
networks: [zebra]
web:
build:
context: ./src
dockerfile: ../Dockerfile.web
image: zebra-web:local
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on: [zebra]
networks: [zebra]
networks:
zebra:
ipam:
config:
- subnet: 172.28.0.0/24 # 固定网段,下面 trusted_proxies 要用
volumes:
zebra_data:
zebra_uploads:
caddy_data:
caddy_config:网段冲突
如果服务器上已有 Docker 网络占用了 172.28.0.0/24,换一个没被用的网段(例如 172.29.0.0/24), 并同步修改下面 config.yml 里的 trusted_proxies。
4. 写 config.yml
bash
cp src/backend/config.example.yml config.yml然后改成下面这样(三个密钥各执行一次 openssl rand -hex 24 生成):
yaml
app:
secret_key: 第一串随机字符
totp_issuer: Zebra-Store
server:
host: 0.0.0.0 # 容器里要监听所有地址,外部访问由 web 容器转发
port: 8081
mode: release
trusted_proxies: ["172.28.0.0/24"] # 相信同一 Docker 网络里的 Caddy 转发的真实 IP
log:
level: info
database:
url: sqlite://data/zebra.db?mode=rwc # 即容器里的 /app/data/zebra.db(zebra_data 卷)
jwt:
secret: 第二串随机字符
user_jwt:
secret: 第三串随机字符
bootstrap:
default_admin_username: admin
default_admin_password: 你的管理员密码
upload:
dir: uploads # 即 /app/uploads(zebra_uploads 卷)
cors:
allowed_origins: ["https://shop.example.com"]5. 写 Caddyfile
text
shop.example.com {
encode zstd gzip
@backend path /api/* /uploads/* /sitemap.xml /robots.txt /shared/* /plugin/open-api/*
handle @backend {
reverse_proxy zebra:8081
}
redir /admin /admin/ 308
handle_path /admin/* {
root * /srv/admin
try_files {path} /index.html
file_server
}
handle {
root * /srv/storefront
try_files {path} /index.html
file_server
}
header /assets/* Cache-Control "public, max-age=31536000, immutable"
}把 shop.example.com 换成你的域名。Caddy 会自动申请并续期 Let's Encrypt 证书。
哪些路径要转给后端
前台和后台只是静态网页,所有数据都来自后端。反向代理必须把下面这些路径转发给后端 127.0.0.1:8081, 并保留原始的 Host 头:
| 路径 | 用途 |
|---|---|
/api/ | 所有接口,包括支付回调和对接接口 |
/uploads/ | 上传的图片 |
/sitemap.xml、/robots.txt | 搜索引擎 |
/shared/ | 异次元发卡把本站当上游时调用(异次元对接) |
/plugin/open-api/ | 萌次元协议把本站当上游时调用(萌次元对接) |
其余路径:/admin/ 开头的返回后台网页,其他的返回前台网页;找不到文件时返回对应的 index.html (前台和后台都是单页应用,刷新 /products/xxx 这类地址时服务器上并没有这个文件)。
一定要保留 Host 头
后端靠 Host 判断访问的是主站还是哪个分站,生成支付回调地址时也会用到。
6. 构建并启动
bash
cd /opt/zebra-docker
docker compose build # 第一次 10–20 分钟(编译 Rust)
docker compose up -d
docker compose ps # zebra 显示 healthy、web 显示 running
docker compose logs -f zebra # 看后端日志,Ctrl+C 退出用 PostgreSQL 代替 SQLite(可选)
在 compose.yml 的 services: 下加一个数据库服务,在 volumes: 下加 pg_data::
yaml
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: zebra
POSTGRES_PASSWORD: 换成一个强密码
POSTGRES_DB: zebra
volumes:
- pg_data:/var/lib/postgresql/data
networks: [zebra]同时给 zebra 服务加上 depends_on: [db],把 config.yml 改成:
yaml
database:
url: postgres://zebra:换成一个强密码@db:5432/zebra然后 docker compose up -d。详见 切换数据库。
验证
逐项检查:
- [ ]
https://你的域名/能打开前台,标题是你的站点名; - [ ]
https://你的域名/admin/能打开后台登录页,用admin和配置里的密码能登录; - [ ]
https://你的域名/api/v1/public/config返回一段 JSON(开头是{"status_code":0); - [ ] 在
https://你的域名/products页面按 F5 刷新,不会出现 404; - [ ] 后台 内容管理 → 素材管理 上传一张图片,能正常显示(说明
/uploads/转发正常); - [ ] 浏览器地址栏有小锁,证书有效。
登录后台后,按 部署完成后要做的事 继续。
升级
bash
cd /opt/zebra-docker
# 先备份(见下方)
git -C src pull
docker compose build
docker compose up -d # 只重建有变化的容器;新版本启动时自动补齐表结构
docker image prune -f # 可选:清理旧镜像备份
数据在 Docker 卷里(卷名是“项目名_卷名”,即 zebra_zebra_data 和 zebra_zebra_uploads):
bash
cd /opt/zebra-docker && mkdir -p backup
docker compose stop zebra
docker run --rm -v zebra_zebra_data:/d -v "$PWD/backup":/b alpine \
tar czf /b/data-$(date +%F).tgz -C /d .
docker run --rm -v zebra_zebra_uploads:/d -v "$PWD/backup":/b alpine \
tar czf /b/uploads-$(date +%F).tgz -C /d .
cp config.yml backup/config-$(date +%F).yml
docker compose start zebra恢复时把压缩包解回对应的卷(先 docker compose stop zebra):
bash
docker run --rm -v zebra_zebra_data:/d -v "$PWD/backup":/b alpine \
sh -c "rm -rf /d/* && tar xzf /b/data-2026-01-01.tgz -C /d"用 PostgreSQL 时再加一步:docker compose exec db pg_dump -U zebra zebra > backup/zebra-$(date +%F).sql。
常见问题
docker compose build 时被杀掉(Killed / exit code 137) 内存不够编译 Rust。加 2 GB swap(fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile)后重试, 或者在别的机器上构建,然后用 docker save zebra-store:local | ssh 服务器 docker load 传过去。
证书申请失败 确认域名已经解析到这台服务器、80/443 端口没有被别的程序占用(ss -ltnp | grep -E ':80|:443')、云服务商安全组已放行。 docker compose logs web 里有详细原因。
已经有别的反向代理占用了 80/443 去掉 web 服务的 ports,改成 - "127.0.0.1:8080:80",把 Caddyfile 第一行改成 :80, 再在原来的反向代理里把 shop.example.com 整站转发到 127.0.0.1:8080(保留 Host 头)。 此时 trusted_proxies 需要同时包含 Docker 网段。
后端日志提示 unable to open database file 卷的权限不对,通常是手动创建过同名卷。执行 docker compose down、docker volume rm zebra_zebra_data(会删除数据,仅限新站)后重新 up。