From a8f72af49cf39c84a46a1f998a54cef41c9629e3 Mon Sep 17 00:00:00 2001 From: wxy <3050128610@qq.com> Date: Thu, 21 May 2026 21:50:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + build-push.sh | 239 ++++++++++++++++++++++++++++++++++++ deploy.sh | 227 ++++++++++++++++++++++++++-------- docker-compose.registry.yml | 176 ++++++++++++++++++++++++++ docker-compose.yml | 8 +- mirror/README.md | 49 ++++++++ 6 files changed, 648 insertions(+), 55 deletions(-) create mode 100644 build-push.sh create mode 100644 docker-compose.registry.yml create mode 100644 mirror/README.md diff --git a/.gitignore b/.gitignore index ef57776..bdcb4de 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,10 @@ go.work.sum # env file .env +# 镜像导出目录 +mirror/*.tar +mirror/*.tar.gz + **/.DS_Store .vscode diff --git a/build-push.sh b/build-push.sh new file mode 100644 index 0000000..c515f8b --- /dev/null +++ b/build-push.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash +# ============================================ +# YouduWiki 镜像构建 & 导出脚本 +# 用法: +# ./build-push.sh # 只构建到本地 Docker +# ./build-push.sh --output ./images # 构建并导出为 tar 文件 +# ./build-push.sh --output ./images --compress # 导出并 gzip 压缩 +# ./build-push.sh --push registry.cn-hangzhou.aliyuncs.com/my-ns # 推送到远程仓库 +# ============================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +TAG="${TAG:-latest}" +REGISTRY="" +DO_PUSH=false +PLATFORM="${PLATFORM:-linux/amd64}" +OUTPUT_DIR="mirror" +DO_COMPRESS=false + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m' + +banner() { + echo -e "${CYAN}" + echo " ╔══════════════════════════════════════╗" + echo " ║ YouduWiki 镜像构建 & 导出 ║" + echo " ╚══════════════════════════════════════╝" + echo -e "${NC}" +} + +usage() { + echo " 用法: $0 [选项]" + echo "" + echo " 选项:" + echo " --compress 导出时 gzip 压缩 (约150MB, 适合网盘分享)" + echo " --push URL 推送到远程镜像仓库" + echo " --tag TAG 镜像标签 (默认: latest)" + echo " --platform ARCH 目标平台 (默认: linux/amd64)" + echo " --help 显示帮助" + echo "" + echo " Linux 构建命令:" + echo " # 标准构建 (导出到 mirror/ 目录, 4个tar文件共约300MB)" + echo " $0" + echo "" + echo " # 压缩构建 (导出 .tar.gz, 约150MB, 适合网盘分享)" + echo " $0 --compress" + echo "" + echo " # 指定版本号" + echo " $0 --tag v1.0.0" + echo "" + + echo -e " ${DIM}镜像预估大小:${NC}" + echo -e " ${DIM} youdu-wiki-api (Go + alpine) ~40MB${NC}" + echo -e " ${DIM} youdu-wiki-consumer (Go + alpine) ~40MB${NC}" + echo -e " ${DIM} youdu-wiki-admin (React + nginx) ~45MB${NC}" + echo -e " ${DIM} youdu-wiki-app (Next.js + node) ~180MB${NC}" + echo -e " ${DIM} ─────────────────────────────────────${NC}" + echo -e " ${DIM} 4个镜像 tar 合计 ~300MB, .tar.gz ~150MB${NC}" + echo "" +} + +# 解析参数 +while [[ $# -gt 0 ]]; do + case "$1" in + --output) OUTPUT_DIR="$2"; shift 2 ;; + --compress) DO_COMPRESS=true; shift ;; + --push) REGISTRY="$2"; DO_PUSH=true; shift 2 ;; + --tag) TAG="$2"; shift 2 ;; + --platform) PLATFORM="$2"; shift 2 ;; + --help|-h) banner; usage; exit 0 ;; + *) echo -e "${RED}未知选项: $1${NC}"; usage; exit 1 ;; + esac +done + +# 构建函数 +build_one() { + local dockerfile="$1" + local context="$2" + local image_name="$3" + local label="$4" + local full_image="${image_name}:${TAG}" + + echo "" + echo -e " ${BOLD}${CYAN}━━━ ${label} ━━━${NC}" + echo -e " ${DIM}Dockerfile: ${dockerfile}${NC}" + echo -e " ${DIM}镜像: ${full_image}${NC}" + + local build_start; build_start=$(date +%s) + + docker buildx build \ + --platform "${PLATFORM}" \ + --tag "${full_image}" \ + --file "${dockerfile}" \ + --progress=plain \ + "${context}" + + local elapsed=$(($(date +%s) - build_start)) + echo -e " ${GREEN}✓${NC} ${label} 构建完成 ${DIM}(${elapsed}s)${NC}" +} + +# 推送到远程仓库 +push_one() { + local image_name="$1" + local full_image="${image_name}:${TAG}" + + [ -z "$REGISTRY" ] && return + + local remote_image="${REGISTRY}/${image_name}:${TAG}" + echo -e " ${YELLOW}推送:${NC} ${full_image} → ${DIM}${remote_image}${NC}" + docker tag "${full_image}" "${remote_image}" + docker push "${remote_image}" + echo -e " ${GREEN}✓${NC} 推送完成" +} + +# 导出为 tar 文件 +export_one() { + local image_name="$1" + local full_image="${image_name}:${TAG}" + local tar_file="${OUTPUT_DIR}/${image_name}-${TAG}.tar" + + echo -e " ${YELLOW}导出:${NC} ${full_image} → ${DIM}${tar_file}${NC}" + docker save -o "${tar_file}" "${full_image}" + + local size; size=$(du -h "${tar_file}" | cut -f1) + echo -e " ${GREEN}✓${NC} 已导出 (${size})" + + if [ "$DO_COMPRESS" = true ]; then + echo -e " ${YELLOW}压缩:${NC} ${tar_file}.gz" + gzip -f "${tar_file}" + local csize; csize=$(du -h "${tar_file}.gz" | cut -f1) + echo -e " ${GREEN}✓${NC} 已压缩 (${csize})" + fi +} + +# 前置检查 +check_prerequisites() { + echo -e " ${DIM}确认构建环境...${NC}" + + if ! command -v docker &>/dev/null; then + echo -e "${RED}未找到 Docker${NC}"; exit 1 + fi + + docker buildx version &>/dev/null || { + echo -e "${RED}需要 Docker Buildx (Docker 19.03+)${NC}"; exit 1 + } + + if [ -n "$OUTPUT_DIR" ]; then + mkdir -p "$OUTPUT_DIR" + echo -e " ${DIM}输出目录: ${OUTPUT_DIR}${NC}" + fi + + if [ "$DO_PUSH" = true ]; then + local reg_host; reg_host=$(echo "$REGISTRY" | cut -d'/' -f1) + echo -e " ${YELLOW}确保已执行 docker login ${reg_host}${NC}" + read -rp " 已登录? [Y/n]: " confirm + [ "$confirm" = "n" ] || [ "$confirm" = "N" ] && { echo -e "${RED}请先登录${NC}"; exit 1; } + fi +} + +# 主流程 +main() { + banner + + echo -e " ${BOLD}构建配置:${NC}" + echo -e " 标签: ${GREEN}${TAG}${NC}" + echo -e " 平台: ${GREEN}${PLATFORM}${NC}" + if [ -n "$OUTPUT_DIR" ]; then + echo -e " 导出目录: ${GREEN}${OUTPUT_DIR}${NC}" + [ "$DO_COMPRESS" = true ] && echo -e " 压缩: ${GREEN}gzip${NC}" + fi + [ "$DO_PUSH" = true ] && echo -e " 推送目标: ${GREEN}${REGISTRY}${NC}" + + check_prerequisites + + local total_start; total_start=$(date +%s) + local IMAGES=( + "backend/Dockerfile.api|./backend|youdu-wiki-api|1/4 后端 API 镜像 (Go 编译)" + "backend/Dockerfile.consumer|./backend|youdu-wiki-consumer|2/4 后端 Consumer 镜像" + "web/Dockerfile.admin|./web|youdu-wiki-admin|3/4 管理后台镜像 (React)" + "web/Dockerfile.app|./web|youdu-wiki-app|4/4 Wiki 前端镜像 (Next.js)" + ) + + for entry in "${IMAGES[@]}"; do + IFS='|' read -r dockerfile context image_name label <<< "$entry" + build_one "$dockerfile" "$context" "$image_name" "$label" + [ "$DO_PUSH" = true ] && push_one "$image_name" + [ -n "$OUTPUT_DIR" ] && export_one "$image_name" + done + + local total_elapsed=$(($(date +%s) - total_start)) + + echo "" + echo -e " ${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e " ${GREEN}${BOLD} ✓ 全部完成${NC} ${DIM}总耗时: $((total_elapsed / 60))m$((total_elapsed % 60))s${NC}" + echo -e " ${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + + echo " 本地镜像:" + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" \ + | grep -E "youdu-wiki|REPOSITORY" \ + | while IFS= read -r line; do echo " $line"; done + + if [ -n "$OUTPUT_DIR" ]; then + echo "" + echo -e " ${GREEN}导出文件:${NC}" + + local ext="tar" + [ "$DO_COMPRESS" = true ] && ext="tar.gz" + + # 生成部署端使用说明 + cat > "${OUTPUT_DIR}/LOAD_README.txt" << READEOF +YouduWiki Docker 镜像 - ${TAG} +构建时间: $(date '+%Y-%m-%d %H:%M:%S') +平台: ${PLATFORM} + +部署端加载命令: + cd 此目录 + docker load -i youdu-wiki-api-${TAG}.${ext} + docker load -i youdu-wiki-consumer-${TAG}.${ext} + docker load -i youdu-wiki-admin-${TAG}.${ext} + docker load -i youdu-wiki-app-${TAG}.${ext} + +或使用一键部署脚本: + ./deploy.sh --load ./mirror +READEOF + + echo "" + ls -lh "${OUTPUT_DIR}/" | grep -E "youdu-wiki|LOAD" | while IFS= read -r line; do echo " $line"; done + echo "" + echo -e " ${YELLOW}将 ${OUTPUT_DIR}/ 整个目录复制到部署服务器,然后:${NC}" + echo -e " ${BOLD} ./deploy.sh --load ./mirror${NC}" + fi + echo "" +} + +main "$@" diff --git a/deploy.sh b/deploy.sh index 3880f2c..f498277 100644 --- a/deploy.sh +++ b/deploy.sh @@ -4,15 +4,21 @@ # 用法: ./deploy.sh [选项] # # 选项: -# --build 强制重新构建所有镜像 -# --skip-build 跳过镜像构建 (使用已有镜像) -# --pull 拉取最新的基础镜像 -# --clean 停止并清理所有容器和数据 (危险!) -# --stop 停止所有服务 -# --restart 重启所有服务 -# --logs 查看所有服务日志 -# --status 查看服务运行状态 -# --help 显示帮助信息 +# --registry URL 从镜像仓库拉取 (跳过源码构建, 2分钟部署) +# --tag TAG 指定镜像标签 (配合 --registry, 默认 latest) +# --build 强制重新构建所有镜像 +# --skip-build 跳过镜像构建 (使用已有镜像) +# --pull 拉取最新的基础镜像 +# --clean 停止并清理所有容器和数据 (危险!) +# --stop 停止所有服务 +# --restart 重启所有服务 +# --logs 查看所有服务日志 +# --status 查看服务运行状态 +# --help 显示帮助信息 +# +# 示例: +# ./deploy.sh # 源码构建部署 +# ./deploy.sh --registry registry.cn-hangzhou.aliyuncs.com/my-ns # 镜像部署 # ============================================ set -e @@ -22,6 +28,10 @@ cd "$SCRIPT_DIR" ENV_FILE=".env" COMPOSE_FILE="docker-compose.yml" +COMPOSE_FILE_REGISTRY="docker-compose.registry.yml" +USE_REGISTRY=false +REGISTRY_URL="" +IMAGE_TAG="${TAG:-latest}" ADMIN_PORT="${ADMIN_PORT:-2443}" APP_PORT="${APP_PORT:-3010}" @@ -479,6 +489,109 @@ pull_base_images() { "$(format_time $(($(date +%s) - STAGE_START)))" } +# ============================================ +# Registry 模式: 拉取预构建镜像 +# ============================================ + +pull_registry_images() { + next_stage + stage_header "从镜像仓库拉取预构建镜像" 120 + + COMPOSE_FILE="$COMPOSE_FILE_REGISTRY" + + echo -e " ${DIM}仓库: ${REGISTRY_URL}${NC}" + echo -e " ${DIM}标签: ${IMAGE_TAG}${NC}" + echo "" + + # 并行拉取 + local pulls=() + local images=( + "youdu-wiki-api" + "youdu-wiki-consumer" + "youdu-wiki-admin" + "youdu-wiki-app" + ) + + for img in "${images[@]}"; do + local full_img="${REGISTRY_URL}/${img}:${IMAGE_TAG}" + echo -e " ${DIM}拉取 ${full_img}${NC}" + docker pull "$full_img" & + pulls+=($!) + done + + # 等待全部拉取完成 + start_spinner "并行拉取 4 个镜像 ..." + for pid in "${pulls[@]}"; do + wait "$pid" 2>/dev/null + done + stop_spinner + + local elapsed=$(($(date +%s) - STAGE_START)) + clear_line + printf " ${GREEN}✓${NC} 镜像拉取完成 ${DIM}(耗时 %s)${NC}\n" "$(format_time $elapsed)" + + echo "" + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" \ + | grep -E "youdu-wiki|REPOSITORY" \ + | while IFS= read -r line; do echo " $line"; done +} + +# ============================================ +# 本地镜像文件加载 +# ============================================ + +load_local_images() { + local dir="$1" + local tag="${2:-latest}" + + next_stage + stage_header "从本地文件加载镜像" 60 + + if [ ! -d "$dir" ]; then + log_error "目录不存在: $dir" + exit 1 + fi + + echo -e " ${DIM}目录: ${dir}${NC}" + echo -e " ${DIM}标签: ${tag}${NC}" + echo "" + + local images=( + "youdu-wiki-api" + "youdu-wiki-consumer" + "youdu-wiki-admin" + "youdu-wiki-app" + ) + + for img in "${images[@]}"; do + local tar_file="${dir}/${img}-${tag}.tar" + local gz_file="${tar_file}.gz" + + if [ -f "$gz_file" ]; then + echo -e " ${YELLOW}加载 (gzip):${NC} ${gz_file}" + gunzip -c "$gz_file" | docker load + elif [ -f "$tar_file" ]; then + echo -e " ${YELLOW}加载:${NC} ${tar_file}" + docker load -i "$tar_file" + else + log_error "未找到: ${tar_file} 或 ${gz_file}" + echo " 请确认文件名格式: ${img}-${tag}.tar[.gz]" + exit 1 + fi + echo -e " ${GREEN}✓${NC} ${img} 加载完成" + echo "" + done + + local elapsed=$(($(date +%s) - STAGE_START)) + clear_line + printf " ${GREEN}✓${NC} 镜像加载完成 ${DIM}(耗时 %s)${NC}\n" "$(format_time $elapsed)" + + echo "" + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" \ + | grep -E "youdu-wiki|REPOSITORY" \ + | while IFS= read -r line; do echo " $line"; done +} + # ============================================ # 7. 启动基础设施 # ============================================ @@ -487,25 +600,24 @@ start_infra() { next_stage stage_header "启动基础设施 (PostgreSQL, Redis, NATS, MinIO)" 60 - # 启动基础服务 + local compose_cmd="docker compose -f $COMPOSE_FILE" + if [ "$USE_REGISTRY" = true ]; then + compose_cmd="REGISTRY=${REGISTRY_URL} TAG=${IMAGE_TAG} docker compose -f $COMPOSE_FILE" + fi + start_spinner "启动容器 ..." - docker compose -f "$COMPOSE_FILE" up -d postgres redis nats minio >/dev/null 2>&1 + eval "$compose_cmd up -d postgres redis nats minio" >/dev/null 2>&1 stop_spinner log_info "容器已创建" - # 等待 PostgreSQL 就绪 (带进度条) echo "" if wait_with_progress "PostgreSQL 启动" 60 \ "docker compose -f $COMPOSE_FILE ps postgres 2>/dev/null | grep -q healthy"; then log_info "PostgreSQL 就绪" else - log_error "PostgreSQL 启动超时" - echo "" - echo " 排查: docker compose logs postgres" - exit 1 + log_error "PostgreSQL 启动超时"; echo ""; echo " 排查: docker compose -f $COMPOSE_FILE logs postgres"; exit 1 fi - # 等待 Redis 就绪 if wait_with_progress "Redis 启动 " 30 \ "docker compose -f $COMPOSE_FILE exec -T redis redis-cli -a ${REDIS_PASSWORD:-ChangeMe123!} ping 2>/dev/null | grep -q PONG"; then log_info "Redis 就绪" @@ -526,22 +638,22 @@ start_app() { next_stage stage_header "启动业务服务 (API, Consumer, Admin, App)" 60 - # 启动业务容器 + local compose_cmd="docker compose -f $COMPOSE_FILE" + if [ "$USE_REGISTRY" = true ]; then + compose_cmd="REGISTRY=${REGISTRY_URL} TAG=${IMAGE_TAG} docker compose -f $COMPOSE_FILE" + fi + start_spinner "启动容器 ..." - docker compose -f "$COMPOSE_FILE" up -d api consumer admin app >/dev/null 2>&1 + eval "$compose_cmd up -d api consumer admin app" >/dev/null 2>&1 stop_spinner log_info "容器已创建" - # 等待 API (数据库迁移 + 启动) echo "" if wait_with_progress "API 服务启动 (含数据库迁移)" 45 \ "curl -sf http://localhost:${API_PORT}/api/v1/health 2>/dev/null"; then log_info "API 服务就绪" else - log_error "API 服务启动超时" - echo "" - echo " 排查: docker compose logs api | tail -30" - exit 1 + log_error "API 服务启动超时"; echo ""; echo " 排查: docker compose -f $COMPOSE_FILE logs api | tail -30"; exit 1 fi local elapsed=$(( $(date +%s) - STAGE_START )) @@ -659,36 +771,43 @@ clean_all() { main() { local DO_BUILD=true local DO_PULL=false + local LOAD_DIR="" while [[ $# -gt 0 ]]; do case "$1" in - --build) DO_BUILD=true; shift ;; - --skip-build) DO_BUILD=false; shift ;; - --pull) DO_PULL=true; shift ;; - --clean) banner; clean_all; exit 0 ;; - --stop) banner; stop_services; exit 0 ;; - --restart) banner; restart_services; show_info; exit 0 ;; - --logs) show_logs; exit 0 ;; - --status) banner; show_status; exit 0 ;; + --load) LOAD_DIR="${2:-mirror}"; DO_BUILD=false; [ $# -gt 1 ] && [[ "$2" != --* ]] && shift; shift ;; + --registry) USE_REGISTRY=true; REGISTRY_URL="$2"; DO_BUILD=false; COMPOSE_FILE="$COMPOSE_FILE_REGISTRY"; shift 2 ;; + --tag) IMAGE_TAG="$2"; shift 2 ;; + --build) DO_BUILD=true; shift ;; + --skip-build) DO_BUILD=false; shift ;; + --pull) DO_PULL=true; shift ;; + --clean) banner; clean_all; exit 0 ;; + --stop) banner; stop_services; exit 0 ;; + --restart) banner; restart_services; show_info; exit 0 ;; + --logs) show_logs; exit 0 ;; + --status) banner; show_status; exit 0 ;; --help|-h) banner echo " 用法: $0 [选项]" echo "" echo " 选项:" - echo " --build 强制重新构建所有镜像 (默认)" - echo " --skip-build 跳过镜像构建步骤" - echo " --pull 拉取最新的基础镜像" - echo " --clean 停止并清理所有容器和数据 (危险!)" - echo " --stop 停止所有服务" - echo " --restart 重启所有服务" - echo " --logs 查看所有服务日志" - echo " --status 查看服务运行状态" - echo " --help 显示此帮助信息" + echo " --load [DIR] 从本地目录加载镜像 tar (默认 ./mirror, 约1分钟)" + echo " --registry URL 从远程镜像仓库拉取 (约2分钟)" + echo " --tag TAG 指定镜像标签 (配合 --load/--registry)" + echo " --build 源码构建所有镜像 (首次 20-30min)" + echo " --skip-build 跳过镜像构建/加载步骤" + echo " --pull 拉取最新的基础镜像" + echo " --clean 停止并清理所有容器和数据 (危险!)" + echo " --stop 停止所有服务" + echo " --restart 重启所有服务" + echo " --logs 查看所有服务日志" + echo " --status 查看服务运行状态" + echo " --help 显示此帮助信息" echo "" - echo " 示例:" - echo " $0 # 完整部署 (构建 + 启动)" - echo " $0 --skip-build # 跳过构建,直接启动" - echo " $0 --restart # 重启已部署的服务" + echo " 三种部署方式 (由快到慢):" + echo " $0 --load ./images # 本地镜像文件 (最快)" + echo " $0 --registry registry.cn-hangzhou.aliyuncs.com/my-ns # 远程拉取" + echo " $0 # 源码构建 (慢)" exit 0 ;; *) log_error "未知选项: $1"; echo " 使用 --help 查看帮助"; exit 1 ;; @@ -709,8 +828,10 @@ main() { OVERALL_START=$(date +%s) - # 如果跳过构建,调整总阶段数 - if [ "$DO_BUILD" = false ]; then + # 根据模式调整阶段数 + if [ -n "$LOAD_DIR" ] || [ "$USE_REGISTRY" = true ]; then + TOTAL_STAGES=5 + elif [ "$DO_BUILD" = false ]; then TOTAL_STAGES=4 fi @@ -722,18 +843,22 @@ main() { pull_base_images fi - if [ "$DO_BUILD" = true ]; then + if [ -n "$LOAD_DIR" ]; then + load_local_images "$LOAD_DIR" "$IMAGE_TAG" + elif [ "$USE_REGISTRY" = true ]; then + pull_registry_images + elif [ "$DO_BUILD" = true ]; then build_images else next_stage - stage_header "跳过镜像构建" 1 + stage_header "跳过镜像步骤" 1 local missing=() for img in youdu-wiki-api youdu-wiki-consumer youdu-wiki-admin youdu-wiki-app; do - docker image inspect "${img}:latest" &>/dev/null || missing+=("$img") + docker image inspect "${img}:${IMAGE_TAG}" &>/dev/null || missing+=("$img") done if [ ${#missing[@]} -gt 0 ]; then log_error "缺少镜像: ${missing[*]}" - echo " 请先运行 ./deploy.sh (不带 --skip-build) 构建镜像" + echo " 请先构建: ./build-push.sh --output ./images" exit 1 fi log_info "所有镜像已存在" diff --git a/docker-compose.registry.yml b/docker-compose.registry.yml new file mode 100644 index 0000000..cc3c63a --- /dev/null +++ b/docker-compose.registry.yml @@ -0,0 +1,176 @@ +# ============================================ +# YouduWiki - 预构建镜像部署配置 +# 使用前请先构建并推送镜像到 Registry +# 参见: build-push.sh +# ============================================ + +version: '3.8' + +services: + # ============================================ + # 基础设施 + # ============================================ + + postgres: + image: postgres:16-alpine + container_name: youdu-wiki-postgres + restart: unless-stopped + environment: + POSTGRES_USER: youdu-wiki + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ChangeMe123!} + POSTGRES_DB: youdu-wiki + volumes: + - pg_data:/var/lib/postgresql/data + networks: + - youdu-wiki + healthcheck: + test: ["CMD-SHELL", "pg_isready -U youdu-wiki"] + interval: 5s + timeout: 5s + retries: 10 + + redis: + image: redis:7-alpine + container_name: youdu-wiki-redis + restart: unless-stopped + command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-ChangeMe123!} + volumes: + - redis_data:/data + networks: + - youdu-wiki + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-ChangeMe123!}", "ping"] + interval: 5s + timeout: 5s + retries: 10 + + nats: + image: nats:2-alpine + container_name: youdu-wiki-nats + restart: unless-stopped + command: > + -js + -m 8222 + --user ${NATS_USER:-youdu-wiki} + --pass ${NATS_PASSWORD:-ChangeMe123!} + networks: + - youdu-wiki + + minio: + image: minio/minio:latest + container_name: youdu-wiki-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${S3_ACCESS_KEY:-s3youdu-wiki} + MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:-ChangeMe123!} + volumes: + - minio_data:/data + networks: + - youdu-wiki + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 10 + + # ============================================ + # 后端服务 (从 Registry 拉取) + # ============================================ + + api: + image: ${REGISTRY:-your-registry}/youdu-wiki-api:${TAG:-latest} + container_name: youdu-wiki-api + restart: unless-stopped + environment: + PG_DSN: "host=youdu-wiki-postgres user=youdu-wiki password=${POSTGRES_PASSWORD:-ChangeMe123!} dbname=youdu-wiki port=5432 sslmode=disable TimeZone=Asia/Shanghai" + MQ_NATS_SERVER: "nats://youdu-wiki-nats:4222" + NATS_USER: ${NATS_USER:-youdu-wiki} + NATS_PASSWORD: ${NATS_PASSWORD:-ChangeMe123!} + REDIS_ADDR: "youdu-wiki-redis:6379" + REDIS_PASSWORD: ${REDIS_PASSWORD:-ChangeMe123!} + S3_ENDPOINT: "youdu-wiki-minio:9000" + S3_ACCESS_KEY: ${S3_ACCESS_KEY:-s3youdu-wiki} + S3_SECRET_KEY: ${S3_SECRET_KEY:-ChangeMe123!} + JWT_SECRET: ${JWT_SECRET:-youdu-wiki-jwt-secret-change-me} + ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123} + RAG_CT_RAG_BASE_URL: ${RAG_BASE_URL:-http://host.docker.internal:5050} + LOG_LEVEL: ${LOG_LEVEL:-0} + ENV: ${ENV:-production} + SENTRY_ENABLED: "false" + ports: + - "${API_PORT:-8000}:8000" + networks: + - youdu-wiki + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + minio: + condition: service_healthy + + consumer: + image: ${REGISTRY:-your-registry}/youdu-wiki-consumer:${TAG:-latest} + container_name: youdu-wiki-consumer + restart: unless-stopped + environment: + PG_DSN: "host=youdu-wiki-postgres user=youdu-wiki password=${POSTGRES_PASSWORD:-ChangeMe123!} dbname=youdu-wiki port=5432 sslmode=disable TimeZone=Asia/Shanghai" + MQ_NATS_SERVER: "nats://youdu-wiki-nats:4222" + NATS_USER: ${NATS_USER:-youdu-wiki} + NATS_PASSWORD: ${NATS_PASSWORD:-ChangeMe123!} + REDIS_ADDR: "youdu-wiki-redis:6379" + REDIS_PASSWORD: ${REDIS_PASSWORD:-ChangeMe123!} + S3_ENDPOINT: "youdu-wiki-minio:9000" + S3_ACCESS_KEY: ${S3_ACCESS_KEY:-s3youdu-wiki} + S3_SECRET_KEY: ${S3_SECRET_KEY:-ChangeMe123!} + JWT_SECRET: ${JWT_SECRET:-youdu-wiki-jwt-secret-change-me} + RAG_CT_RAG_BASE_URL: ${RAG_BASE_URL:-http://host.docker.internal:5050} + LOG_LEVEL: ${LOG_LEVEL:-0} + ENV: ${ENV:-production} + SENTRY_ENABLED: "false" + networks: + - youdu-wiki + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + # ============================================ + # 前端 (从 Registry 拉取) + # ============================================ + + admin: + image: ${REGISTRY:-your-registry}/youdu-wiki-admin:${TAG:-latest} + container_name: youdu-wiki-admin + restart: unless-stopped + ports: + - "${ADMIN_PORT:-2443}:8080" + networks: + - youdu-wiki + depends_on: + - api + + app: + image: ${REGISTRY:-your-registry}/youdu-wiki-app:${TAG:-latest} + container_name: youdu-wiki-app + restart: unless-stopped + ports: + - "${APP_PORT:-3010}:3010" + networks: + - youdu-wiki + depends_on: + - api + +volumes: + pg_data: + driver: local + redis_data: + driver: local + minio_data: + driver: local + +networks: + youdu-wiki: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 88b40d8..aa85f41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,7 +76,7 @@ services: build: context: ./backend dockerfile: Dockerfile.api - image: youdu-wiki-api:latest + image: youdu-wiki-api:${TAG:-latest} container_name: youdu-wiki-api restart: unless-stopped environment: @@ -111,7 +111,7 @@ services: build: context: ./backend dockerfile: Dockerfile.consumer - image: youdu-wiki-consumer:latest + image: youdu-wiki-consumer:${TAG:-latest} container_name: youdu-wiki-consumer restart: unless-stopped environment: @@ -146,7 +146,7 @@ services: build: context: ./web dockerfile: Dockerfile.admin - image: youdu-wiki-admin:latest + image: youdu-wiki-admin:${TAG:-latest} container_name: youdu-wiki-admin restart: unless-stopped ports: @@ -164,7 +164,7 @@ services: build: context: ./web dockerfile: Dockerfile.app - image: youdu-wiki-app:latest + image: youdu-wiki-app:${TAG:-latest} container_name: youdu-wiki-app restart: unless-stopped ports: diff --git a/mirror/README.md b/mirror/README.md new file mode 100644 index 0000000..8594f5a --- /dev/null +++ b/mirror/README.md @@ -0,0 +1,49 @@ +# YouduWiki Docker 镜像包 + +## 使用说明 + +将整个 `mirror/` 目录放到项目根目录下,然后运行: + +```bash +chmod +x deploy.sh +./deploy.sh --load ./mirror +``` + +脚本会自动加载 4 个镜像并启动全部服务。 + +## 手动加载 + +```bash +docker load -i mirror/youdu-wiki-api-latest.tar +docker load -i mirror/youdu-wiki-consumer-latest.tar +docker load -i mirror/youdu-wiki-admin-latest.tar +docker load -i mirror/youdu-wiki-app-latest.tar + +# 加载完成后启动 +docker compose up -d +``` + +## 更新到新版本 + +1. 下载新版 `mirror/` 目录 +2. 替换旧的 `mirror/` 目录 +3. 重新运行 `./deploy.sh --load ./mirror` + +数据(数据库、文件)存储在 Docker Volume 中,替换镜像不会丢失数据。 + +## 镜像内容 + +| 文件 | 说明 | 大小 | +|------|------|------| +| youdu-wiki-api-latest.tar | 后端 API 服务 | ~40MB | +| youdu-wiki-consumer-latest.tar | 异步任务处理 | ~40MB | +| youdu-wiki-admin-latest.tar | 管理后台 | ~45MB | +| youdu-wiki-app-latest.tar | Wiki 用户端 | ~180MB | + +## 系统要求 + +- Ubuntu 22.04 / Debian 12 / CentOS 7+ +- Docker 20.x+ +- CPU: 4 核以上 +- 内存: 8 GB 以上 +- 磁盘: 50 GB 以上