feat(deploy): dev/prod Dockerfiles in service dirs (backend/,web/), templates live beside rendered outputs, entrypoint copies /etc/booklib conf before resolver injection

This commit is contained in:
2026-09-07 20:53:41 +08:00
parent a78442da54
commit 67dc4bc2bf
15 changed files with 85 additions and 41 deletions
+3 -2
View File
@@ -2,8 +2,9 @@
.git
.superpowers/
deploy/logs/
deploy/nginx/
deploy/redis/
deploy/nginx/nginx.conf
deploy/nginx/conf.d/default.conf
deploy/redis/redis.conf
deploy/api/storage/
backend/booklib*
web/node_modules
+2 -1
View File
@@ -1,7 +1,8 @@
.env
.superpowers/
backend/server
deploy/nginx/
deploy/nginx/nginx.conf
deploy/nginx/conf.d/default.conf
deploy/redis/redis.conf
deploy/logs/
deploy/api/storage/*
+8
View File
@@ -0,0 +1,8 @@
FROM golang:1.26 AS base
WORKDIR /app
FROM base AS dev
# 源码由 compose 挂载进 /app;镜像只带工具链 + delve
RUN go install github.com/go-delve/delve/cmd/dlv@latest
EXPOSE 8080 2345
CMD ["sh", "-c", "go mod download && dlv debug ./cmd/server --headless --listen=0.0.0.0:2345 --api-version=2 --continue --log"]
@@ -5,10 +5,11 @@ RUN go mod download
COPY backend/ ./
RUN CGO_ENABLED=0 go build -trimpath -o /server ./cmd/server
FROM alpine:3.20
FROM alpine:3.20 AS runner
RUN adduser -D -H app
COPY --from=build /server /server
RUN mkdir -p /data/cache /data/books && chown app:app /data/cache /data/books
# /data/books 由宿主 bind(./api/storage)覆盖;/data 下目录预建并授权,兼容 podman
RUN mkdir -p /data && chown app:app /data
USER app
EXPOSE 8080
ENTRYPOINT ["/server"]
+1
View File
@@ -1,3 +1,4 @@
name: booklib
services:
postgres:
image: postgres:16-alpine
+41
View File
@@ -0,0 +1,41 @@
# release:编译产物、无源码挂载、infra 端口不出宿主机
name: booklib
services:
web:
build: { context: .., dockerfile: web/Dockerfile.prod, target: runner }
ports: ["${WEB_PORT:-8080}:80"]
volumes:
- ./nginx/nginx.conf:/etc/booklib/nginx.conf:ro
- ./nginx/conf.d/default.conf:/etc/booklib/default.conf:ro
- ./logs/nginx:/var/log/nginx
depends_on: [api]
api:
build: { context: .., dockerfile: backend/Dockerfile.prod, target: runner }
environment:
DATABASE_URL: postgres://lib:lib@postgres:5432/lib?sslmode=disable
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
ADMIN_USER: ${ADMIN_USER}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
BOOKS_DIR: /data/books
CACHE_DIR: /data/books/cache
SCAN_INTERVAL_SEC: ${SCAN_INTERVAL_SEC:-60}
volumes:
- ./api/storage:/data/books
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_started }
postgres:
image: postgres:16-alpine
environment: { POSTGRES_USER: lib, POSTGRES_PASSWORD: lib, POSTGRES_DB: lib }
volumes: [postgres_data:/var/lib/postgresql/data]
healthcheck: { test: ["CMD-SHELL", "pg_isready -U lib"], interval: 2s, timeout: 2s, retries: 30 }
redis:
image: redis:7-alpine
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
- redis_data:/data
volumes:
postgres_data:
redis_data:
+7
View File
@@ -3,11 +3,18 @@
# (Docker=127.0.0.11,podman aardvark=网络网关,见容器 /etc/resolv.conf)。
# 启动前取 resolv.conf 首个 nameserver 注入 nginx 配置,保住 spec §11 的
# 变量式 proxy_pass 运行时重解析(valid=10s,scale/重建后秒级感知新 IP)。
# 配置以 :ro 挂在 /etc/booklib/(deploy/prepare.sh 渲染产物),先拷入原位再改写。
set -eu
for f in nginx.conf default.conf; do
[ -r "/etc/booklib/$f" ] || { echo "entrypoint-resolver: missing /etc/booklib/$f — 先跑 deploy/prepare.sh" >&2; exit 1; }
done
cp /etc/booklib/nginx.conf /etc/nginx/nginx.conf
cp /etc/booklib/default.conf /etc/nginx/conf.d/default.conf
RESOLVER=$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf 2>/dev/null || true)
[ -n "$RESOLVER" ] || RESOLVER=127.0.0.11
CONF=/etc/nginx/conf.d/default.conf
sed -i "s#resolver [0-9a-fA-F:.]* valid=#resolver ${RESOLVER} valid=#" "$CONF"
echo "entrypoint-resolver: resolver=${RESOLVER} injected into ${CONF}"
nginx -t
if [ $# -gt 0 ]; then exec "$@"; fi
exec nginx -g 'daemon off;'
-23
View File
@@ -1,23 +0,0 @@
server {
listen 80;
client_max_body_size 200m;
# 地址为占位默认值(127.0.0.11=Docker 内嵌 DNS);镜像 entrypoint 启动时会按
# /etc/resolv.conf 的首个 nameserver 重写本行,兼容 podman aardvark-dns。
resolver 127.0.0.11 valid=10s;
# spec §7 风险接受所假设的 CSP:全部同源,blob:/data: 供 SW/reader 用
add_header Content-Security-Policy "default-src 'self'; img-src 'self' blob: data:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' blob: data:; object-src 'none'; frame-src 'self' blob:" always;
location /api/ {
set $api_upstream http://api:8080; # 变量式 → 每次按 DNS 解析,scale 后轮询到新副本(spec §11)
proxy_pass $api_upstream; # 无 URI 部分:保留 /api 前缀转发
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}
}
+7 -6
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# 渲染 deploy/templates/*.tpl → deploy/nginx、deploy/redis,并创建运行所需目录。
# 渲染各目录下的 *.tpl → 同位置产物(nginx/nginx.conf 等),并创建运行所需目录。
# 约定:每次 up 之前(或改模板/.env 后)必须重跑本脚本。幂等,可反复执行。
set -eu
cd "$(dirname "$0")"
@@ -12,18 +12,19 @@ NGINX_CLIENT_MAX_BODY_SIZE=${NGINX_CLIENT_MAX_BODY_SIZE:-200m}
REDIS_MAXMEMORY=${REDIS_MAXMEMORY:-128mb}
REDIS_MAXMEMORY_POLICY=${REDIS_MAXMEMORY_POLICY:-allkeys-lru}
for t in templates/nginx.conf.tpl templates/default.conf.tpl templates/redis.conf.tpl; do
# 模板与渲染产物同目录:改模板即改在产物旁边,产物文件名 = 模板名去 .tpl
for t in nginx/nginx.conf.tpl nginx/conf.d/default.conf.tpl redis/redis.conf.tpl; do
[ -f "$t" ] || { echo "prepare.sh: missing template $t" >&2; exit 1; }
done
mkdir -p nginx/conf.d redis logs/nginx api/storage
mkdir -p logs/nginx api/storage
sed -e "s|{{NGINX_CLIENT_MAX_BODY_SIZE}}|${NGINX_CLIENT_MAX_BODY_SIZE}|g" \
templates/nginx.conf.tpl > nginx/nginx.conf
nginx/nginx.conf.tpl > nginx/nginx.conf
sed -e "s|{{NGINX_CLIENT_MAX_BODY_SIZE}}|${NGINX_CLIENT_MAX_BODY_SIZE}|g" \
templates/default.conf.tpl > nginx/conf.d/default.conf
nginx/conf.d/default.conf.tpl > nginx/conf.d/default.conf
sed -e "s|{{REDIS_MAXMEMORY}}|${REDIS_MAXMEMORY}|g" \
-e "s|{{REDIS_MAXMEMORY_POLICY}}|${REDIS_MAXMEMORY_POLICY}|g" \
templates/redis.conf.tpl > redis/redis.conf
redis/redis.conf.tpl > redis/redis.conf
echo "prepare.sh: rendered nginx($(pwd)/nginx), redis($(pwd)/redis), logs($(pwd)/logs/nginx), storage($(pwd)/api/storage)"
+4
View File
@@ -0,0 +1,4 @@
# booklib redis 配置 — 由 deploy/prepare.sh 从本目录 redis.conf.tpl 渲染,勿直接编辑产物 redis.conf
maxmemory {{REDIS_MAXMEMORY}}
maxmemory-policy {{REDIS_MAXMEMORY_POLICY}}
dir /data
-4
View File
@@ -1,4 +0,0 @@
# booklib redis 配置 — 由 deploy/prepare.sh 从 templates/redis.conf.tpl 渲染,勿直接编辑产物
maxmemory {{REDIS_MAXMEMORY}}
maxmemory-policy {{REDIS_MAXMEMORY_POLICY}}
dir /data
+6
View File
@@ -0,0 +1,6 @@
FROM node:22 AS dev
WORKDIR /app
COPY web/package.json web/package-lock.json ./
RUN npm ci
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
@@ -5,9 +5,9 @@ RUN npm ci
COPY web/ ./
RUN npm run build
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
FROM nginx:1.27-alpine AS runner
COPY --chmod=755 deploy/entrypoint-resolver.sh /entrypoint-resolver.sh
COPY --from=build /src/dist /usr/share/nginx/html
# entrypoint 先按 /etc/resolv.conf 注入 resolver(Docker/podman 双运行时),再执行继承的 nginx CMD
# nginx 配置不 bake 进镜像:由 compose 挂到 /etc/booklib/(渲染产物),
# entrypoint 启动时拷入原位、注入运行时 resolver 再 exec nginx
ENTRYPOINT ["/entrypoint-resolver.sh"]