#!/bin/bash # 通用Docker镜像构建+推送脚本 # 缓存策略(2026-08 起): # - buildx 使用宿主机持久 builder (ci-builder-persist),层缓存保存在 # buildkit 容器/命名卷中,跨 job 共享、job 结束不清理 # - registry cache 仅作为冷启动兜底读取 # - 额外 tag(如分支 tag :develop)通过 EXTRA_TAGS 环境变量传入,随构建一并推送 # 用法: docker_build_push.sh [--no-cache] [build_arg...] # 环境变量: # EXTRA_TAGS 空格分隔的额外 tag(完整 image:tag 引用),可选 set -eu # 单次 build 超时时间(秒),防止 docker buildx build 无限挂起 BUILD_TIMEOUT=1500 # 持久 builder 名(宿主机级,所有 CI job 共享;由 ensure_persistent_builder.sh 维护) BUILDER_NAME="ci-builder-persist" NO_CACHE_FLAG="" if [ "$1" = "--no-cache" ]; then NO_CACHE_FLAG="--no-cache" shift echo "模式: --no-cache (不使用缓存,全新构建)" fi DOCKERFILE="$1" IMAGE_TAG="$2" CACHE_REF="$3" shift 3 BUILD_ARGS="" for arg in "$@"; do BUILD_ARGS="$BUILD_ARGS --build-arg $arg" done # Web 镜像 cache bust:计算 apps/web/ 的 git tree hash # 当源码变化时 hash 变化,buildx 的 ARG 缓存键失效 → vite build 必定重新执行 if [ "${DOCKERFILE##*/}" = "web.Dockerfile" ]; then SOURCE_HASH=$(git rev-parse HEAD:apps/web 2>/dev/null || echo "") if [ -n "$SOURCE_HASH" ]; then echo "Web cache bust: SOURCE_HASH=${SOURCE_HASH}" BUILD_ARGS="$BUILD_ARGS --build-arg SOURCE_HASH=${SOURCE_HASH}" else echo "⚠️ 无法计算 apps/web tree hash,跳过 cache bust" fi fi # 确保持久 builder 存在并使用(幂等) if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then echo "持久 builder 不存在,创建中..." docker buildx create --name "$BUILDER_NAME" --driver docker-container \ --driver-opt network=host \ --buildkitd-flags "--allow-insecure-entitlement network.host" \ --platform linux/amd64 fi docker buildx use "$BUILDER_NAME" docker buildx inspect "$BUILDER_NAME" --bootstrap # 组装额外 tag 参数 EXTRA_TAG_FLAGS="" EXTRA_TAG_LIST="" if [ -n "${EXTRA_TAGS:-}" ]; then for t in $EXTRA_TAGS; do EXTRA_TAG_FLAGS="$EXTRA_TAG_FLAGS -t $t" EXTRA_TAG_LIST="$EXTRA_TAG_LIST $t" done fi build_with_cache_retry() { local attempt=1 local max_attempts=2 while [ $attempt -le $max_attempts ]; do local build_output local exit_code set +e build_output=$(timeout ${BUILD_TIMEOUT} docker buildx build \ $NO_CACHE_FLAG \ $BUILD_ARGS \ --cache-from "type=registry,ref=${CACHE_REF}" \ -f "${DOCKERFILE}" \ -t "${IMAGE_TAG}" \ $EXTRA_TAG_FLAGS \ --push \ . 2>&1) exit_code=$? set -e if [ $exit_code -eq 0 ]; then echo "$build_output" return 0 fi # 超时退出(exit code 124) if [ $exit_code -eq 124 ]; then echo "❌ Docker build TIMEOUT after ${BUILD_TIMEOUT}s - build hung and was killed" echo "$build_output" | tail -20 return $exit_code fi # 检测到缓存/快照损坏类错误,重建 builder 后重试 if echo "$build_output" | grep -qE "parent snapshot.*not found|snapshot.*does not exist|cache.*corrupt|failed to compute cache key|no such file or directory.*cache"; then echo "$build_output" echo "" echo "⚠️ builder 缓存异常,重建持久 builder 后重试 (attempt $attempt/$max_attempts)..." # 共享 builder 的重建必须串行:ci-builder-persist 被所有 build job 共用, # 若 job A 正在构建、job B 检测到损坏直接 rm,会把 A 正在用的 buildkit 杀掉。 # 用 flock 串行化重建;拿到锁后再次检查 builder 健康度,已被别的 job 重建则直接复用。 LOCK_FILE="/tmp/ci-builder-persist-rebuild.lock" exec 9>"$LOCK_FILE" echo "🔒 等待重建锁(最多 120s)..." if flock -w 120 9; then if docker buildx inspect "$BUILDER_NAME" --bootstrap >/dev/null 2>&1; then echo "✅ builder 已被其他并发 job 重建/恢复,直接复用" else echo "🔨 锁内重建持久 builder..." docker buildx rm "$BUILDER_NAME" >/dev/null 2>&1 || true docker buildx create --name "$BUILDER_NAME" --driver docker-container \ --driver-opt network=host \ --buildkitd-flags "--allow-insecure-entitlement network.host" \ --platform linux/amd64 docker buildx use "$BUILDER_NAME" docker buildx inspect "$BUILDER_NAME" --bootstrap fi else echo "⚠️ 等待重建锁超时,直接重试 build(失败将重试/--no-cache)" docker buildx use "$BUILDER_NAME" 2>/dev/null || true fi attempt=$((attempt + 1)) else echo "$build_output" return $exit_code fi done return 1 } echo "=== Build & push image (persistent builder cache) ===" echo "Builder: ${BUILDER_NAME} (persistent)" echo "Registry cache(from): ${CACHE_REF}" echo "Image tag: ${IMAGE_TAG}" [ -n "$EXTRA_TAG_LIST" ] && echo "Extra tags: ${EXTRA_TAG_LIST}" echo "Timeout: ${BUILD_TIMEOUT}s" echo "" build_with_cache_retry echo "" echo "Image pushed: ${IMAGE_TAG}" [ -n "$EXTRA_TAG_LIST" ] && echo "Also pushed: ${EXTRA_TAG_LIST}" echo "" echo "Build completed: ${IMAGE_TAG}"