From 67dc4bc2bfaca2ddb65c7e91d57c351dc754d9cc Mon Sep 17 00:00:00 2001 From: Fendy Date: Mon, 7 Sep 2026 20:53:41 +0800 Subject: [PATCH] feat(deploy): dev/prod Dockerfiles in service dirs (backend/,web/), templates live beside rendered outputs, entrypoint copies /etc/booklib conf before resolver injection --- .dockerignore | 5 ++- .gitignore | 3 +- backend/Dockerfile.dev | 8 ++++ .../Dockerfile.api => backend/Dockerfile.prod | 5 ++- deploy/docker-compose.dev.yml | 1 + deploy/docker-compose.yml | 41 +++++++++++++++++++ deploy/entrypoint-resolver.sh | 7 ++++ deploy/nginx.conf | 23 ----------- .../conf.d}/default.conf.tpl | 0 deploy/{templates => nginx}/nginx.conf.tpl | 0 deploy/prepare.sh | 13 +++--- deploy/redis/redis.conf.tpl | 4 ++ deploy/templates/redis.conf.tpl | 4 -- web/Dockerfile.dev | 6 +++ deploy/Dockerfile.web => web/Dockerfile.prod | 6 +-- 15 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 backend/Dockerfile.dev rename deploy/Dockerfile.api => backend/Dockerfile.prod (63%) create mode 100644 deploy/docker-compose.yml delete mode 100644 deploy/nginx.conf rename deploy/{templates => nginx/conf.d}/default.conf.tpl (100%) rename deploy/{templates => nginx}/nginx.conf.tpl (100%) create mode 100644 deploy/redis/redis.conf.tpl delete mode 100644 deploy/templates/redis.conf.tpl create mode 100644 web/Dockerfile.dev rename deploy/Dockerfile.web => web/Dockerfile.prod (60%) diff --git a/.dockerignore b/.dockerignore index b57aa85..01bd18c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -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 diff --git a/.gitignore b/.gitignore index b297d3c..47d61eb 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..dc7c1d2 --- /dev/null +++ b/backend/Dockerfile.dev @@ -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"] diff --git a/deploy/Dockerfile.api b/backend/Dockerfile.prod similarity index 63% rename from deploy/Dockerfile.api rename to backend/Dockerfile.prod index ece5080..4cddfa9 100644 --- a/deploy/Dockerfile.api +++ b/backend/Dockerfile.prod @@ -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"] diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml index e19ac4b..2f6c606 100644 --- a/deploy/docker-compose.dev.yml +++ b/deploy/docker-compose.dev.yml @@ -1,3 +1,4 @@ +name: booklib services: postgres: image: postgres:16-alpine diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..3e1d3e5 --- /dev/null +++ b/deploy/docker-compose.yml @@ -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: diff --git a/deploy/entrypoint-resolver.sh b/deploy/entrypoint-resolver.sh index 0ca0db1..be4833b 100644 --- a/deploy/entrypoint-resolver.sh +++ b/deploy/entrypoint-resolver.sh @@ -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;' diff --git a/deploy/nginx.conf b/deploy/nginx.conf deleted file mode 100644 index 09aab30..0000000 --- a/deploy/nginx.conf +++ /dev/null @@ -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; - } -} diff --git a/deploy/templates/default.conf.tpl b/deploy/nginx/conf.d/default.conf.tpl similarity index 100% rename from deploy/templates/default.conf.tpl rename to deploy/nginx/conf.d/default.conf.tpl diff --git a/deploy/templates/nginx.conf.tpl b/deploy/nginx/nginx.conf.tpl similarity index 100% rename from deploy/templates/nginx.conf.tpl rename to deploy/nginx/nginx.conf.tpl diff --git a/deploy/prepare.sh b/deploy/prepare.sh index 593ddaa..8872888 100755 --- a/deploy/prepare.sh +++ b/deploy/prepare.sh @@ -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)" diff --git a/deploy/redis/redis.conf.tpl b/deploy/redis/redis.conf.tpl new file mode 100644 index 0000000..53be327 --- /dev/null +++ b/deploy/redis/redis.conf.tpl @@ -0,0 +1,4 @@ +# booklib redis 配置 — 由 deploy/prepare.sh 从本目录 redis.conf.tpl 渲染,勿直接编辑产物 redis.conf +maxmemory {{REDIS_MAXMEMORY}} +maxmemory-policy {{REDIS_MAXMEMORY_POLICY}} +dir /data diff --git a/deploy/templates/redis.conf.tpl b/deploy/templates/redis.conf.tpl deleted file mode 100644 index d91fa80..0000000 --- a/deploy/templates/redis.conf.tpl +++ /dev/null @@ -1,4 +0,0 @@ -# booklib redis 配置 — 由 deploy/prepare.sh 从 templates/redis.conf.tpl 渲染,勿直接编辑产物 -maxmemory {{REDIS_MAXMEMORY}} -maxmemory-policy {{REDIS_MAXMEMORY_POLICY}} -dir /data diff --git a/web/Dockerfile.dev b/web/Dockerfile.dev new file mode 100644 index 0000000..fef499b --- /dev/null +++ b/web/Dockerfile.dev @@ -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"] diff --git a/deploy/Dockerfile.web b/web/Dockerfile.prod similarity index 60% rename from deploy/Dockerfile.web rename to web/Dockerfile.prod index fb1a13a..2609882 100644 --- a/deploy/Dockerfile.web +++ b/web/Dockerfile.prod @@ -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"]