优化 Docker 开发启动流程

This commit is contained in:
yml2213
2026-07-31 16:27:51 +08:00
parent 30362f14bd
commit a091806283
5 changed files with 86 additions and 45 deletions
+5
View File
@@ -4,8 +4,13 @@ DATA_ENCRYPTION_KEY=your_32_plus_char_data_encryption_key_change_me
POSTGRES_USER=affiliate POSTGRES_USER=affiliate
POSTGRES_PASSWORD=affiliate_dev_password POSTGRES_PASSWORD=affiliate_dev_password
POSTGRES_DB=affiliate_dash POSTGRES_DB=affiliate_dash
POSTGRES_PORT=5432
COMPOSE_PROJECT_NAME=affiliate_dash
POSTGRES_DATA_VOLUME=affiliate_dash_postgres_data
TZ=Asia/Shanghai TZ=Asia/Shanghai
DATABASE_URL="postgres://affiliate:affiliate_dev_password@127.0.0.1:5432/affiliate_dash?sslmode=disable&TimeZone=Asia/Shanghai" DATABASE_URL="postgres://affiliate:affiliate_dev_password@127.0.0.1:5432/affiliate_dash?sslmode=disable&TimeZone=Asia/Shanghai"
# Docker 后端容器内连接串通常不需要改;需要外部数据库时再覆盖。
# DOCKER_DATABASE_URL="postgres://affiliate:affiliate_dev_password@postgres:5432/affiliate_dash?sslmode=disable&TimeZone=Asia/Shanghai"
GIN_MODE=release GIN_MODE=release
OPEN_API_KEY=sk_source_dev_key_change_me OPEN_API_KEY=sk_source_dev_key_change_me
OPEN_API_SECRET=sk_source_dev_secret_change_me OPEN_API_SECRET=sk_source_dev_secret_change_me
+9 -1
View File
@@ -3,7 +3,7 @@
# admin off # admin off
} }
skin.khhao.com { (app_routes) {
encode gzip zstd encode gzip zstd
# /api-debug 是前端 SPA 路由,不要代理到后端 # /api-debug 是前端 SPA 路由,不要代理到后端
@@ -25,3 +25,11 @@ skin.khhao.com {
file_server file_server
} }
} }
:80 {
import app_routes
}
skin.khhao.com {
import app_routes
}
+11 -3
View File
@@ -18,7 +18,7 @@
- Go 1.26+ - Go 1.26+
- Node.js 22+ - Node.js 22+
- Docker(用于自动启动 PostgreSQL 开发容器 - Docker / Docker Compose(用于自动启动 PostgreSQL 开发服务
### 快速启动 ### 快速启动
@@ -33,6 +33,8 @@ make dev
./start.sh ./start.sh
``` ```
`make dev` 会通过 Docker Compose 启动 `affiliate_dash/postgres`,后端和前端仍在宿主机运行,方便热更新。首次从旧版 `affiliate_dash_postgres_dev` 开发容器迁移时,脚本会停止旧容器并优先复用旧数据卷。
启动后: 启动后:
- 前端:`http://localhost:5173` - 前端:`http://localhost:5173`
- 后端:`http://localhost:8080` - 后端:`http://localhost:8080`
@@ -61,10 +63,11 @@ cp .env.example .env
# JWT_SECRET — 生产环境务必修改 # JWT_SECRET — 生产环境务必修改
# OPEN_API_KEY — 皮肤源头对接 API Key # OPEN_API_KEY — 皮肤源头对接 API Key
# OPEN_API_SECRET — 签名密钥 # OPEN_API_SECRET — 签名密钥
# DATABASE_URL — PostgreSQL 连接,容器内主机名为 postgres # POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB — PostgreSQL 初始化配置
``` ```
> 国内服务器可在 `.env` 中取消 `GOPROXY`、`ALPINE_MIRROR`、`NPM_REGISTRY` 等镜像加速源的注释。 > 国内服务器可在 `.env` 中取消 `GOPROXY`、`ALPINE_MIRROR`、`NPM_REGISTRY` 等镜像加速源的注释。
> Docker 后端容器默认使用 `postgres` 作为数据库主机;只有连接外部数据库时才需要设置 `DOCKER_DATABASE_URL`。
### 启动服务 ### 启动服务
@@ -78,6 +81,7 @@ docker compose up -d --build
服务端口: 服务端口:
- `:80` — 前端页面 + API(Caddy 统一入口) - `:80` — 前端页面 + API(Caddy 统一入口)
- `:8080` — 后端 API(直接访问,调试用) - `:8080` — 后端 API(直接访问,调试用)
- `:5432` — PostgreSQL(本地开发/调试用,可通过 `POSTGRES_PORT` 调整)
### 常用命令 ### 常用命令
@@ -89,7 +93,7 @@ make docker-down # 停止并移除
### 自定义域名 ### 自定义域名
修改 `Caddyfile` 中的域名 `skin.khhao.com` 为你的域名,重新构建即可。 `Caddyfile` 默认同时支持本地 `:80` 和示例域名 `skin.khhao.com`。生产部署时把 `skin.khhao.com`为你的域名,重新构建即可。
## 环境变量 ## 环境变量
@@ -98,6 +102,10 @@ make docker-down # 停止并移除
| `PORT` | 后端端口 | `8080` | | `PORT` | 后端端口 | `8080` |
| `JWT_SECRET` | JWT 签名密钥 | 开发默认值 | | `JWT_SECRET` | JWT 签名密钥 | 开发默认值 |
| `DATABASE_URL` | PostgreSQL 连接串 | 本地开发默认值 | | `DATABASE_URL` | PostgreSQL 连接串 | 本地开发默认值 |
| `DOCKER_DATABASE_URL` | Docker 后端容器内 PostgreSQL 连接串,通常无需设置 | Compose 内置默认值 |
| `POSTGRES_PORT` | PostgreSQL 映射到宿主机的端口 | `5432` |
| `COMPOSE_PROJECT_NAME` | Docker Compose 项目名 | `affiliate_dash` |
| `POSTGRES_DATA_VOLUME` | PostgreSQL 数据卷名 | `affiliate_dash_postgres_data` |
| `GIN_MODE` | Gin 运行模式 | `debug` | | `GIN_MODE` | Gin 运行模式 | `debug` |
| `OPEN_API_KEY` | 开放接口 ApiKey | 需修改 | | `OPEN_API_KEY` | 开放接口 ApiKey | 需修改 |
| `OPEN_API_SECRET` | 开放接口签名密钥 | 需修改 | | `OPEN_API_SECRET` | 开放接口签名密钥 | 需修改 |
+6 -1
View File
@@ -1,3 +1,5 @@
name: ${COMPOSE_PROJECT_NAME:-affiliate_dash}
services: services:
postgres: postgres:
image: ${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine} image: ${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}
@@ -8,6 +10,8 @@ services:
- TZ=${TZ:-Asia/Shanghai} - TZ=${TZ:-Asia/Shanghai}
- PGTZ=${TZ:-Asia/Shanghai} - PGTZ=${TZ:-Asia/Shanghai}
command: ["postgres", "-c", "timezone=${TZ:-Asia/Shanghai}"] command: ["postgres", "-c", "timezone=${TZ:-Asia/Shanghai}"]
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes: volumes:
- postgres-data:/var/lib/postgresql/data - postgres-data:/var/lib/postgresql/data
restart: unless-stopped restart: unless-stopped
@@ -34,7 +38,7 @@ services:
- PORT=8080 - PORT=8080
- TZ=${TZ:-Asia/Shanghai} - TZ=${TZ:-Asia/Shanghai}
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
- "DATABASE_URL=postgres://${POSTGRES_USER:-affiliate}:${POSTGRES_PASSWORD:-affiliate_dev_password}@postgres:5432/${POSTGRES_DB:-affiliate_dash}?sslmode=disable&TimeZone=${TZ:-Asia/Shanghai}" - "DATABASE_URL=${DOCKER_DATABASE_URL:-postgres://${POSTGRES_USER:-affiliate}:${POSTGRES_PASSWORD:-affiliate_dev_password}@postgres:5432/${POSTGRES_DB:-affiliate_dash}?sslmode=disable&TimeZone=${TZ:-Asia/Shanghai}}"
- GIN_MODE=${GIN_MODE:-release} - GIN_MODE=${GIN_MODE:-release}
- OPEN_API_KEY=${OPEN_API_KEY} - OPEN_API_KEY=${OPEN_API_KEY}
- OPEN_API_SECRET=${OPEN_API_SECRET} - OPEN_API_SECRET=${OPEN_API_SECRET}
@@ -79,6 +83,7 @@ services:
volumes: volumes:
postgres-data: postgres-data:
name: ${POSTGRES_DATA_VOLUME:-affiliate_dash_postgres_data}
backend-data: backend-data:
caddy-data: caddy-data:
caddy-config: caddy-config:
+55 -40
View File
@@ -25,11 +25,15 @@ POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-affiliate_dev_password}"
POSTGRES_DB="${POSTGRES_DB:-affiliate_dash}" POSTGRES_DB="${POSTGRES_DB:-affiliate_dash}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}" POSTGRES_PORT="${POSTGRES_PORT:-5432}"
POSTGRES_IMAGE="${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}" POSTGRES_IMAGE="${POSTGRES_IMAGE:-docker.m.daocloud.io/library/postgres:16-alpine}"
DEV_DB_CONTAINER="${DEV_DB_CONTAINER:-affiliate_dash_postgres_dev}" COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-affiliate_dash}"
POSTGRES_DATA_VOLUME="${POSTGRES_DATA_VOLUME:-affiliate_dash_postgres_data}"
LEGACY_DEV_DB_CONTAINER="${DEV_DB_CONTAINER:-affiliate_dash_postgres_dev}"
TZ="${TZ:-Asia/Shanghai}" TZ="${TZ:-Asia/Shanghai}"
DATABASE_URL="${DATABASE_URL:-postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable&TimeZone=${TZ}}" DATABASE_URL="${DATABASE_URL:-postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable&TimeZone=${TZ}}"
export DATABASE_URL export DATABASE_URL
export TZ export TZ
export COMPOSE_PROJECT_NAME
export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB POSTGRES_PORT POSTGRES_IMAGE POSTGRES_DATA_VOLUME
# 结束指定端口上的进程(避免残留占用) # 结束指定端口上的进程(避免残留占用)
free_port() { free_port() {
@@ -49,64 +53,75 @@ free_port() {
fi fi
} }
postgres_container_exists() { docker_compose_available() {
docker ps -a --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER" command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1
} }
postgres_container_running() { postgres_port_reachable() {
docker ps --format '{{.Names}}' | grep -qx "$DEV_DB_CONTAINER" (echo >"/dev/tcp/127.0.0.1/${POSTGRES_PORT}") >/dev/null 2>&1
} }
postgres_container_timezone_ready() { legacy_postgres_container_exists() {
local env cmd docker ps -a --format '{{.Names}}' | grep -qx "$LEGACY_DEV_DB_CONTAINER"
env="$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$DEV_DB_CONTAINER" 2>/dev/null || true)" }
cmd="$(docker inspect -f '{{json .Config.Cmd}}' "$DEV_DB_CONTAINER" 2>/dev/null || true)"
grep -qx "TZ=${TZ}" <<<"$env" && grep -qx "PGTZ=${TZ}" <<<"$env" && [[ "$cmd" == *"timezone=${TZ}"* ]] legacy_postgres_container_running() {
docker ps --format '{{.Names}}' | grep -qx "$LEGACY_DEV_DB_CONTAINER"
}
compose_postgres_running() {
docker compose ps --services --status running 2>/dev/null | grep -qx "postgres"
}
prepare_postgres_volume() {
local legacy_volume="${LEGACY_DEV_DB_CONTAINER}-data"
if [[ "${POSTGRES_DATA_VOLUME}" == "affiliate_dash_postgres_data" ]] && docker volume inspect "$legacy_volume" >/dev/null 2>&1; then
POSTGRES_DATA_VOLUME="$legacy_volume"
export POSTGRES_DATA_VOLUME
log "检测到旧开发库数据卷,Compose 将继续复用: ${POSTGRES_DATA_VOLUME}"
fi
}
stop_legacy_postgres_container() {
if legacy_postgres_container_exists; then
if legacy_postgres_container_running; then
log "检测到旧 PostgreSQL 开发容器,停止后交由 Docker Compose 管理: ${LEGACY_DEV_DB_CONTAINER}"
docker stop "$LEGACY_DEV_DB_CONTAINER" >/dev/null
else
log "检测到旧 PostgreSQL 开发容器已停止,保留容器和数据卷: ${LEGACY_DEV_DB_CONTAINER}"
fi
fi
} }
ensure_postgres() { ensure_postgres() {
if command -v docker >/dev/null 2>&1 && postgres_container_exists; then if ! docker_compose_available; then
if ! postgres_container_timezone_ready; then log "未找到 Docker Compose,请确认本机 PostgreSQL 已可通过 DATABASE_URL 访问"
log "检测到旧 PostgreSQL 开发容器缺少时区配置,重建容器并保留数据卷: ${DEV_DB_CONTAINER}"
docker rm -f "$DEV_DB_CONTAINER" >/dev/null
fi
elif (echo >"/dev/tcp/127.0.0.1/${POSTGRES_PORT}") >/dev/null 2>&1; then
log "检测到本机 PostgreSQL 端口已可访问: ${POSTGRES_PORT}"
return 0 return 0
fi fi
if ! command -v docker >/dev/null 2>&1; then
log "未找到 Docker,请确认本机 PostgreSQL 已可通过 DATABASE_URL 访问" prepare_postgres_volume
return 0 stop_legacy_postgres_container
fi
if postgres_container_running; then if compose_postgres_running; then
log "PostgreSQL 开发容器已运行: ${DEV_DB_CONTAINER}" log "PostgreSQL Compose 服务已运行: ${COMPOSE_PROJECT_NAME}/postgres"
elif postgres_container_exists; then
log "启动 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}"
docker start "$DEV_DB_CONTAINER" >/dev/null
else else
log "创建 PostgreSQL 开发容器: ${DEV_DB_CONTAINER}" if postgres_port_reachable; then
docker run -d \ log "检测到本机 PostgreSQL 端口已可访问: ${POSTGRES_PORT},跳过 Compose 数据库启动"
--name "$DEV_DB_CONTAINER" \ return 0
-p "${POSTGRES_PORT}:5432" \ fi
-e POSTGRES_USER="$POSTGRES_USER" \ log "启动 PostgreSQL Compose 服务: ${COMPOSE_PROJECT_NAME}/postgres"
-e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ docker compose up -d postgres >/dev/null
-e POSTGRES_DB="$POSTGRES_DB" \
-e TZ="$TZ" \
-e PGTZ="$TZ" \
-v "${DEV_DB_CONTAINER}-data:/var/lib/postgresql/data" \
"$POSTGRES_IMAGE" \
-c timezone="$TZ" >/dev/null
fi fi
for i in $(seq 1 40); do for i in $(seq 1 40); do
if docker exec "$DEV_DB_CONTAINER" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then if docker compose exec -T postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then
log "PostgreSQL 已就绪" log "PostgreSQL 已就绪"
return 0 return 0
fi fi
sleep 0.5 sleep 0.5
done done
echo "错误: PostgreSQL 启动超时" echo "错误: PostgreSQL 启动超时"
docker logs "$DEV_DB_CONTAINER" 2>/dev/null || true docker compose logs postgres 2>/dev/null || true
exit 1 exit 1
} }