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:
+3
-2
@@ -2,8 +2,9 @@
|
|||||||
.git
|
.git
|
||||||
.superpowers/
|
.superpowers/
|
||||||
deploy/logs/
|
deploy/logs/
|
||||||
deploy/nginx/
|
deploy/nginx/nginx.conf
|
||||||
deploy/redis/
|
deploy/nginx/conf.d/default.conf
|
||||||
|
deploy/redis/redis.conf
|
||||||
deploy/api/storage/
|
deploy/api/storage/
|
||||||
backend/booklib*
|
backend/booklib*
|
||||||
web/node_modules
|
web/node_modules
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
.env
|
.env
|
||||||
.superpowers/
|
.superpowers/
|
||||||
backend/server
|
backend/server
|
||||||
deploy/nginx/
|
deploy/nginx/nginx.conf
|
||||||
|
deploy/nginx/conf.d/default.conf
|
||||||
deploy/redis/redis.conf
|
deploy/redis/redis.conf
|
||||||
deploy/logs/
|
deploy/logs/
|
||||||
deploy/api/storage/*
|
deploy/api/storage/*
|
||||||
|
|||||||
@@ -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/ ./
|
COPY backend/ ./
|
||||||
RUN CGO_ENABLED=0 go build -trimpath -o /server ./cmd/server
|
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
|
RUN adduser -D -H app
|
||||||
COPY --from=build /server /server
|
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
|
USER app
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["/server"]
|
ENTRYPOINT ["/server"]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
name: booklib
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -3,11 +3,18 @@
|
|||||||
# (Docker=127.0.0.11,podman aardvark=网络网关,见容器 /etc/resolv.conf)。
|
# (Docker=127.0.0.11,podman aardvark=网络网关,见容器 /etc/resolv.conf)。
|
||||||
# 启动前取 resolv.conf 首个 nameserver 注入 nginx 配置,保住 spec §11 的
|
# 启动前取 resolv.conf 首个 nameserver 注入 nginx 配置,保住 spec §11 的
|
||||||
# 变量式 proxy_pass 运行时重解析(valid=10s,scale/重建后秒级感知新 IP)。
|
# 变量式 proxy_pass 运行时重解析(valid=10s,scale/重建后秒级感知新 IP)。
|
||||||
|
# 配置以 :ro 挂在 /etc/booklib/(deploy/prepare.sh 渲染产物),先拷入原位再改写。
|
||||||
set -eu
|
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)
|
RESOLVER=$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf 2>/dev/null || true)
|
||||||
[ -n "$RESOLVER" ] || RESOLVER=127.0.0.11
|
[ -n "$RESOLVER" ] || RESOLVER=127.0.0.11
|
||||||
CONF=/etc/nginx/conf.d/default.conf
|
CONF=/etc/nginx/conf.d/default.conf
|
||||||
sed -i "s#resolver [0-9a-fA-F:.]* valid=#resolver ${RESOLVER} valid=#" "$CONF"
|
sed -i "s#resolver [0-9a-fA-F:.]* valid=#resolver ${RESOLVER} valid=#" "$CONF"
|
||||||
echo "entrypoint-resolver: resolver=${RESOLVER} injected into ${CONF}"
|
echo "entrypoint-resolver: resolver=${RESOLVER} injected into ${CONF}"
|
||||||
|
nginx -t
|
||||||
if [ $# -gt 0 ]; then exec "$@"; fi
|
if [ $# -gt 0 ]; then exec "$@"; fi
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@@ -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
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 渲染 deploy/templates/*.tpl → deploy/nginx、deploy/redis,并创建运行所需目录。
|
# 渲染各目录下的 *.tpl → 同位置产物(nginx/nginx.conf 等),并创建运行所需目录。
|
||||||
# 约定:每次 up 之前(或改模板/.env 后)必须重跑本脚本。幂等,可反复执行。
|
# 约定:每次 up 之前(或改模板/.env 后)必须重跑本脚本。幂等,可反复执行。
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
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=${REDIS_MAXMEMORY:-128mb}
|
||||||
REDIS_MAXMEMORY_POLICY=${REDIS_MAXMEMORY_POLICY:-allkeys-lru}
|
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; }
|
[ -f "$t" ] || { echo "prepare.sh: missing template $t" >&2; exit 1; }
|
||||||
done
|
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" \
|
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" \
|
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" \
|
sed -e "s|{{REDIS_MAXMEMORY}}|${REDIS_MAXMEMORY}|g" \
|
||||||
-e "s|{{REDIS_MAXMEMORY_POLICY}}|${REDIS_MAXMEMORY_POLICY}|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)"
|
echo "prepare.sh: rendered nginx($(pwd)/nginx), redis($(pwd)/redis), logs($(pwd)/logs/nginx), storage($(pwd)/api/storage)"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# booklib redis 配置 — 由 deploy/prepare.sh 从本目录 redis.conf.tpl 渲染,勿直接编辑产物 redis.conf
|
||||||
|
maxmemory {{REDIS_MAXMEMORY}}
|
||||||
|
maxmemory-policy {{REDIS_MAXMEMORY_POLICY}}
|
||||||
|
dir /data
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# booklib redis 配置 — 由 deploy/prepare.sh 从 templates/redis.conf.tpl 渲染,勿直接编辑产物
|
|
||||||
maxmemory {{REDIS_MAXMEMORY}}
|
|
||||||
maxmemory-policy {{REDIS_MAXMEMORY_POLICY}}
|
|
||||||
dir /data
|
|
||||||
@@ -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/ ./
|
COPY web/ ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.27-alpine
|
FROM nginx:1.27-alpine AS runner
|
||||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
COPY --chmod=755 deploy/entrypoint-resolver.sh /entrypoint-resolver.sh
|
COPY --chmod=755 deploy/entrypoint-resolver.sh /entrypoint-resolver.sh
|
||||||
COPY --from=build /src/dist /usr/share/nginx/html
|
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"]
|
ENTRYPOINT ["/entrypoint-resolver.sh"]
|
||||||
Reference in New Issue
Block a user