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
|
||||
.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
@@ -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/*
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
name: booklib
|
||||
services:
|
||||
postgres:
|
||||
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)。
|
||||
# 启动前取 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;'
|
||||
|
||||
@@ -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
|
||||
# 渲染 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)"
|
||||
|
||||
@@ -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/ ./
|
||||
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"]
|
||||
Reference in New Issue
Block a user