name: Docker Build and Deploy on: push: branches: [ main ] paths-ignore: - '**.md' - 'docs/**' workflow_dispatch: inputs: deploy: description: '是否部署' type: boolean default: true env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} TIME: ${{ github.run_number }}-${{ github.run_attempt }} jobs: prepare: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Generate version id: version run: | echo "version=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT build: needs: prepare runs-on: ubuntu-latest strategy: matrix: service: [backend, frontend] include: - service: backend context: . - service: frontend context: ./frontend # 允许其中一个任务失败时,其他任务仍继续执行 fail-fast: false steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: platforms: linux/amd64,linux/arm64 - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKERHUB_USERNAME }}/stock-scanner-${{ matrix.service }} tags: | type=raw,value=latest type=raw,value=${{ needs.prepare.outputs.version }} - name: Build and push uses: docker/build-push-action@v5 with: context: ${{ matrix.context }} platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha,scope=${{ matrix.service }} cache-to: type=gha,scope=${{ matrix.service }},mode=max deploy: needs: [prepare, build] runs-on: ubuntu-latest if: | success() && (github.ref == 'refs/heads/main' && github.event_name == 'push') || (github.event_name == 'workflow_dispatch' && inputs.deploy) steps: - name: Checkout code for prod compose file uses: actions/checkout@v4 with: sparse-checkout: | docker-compose.prod.yml - name: Create .env file for deployment run: | cat > .env << EOL DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }} TAG=${{ needs.prepare.outputs.version }} API_KEY=${{ secrets.API_KEY }} API_URL=${{ secrets.API_URL }} API_MODEL=${{ secrets.API_MODEL }} API_TIMEOUT=${{ secrets.API_TIMEOUT }} LOGIN_PASSWORD=${{ secrets.LOGIN_PASSWORD }} ANNOUNCEMENT_TEXT=${{ secrets.ANNOUNCEMENT_TEXT }} EOL - name: Deploy to server uses: appleboy/ssh-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} script_stop: true # 遇到错误时停止执行 envs: DEPLOY_PATH script: | # 创建备份目录(如果不存在) mkdir -p ${DEPLOY_PATH}/backups # 如果存在旧容器,备份当前的配置和数据 if [ -f ${DEPLOY_PATH}/docker-compose.prod.yml ]; then cp ${DEPLOY_PATH}/docker-compose.prod.yml ${DEPLOY_PATH}/backups/docker-compose.prod.$(date +%Y%m%d%H%M%S).yml if [ -f ${DEPLOY_PATH}/.env ]; then cp ${DEPLOY_PATH}/.env ${DEPLOY_PATH}/backups/.env.$(date +%Y%m%d%H%M%S) fi fi - name: Copy files to server uses: appleboy/scp-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} source: "docker-compose.prod.yml,.env" target: ${{ secrets.DEPLOY_PATH }} overwrite: true - name: Start services uses: appleboy/ssh-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} script_stop: true script: | cd ${{ secrets.DEPLOY_PATH }} # 拉取最新镜像并启动服务 docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d # 等待服务启动完成 echo "等待服务启动..." sleep 10 # 验证服务是否正常运行 if ! curl -s http://localhost:80 > /dev/null; then echo "前端服务未正常运行!" exit 1 fi if ! curl -s http://localhost:8888/config > /dev/null; then echo "后端服务未正常运行!" exit 1 fi # 清理未使用的镜像和容器 docker system prune -af --volumes echo "部署完成并验证成功!" notify: needs: [prepare, build, deploy] runs-on: ubuntu-latest if: always() steps: - name: Notify deployment status uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_CHANNEL: deployments SLACK_COLOR: ${{ needs.deploy.result == 'success' && 'good' || needs.deploy.result == 'skipped' && 'warning' || 'danger' }} SLACK_TITLE: Stock Scanner Deployment Status SLACK_MESSAGE: | Build: ${{ needs.build.result == 'success' && '✅' || '❌' }} Deploy: ${{ needs.deploy.result == 'success' && '✅' || needs.deploy.result == 'skipped' && '⏭️' || '❌' }} Version: ${{ needs.prepare.outputs.version }} SLACK_FOOTER: GitHub Actions