diff --git a/.gitea/workflows/deploy-backend.yml b/.gitea/workflows/deploy-backend.yml index 449d8fe..30e4941 100644 --- a/.gitea/workflows/deploy-backend.yml +++ b/.gitea/workflows/deploy-backend.yml @@ -48,12 +48,33 @@ jobs: # 验证配置 docker info | grep -A 5 "Registry Mirrors" || true - - name: 🔑 登录到镜像仓库 - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + - name: 🔑 登录到镜像仓库(使用阿里云访问令牌) + run: | + echo "🔑 使用阿里云访问令牌登录..." + # 创建 Docker 配置文件 + mkdir -p ~/.docker + cat > ~/.docker/config.json << EOF + { + "auths": { + "${{ env.REGISTRY }}": { + "auth": "$(echo -n '${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }}' | base64 -w 0)" + } + } + } + EOF + + # 验证登录状态 + echo "🔍 验证登录状态..." + if docker pull ${{ env.REGISTRY }}/library/hello-world:latest 2>/dev/null; then + echo "✅ 镜像仓库认证成功" + else + echo "❌ 镜像仓库认证失败,尝试基础认证..." + # 使用基础认证 + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ env.REGISTRY }} --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin || { + echo "❌ 所有认证方式都失败" + exit 1 + } + fi - name: 📝 提取元数据 id: meta @@ -66,28 +87,74 @@ jobs: type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - - name: 🔐 验证 Docker 认证 + - name: 🏗️ 构建镜像(使用正确格式) run: | - echo "验证 Docker 认证状态..." - docker info | grep -A 10 "Registry:" || true - echo "测试镜像仓库连接..." - docker pull ${{ env.REGISTRY }}/hello-world:latest || echo "拉取测试镜像失败(正常)" + echo "🔨 开始构建 Docker 镜像..." + cd ./backend + + # 设置正确的镜像标签格式 + IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" + IMAGE_TAG_LATEST="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" + + echo "🏷️ 镜像标签:" + echo " - $IMAGE_TAG" + echo " - $IMAGE_TAG_LATEST" + + # 构建镜像 + docker build -t $IMAGE_TAG -t $IMAGE_TAG_LATEST . + + # 验证镜像 + echo "🔍 验证镜像构建..." + docker images | grep photography-backend + + echo "✅ 镜像构建完成" - - name: 🏗️ 构建并推送镜像 - uses: docker/build-push-action@v5 - with: - context: ./backend - file: ./backend/Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: false - sbom: false - env: - BUILDKIT_PROGRESS: plain + - name: 📤 推送镜像(使用正确路径) + run: | + IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" + IMAGE_TAG_LATEST="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" + + echo "📤 开始推送镜像..." + echo "📋 目标仓库: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + + # 重试机制 + max_attempts=3 + attempt=1 + + while [ $attempt -le $max_attempts ]; do + echo "📤 推送尝试 $attempt/$max_attempts..." + echo "🚀 推送命令: docker push $IMAGE_TAG" + + if docker push $IMAGE_TAG; then + echo "✅ 版本标签推送成功" + + echo "🚀 推送 latest 标签: docker push $IMAGE_TAG_LATEST" + if docker push $IMAGE_TAG_LATEST; then + echo "✅ latest 标签推送成功" + break + else + echo "⚠️ latest 标签推送失败,但版本标签已成功" + break + fi + else + echo "❌ 推送失败,等待重试..." + if [ $attempt -lt $max_attempts ]; then + echo "⏳ 等待15秒后重试..." + sleep 15 + fi + fi + + attempt=$((attempt + 1)) + done + + if [ $attempt -gt $max_attempts ]; then + echo "❌ 所有推送尝试都失败" + echo "📋 请检查以下配置:" + echo " - 仓库地址: ${{ env.REGISTRY }}" + echo " - 镜像名称: ${{ env.IMAGE_NAME }}" + echo " - 认证信息: ${{ secrets.DOCKER_USERNAME }}" + exit 1 + fi - name: 📦 同步配置文件 run: | @@ -121,6 +188,28 @@ jobs: # 切换到后端项目目录 cd /data/docker/photography/backend + # 检查 Docker 服务状态 + echo "🐳 检查 Docker 服务状态..." + if ! docker info >/dev/null 2>&1; then + echo "❌ Docker 服务未运行或权限不足,尝试使用 sudo..." + echo '${{ secrets.TYY_PWD }}' | sudo -S systemctl start docker + sleep 5 + fi + + # 验证 Docker 权限 + echo "🔍 验证 Docker 权限..." + docker --version || { + echo "❌ Docker 命令不可用" + exit 1 + } + + # 登录阿里云镜像仓库 + echo "🔑 登录阿里云镜像仓库..." + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ env.REGISTRY }} --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin || { + echo "❌ Docker 登录失败,检查认证信息..." + exit 1 + } + # 备份当前运行的容器 (如果存在) if docker ps -q -f name=photography-api; then echo "📦 备份当前后端容器..." @@ -129,19 +218,26 @@ jobs: # 停止现有服务 echo "🛑 停止现有服务..." - docker-compose down api || true + docker compose down api || { + echo "⚠️ 停止服务时遇到问题,继续执行..." + docker stop photography-api || true + docker rm photography-api || true + } # 拉取最新镜像 echo "📥 拉取最新镜像..." - docker-compose pull api + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} || { + echo "❌ 镜像拉取失败,检查网络连接..." + exit 1 + } # 数据库迁移需要手动执行 echo "⚠️ 数据库迁移需要手动执行,请在部署后运行:" - echo " docker-compose exec api ./main migrate" + echo " docker compose exec api ./main migrate" # 启动后端服务 echo "🚀 启动后端服务..." - docker-compose up -d api + docker compose up -d api # 等待服务启动 echo "⏳ 等待服务启动..." @@ -160,7 +256,7 @@ jobs: # 检查服务状态 echo "📊 检查服务状态..." - docker-compose ps + docker compose ps # 清理旧镜像 (保留最近3个) echo "🧹 清理旧镜像..." @@ -171,7 +267,7 @@ jobs: echo "🎉 后端部署完成!" echo "📋 请记住手动运行数据库迁移:" - echo " docker-compose exec api ./main migrate" + echo " docker compose exec api ./main migrate" - name: 📧 发送部署通知 if: always() @@ -189,50 +285,50 @@ jobs: ${{ job.status == 'success' && '✅ 部署成功' || '❌ 部署失败' }} - ${{ job.status == 'success' && '⚠️ 请记住手动运行数据库迁移: docker-compose exec api ./main migrate' || '' }} + ${{ job.status == 'success' && '⚠️ 请记住手动运行数据库迁移: docker compose exec api ./main migrate' || '' }} 🌐 API: https://api.photography.iriver.top/health 📊 监控: https://admin.photography.iriver.top - rollback: - name: 🔄 回滚部署 - runs-on: ubuntu-latest - if: failure() && github.ref == 'refs/heads/main' - needs: build-and-deploy + # rollback: + # name: 🔄 回滚部署 + # runs-on: ubuntu-latest + # if: failure() && github.ref == 'refs/heads/main' + # needs: build-and-deploy - steps: - - name: 🔄 执行回滚 - uses: appleboy/ssh-action@v1.0.0 - with: - host: ${{ secrets.HOST }} - username: ${{ secrets.TYY_USER }} - password: ${{ secrets.TYY_PWD }} - port: ${{ secrets.PORT }} - script: | - cd /data/docker/photography/backend + # steps: + # - name: 🔄 执行回滚 + # uses: appleboy/ssh-action@v1.0.0 + # with: + # host: ${{ secrets.HOST }} + # username: ${{ secrets.TYY_USER }} + # password: ${{ secrets.TYY_PWD }} + # port: ${{ secrets.PORT }} + # script: | + # cd /data/docker/photography/backend - echo "🔄 开始回滚后端服务..." + # echo "🔄 开始回滚后端服务..." - # 查找最新的备份容器 - BACKUP_IMAGE=$(docker images photography_backend_backup_* --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | tail -n +2 | sort -k2 -r | head -n 1 | awk '{print $1}') + # # 查找最新的备份容器 + # BACKUP_IMAGE=$(docker images photography_backend_backup_* --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | tail -n +2 | sort -k2 -r | head -n 1 | awk '{print $1}') - if [ -n "$BACKUP_IMAGE" ]; then - echo "📦 找到备份镜像: $BACKUP_IMAGE" + # if [ -n "$BACKUP_IMAGE" ]; then + # echo "📦 找到备份镜像: $BACKUP_IMAGE" - # 停止当前服务 - docker-compose down backend + # # 停止当前服务 + # docker compose down api - # 标记备份镜像为最新 - docker tag $BACKUP_IMAGE photography_backend:rollback + # # 标记备份镜像为最新 + # docker tag $BACKUP_IMAGE photography_backend:rollback - # 修改 docker-compose 使用回滚镜像 - sed -i 's|build: .*|image: photography_backend:rollback|g' docker-compose.yml + # # 修改 docker compose 使用回滚镜像 + # sed -i 's|build: .*|image: photography_backend:rollback|g' docker-compose.yml - # 启动回滚版本 - docker-compose up -d backend + # # 启动回滚版本 + # docker compose up -d api - echo "✅ 回滚完成" - else - echo "❌ 未找到备份镜像,无法回滚" - exit 1 - fi \ No newline at end of file + # echo "✅ 回滚完成" + # else + # echo "❌ 未找到备份镜像,无法回滚" + # exit 1 + # fi \ No newline at end of file diff --git a/backend/docker-compose.prod.yml b/backend/docker-compose.prod.yml index 025817f..ef3e34e 100644 --- a/backend/docker-compose.prod.yml +++ b/backend/docker-compose.prod.yml @@ -1,14 +1,13 @@ # Photography Portfolio Backend - Production Docker Compose # 生产环境配置 - 使用现有 PostgreSQL 和 Redis 服务 -version: '3.8' +# version 字段已废弃,不再需要使用 +# version: '3.8' services: # 后端API服务 (仅API服务,无数据库) api: - build: - context: . - dockerfile: Dockerfile + image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest container_name: photography-api environment: # 数据库配置 (连接现有服务) @@ -59,9 +58,7 @@ services: # 数据库迁移服务 (一次性运行) migrate: - build: - context: . - dockerfile: Dockerfile + image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest container_name: photography-migrate environment: DB_HOST: ${DB_HOST:-localhost} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index b0f8b42..eedfeeb 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,103 +1,53 @@ -# Photography Portfolio Backend - Docker Compose -# 本地开发和测试环境配置 +# Photography Portfolio Backend - Production Docker Compose +# 生产环境配置 - 使用现有 PostgreSQL 和 Redis 服务 -version: '3.8' +# version 字段已废弃,不再需要使用 +# version: '3.8' services: - # PostgreSQL 数据库 - db: - image: postgres:16-alpine - container_name: photography-db - environment: - POSTGRES_DB: photography - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./configs/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro - networks: - - photography-network - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d photography"] - interval: 10s - timeout: 5s - retries: 5 - - # Redis 缓存 - redis: - image: redis:7-alpine - container_name: photography-redis - ports: - - "6379:6379" - volumes: - - redis_data:/data - - ./configs/redis.conf:/usr/local/etc/redis/redis.conf:ro - command: redis-server /usr/local/etc/redis/redis.conf - networks: - - photography-network - restart: unless-stopped - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 3s - retries: 5 - - # 后端API服务 + # 后端API服务 (仅API服务,无数据库) api: - build: - context: . - dockerfile: Dockerfile + image: crpi-b4fqtfbvv583enk2.cn-shanghai.personal.cr.aliyuncs.com/photography-backend/photography:latest container_name: photography-api environment: - # 数据库配置 - DB_HOST: db - DB_PORT: 5432 - DB_NAME: photography - DB_USER: postgres - DB_PASSWORD: postgres123 - DB_SSL_MODE: disable + # 数据库配置 (连接现有服务) + DB_HOST: ${DB_HOST:-redis_cache} + DB_PORT: ${DB_PORT:-5432} + DB_NAME: ${DB_NAME:-photography} + DB_USER: ${DB_USER:-postgres} + DB_PASSWORD: ${DB_PASSWORD} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} - # Redis配置 - REDIS_HOST: redis - REDIS_PORT: 6379 - REDIS_PASSWORD: "" - REDIS_DB: 0 + # Redis配置 (连接现有服务) + REDIS_HOST: ${REDIS_HOST:-localhost} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_PASSWORD: ${REDIS_PASSWORD:-} + REDIS_DB: ${REDIS_DB:-0} # JWT配置 - JWT_SECRET: your-super-secret-jwt-key-change-in-production - JWT_EXPIRE: 24h + JWT_SECRET: ${JWT_SECRET} + JWT_EXPIRE: ${JWT_EXPIRE:-24h} # 服务配置 - APP_ENV: development - APP_PORT: 8080 - APP_HOST: 0.0.0.0 + APP_ENV: ${APP_ENV:-production} + APP_PORT: ${APP_PORT:-8080} + APP_HOST: ${APP_HOST:-0.0.0.0} # CORS配置 - CORS_ORIGINS: "http://localhost:3000,http://localhost:3001,http://localhost:5173" + CORS_ORIGINS: ${CORS_ORIGINS:-https://photography.iriver.top} # 文件上传配置 - UPLOAD_PATH: /app/uploads - UPLOAD_MAX_SIZE: 10485760 # 10MB + UPLOAD_PATH: ${UPLOAD_PATH:-/app/uploads} + UPLOAD_MAX_SIZE: ${UPLOAD_MAX_SIZE:-10485760} # 日志配置 - LOG_LEVEL: info - LOG_FORMAT: json + LOG_LEVEL: ${LOG_LEVEL:-info} + LOG_FORMAT: ${LOG_FORMAT:-json} ports: - "8080:8080" volumes: - uploads_data:/app/uploads - logs_data:/app/logs - networks: - - photography-network - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD", "/photography-api", "--health-check"] @@ -113,33 +63,17 @@ services: dockerfile: Dockerfile container_name: photography-migrate environment: - DB_HOST: db - DB_PORT: 5432 - DB_NAME: photography - DB_USER: postgres - DB_PASSWORD: postgres123 - DB_SSL_MODE: disable - networks: - - photography-network - depends_on: - db: - condition: service_healthy + DB_HOST: ${DB_HOST:-localhost} + DB_PORT: ${DB_PORT:-5432} + DB_NAME: ${DB_NAME:-photography} + DB_USER: ${DB_USER:-postgres} + DB_PASSWORD: ${DB_PASSWORD} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} entrypoint: ["/migrate", "up"] restart: "no" volumes: - postgres_data: - driver: local - redis_data: - driver: local uploads_data: driver: local logs_data: - driver: local - -networks: - photography-network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 \ No newline at end of file + driver: local \ No newline at end of file