fix: stop existing container before deployment #4
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================= | |
| # mqBase Release Workflow | |
| # ============================================================================= | |
| # Triggers: | |
| # - Tag push: v* (e.g., v0.13.0) on any branch | |
| # - Manual: workflow_dispatch with version input | |
| # | |
| # Jobs: | |
| # 1. build: Build and push Docker images to Docker Hub and GHCR | |
| # 2. deploy: Deploy to production (requires environment approval) | |
| # | |
| # Setup: | |
| # - Create "prod" environment in repo settings with required reviewers | |
| # - Install self-hosted runner on production server (see docs) | |
| # - Configure runner with labels: [self-hosted, linux, prod] | |
| # ============================================================================= | |
| name: Release mqBase | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., 0.13.0)' | |
| required: true | |
| deploy: | |
| description: 'Deploy to production after build' | |
| type: boolean | |
| default: false | |
| env: | |
| DOCKERHUB_IMAGE: dotstartech/mqbase | |
| GHCR_IMAGE: ghcr.io/dotstartech/mqbase | |
| jobs: | |
| # =========================================================================== | |
| # Build Job - Runs on GitHub-hosted runner | |
| # =========================================================================== | |
| build: | |
| name: Build Docker Images | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| version: ${{ steps.version.outputs.VERSION }} | |
| steps: | |
| - name: Extract version | |
| id: version | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| else | |
| VERSION="${{ inputs.version }}" | |
| fi | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Building version: $VERSION" | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up QEMU for multi-arch builds | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Minify app.js for production | |
| run: | | |
| npm install -g terser | |
| ORIGINAL_SIZE=$(wc -c < admin/app.js) | |
| terser admin/app.js --compress --mangle -o admin/app.js | |
| MINIFIED_SIZE=$(wc -c < admin/app.js) | |
| REDUCTION=$((100 - (MINIFIED_SIZE * 100 / ORIGINAL_SIZE))) | |
| echo "app.js: ${ORIGINAL_SIZE} bytes -> ${MINIFIED_SIZE} bytes (${REDUCTION}% reduction)" | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: docker.io | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # ========================================================================= | |
| # Standard image - AMD64 | |
| # ========================================================================= | |
| - name: Build and push standard AMD64 image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile | |
| platforms: linux/amd64 | |
| push: true | |
| tags: | | |
| ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64 | |
| ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }} | |
| ${{ env.DOCKERHUB_IMAGE }}:latest | |
| ${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64 | |
| ${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }} | |
| ${{ env.GHCR_IMAGE }}:latest | |
| build-args: | | |
| UID=1000 | |
| GID=1000 | |
| cache-from: type=gha,scope=standard-amd64 | |
| cache-to: type=gha,mode=max,scope=standard-amd64 | |
| # ========================================================================= | |
| # Standard image - ARM64 | |
| # ========================================================================= | |
| - name: Build and push standard ARM64 image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile | |
| platforms: linux/arm64 | |
| push: true | |
| tags: | | |
| ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64 | |
| ${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64 | |
| build-args: | | |
| UID=1000 | |
| GID=1000 | |
| cache-from: type=gha,scope=standard-arm64 | |
| cache-to: type=gha,mode=max,scope=standard-arm64 | |
| # ========================================================================= | |
| # Distroless image - AMD64 | |
| # ========================================================================= | |
| - name: Build and push distroless AMD64 image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.distroless | |
| platforms: linux/amd64 | |
| push: true | |
| tags: | | |
| ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64-distroless | |
| ${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64-distroless | |
| cache-from: type=gha,scope=distroless-amd64 | |
| cache-to: type=gha,mode=max,scope=distroless-amd64 | |
| # ========================================================================= | |
| # Distroless image - ARM64 | |
| # ========================================================================= | |
| - name: Build and push distroless ARM64 image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./docker/Dockerfile.distroless | |
| platforms: linux/arm64 | |
| push: true | |
| tags: | | |
| ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64-distroless | |
| ${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64-distroless | |
| cache-from: type=gha,scope=distroless-arm64 | |
| cache-to: type=gha,mode=max,scope=distroless-arm64 | |
| - name: Build Summary | |
| run: | | |
| echo "## Images pushed successfully! :rocket:" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Version: ${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Standard Images (debian:trixie-slim)" >> $GITHUB_STEP_SUMMARY | |
| echo "| Architecture | Docker Hub | GitHub Container Registry |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------------|------------|---------------------------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| AMD64 | \`${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64\` | \`${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ARM64 | \`${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64\` | \`${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Distroless Images (gcr.io/distroless/base-debian13)" >> $GITHUB_STEP_SUMMARY | |
| echo "| Architecture | Docker Hub | GitHub Container Registry |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------------|------------|---------------------------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| AMD64 | \`${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64-distroless\` | \`${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-amd64-distroless\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| ARM64 | \`${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64-distroless\` | \`${{ env.GHCR_IMAGE }}:${{ steps.version.outputs.VERSION }}-arm64-distroless\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Latest Tag" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.DOCKERHUB_IMAGE }}:latest\` → AMD64 standard image" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.GHCR_IMAGE }}:latest\` → AMD64 standard image" >> $GITHUB_STEP_SUMMARY | |
| # =========================================================================== | |
| # Release Job - Create GitHub Release (only on tag push) | |
| # =========================================================================== | |
| release: | |
| name: Create GitHub Release | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Full history for release notes | |
| - name: Generate release notes | |
| id: notes | |
| run: | | |
| VERSION="${{ needs.build.outputs.version }}" | |
| # Create release body | |
| cat << EOF > release_notes.md | |
| ## Docker Images | |
| ### Standard Images (debian:trixie-slim) | |
| | Architecture | Docker Hub | GitHub Container Registry | | |
| |--------------|------------|---------------------------| | |
| | AMD64 | \`dotstartech/mqbase:${VERSION}-amd64\` | \`ghcr.io/dotstartech/mqbase:${VERSION}-amd64\` | | |
| | ARM64 | \`dotstartech/mqbase:${VERSION}-arm64\` | \`ghcr.io/dotstartech/mqbase:${VERSION}-arm64\` | | |
| ### Distroless Images (minimal, no shell) | |
| | Architecture | Docker Hub | GitHub Container Registry | | |
| |--------------|------------|---------------------------| | |
| | AMD64 | \`dotstartech/mqbase:${VERSION}-amd64-distroless\` | \`ghcr.io/dotstartech/mqbase:${VERSION}-amd64-distroless\` | | |
| | ARM64 | \`dotstartech/mqbase:${VERSION}-arm64-distroless\` | \`ghcr.io/dotstartech/mqbase:${VERSION}-arm64-distroless\` | | |
| ### Quick Start | |
| \`\`\`bash | |
| docker run -d --name mqbase -p 1883:1883 -p 8080:8080 -p 9001:9001 dotstartech/mqbase:${VERSION} | |
| \`\`\` | |
| Open http://localhost:8080 and log in with \`admin:admin\`. | |
| --- | |
| EOF | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: mqBase v${{ needs.build.outputs.version }} | |
| body_path: release_notes.md | |
| generate_release_notes: true # Appends auto-generated notes from commits | |
| draft: false | |
| prerelease: ${{ contains(needs.build.outputs.version, '-') }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # =========================================================================== | |
| # Deploy Job - Runs on self-hosted runner on production server | |
| # =========================================================================== | |
| deploy: | |
| name: Deploy to Production | |
| needs: build | |
| # Only deploy on tag push OR when manually triggered with deploy=true | |
| # Note: runs after release job completes (if it ran) due to GitHub's job ordering | |
| if: | | |
| always() && | |
| needs.build.result == 'success' && | |
| (startsWith(github.ref, 'refs/tags/v') || inputs.deploy == true) | |
| runs-on: [self-hosted, linux, prod] | |
| environment: prod # Requires approval from reviewers | |
| steps: | |
| - name: Deploy mqBase v${{ needs.build.outputs.version }} | |
| run: | | |
| echo "=== Deploying mqBase v${{ needs.build.outputs.version }} ===" | |
| cd /opt/mqbase | |
| # Pull the new image | |
| echo "Pulling new image..." | |
| sudo docker compose -f compose.prod.yml pull | |
| # Stop and remove existing container, then start fresh | |
| echo "Stopping existing container..." | |
| sudo docker compose -f compose.prod.yml down --remove-orphans || true | |
| echo "Starting new container..." | |
| sudo docker compose -f compose.prod.yml up -d | |
| # Wait for container to be healthy | |
| echo "Waiting for health check..." | |
| sleep 5 | |
| # Verify deployment | |
| HEALTH=$(curl -sf --max-time 10 https://mqbase.io/health || echo "FAILED") | |
| if [ "$HEALTH" = "OK" ]; then | |
| echo "✅ Deployment successful!" | |
| else | |
| echo "❌ Health check failed: $HEALTH" | |
| exit 1 | |
| fi | |
| - name: Deployment Summary | |
| run: | | |
| echo "## Deployment Complete :white_check_mark:" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version**: ${{ needs.build.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Server**: mqbase.io (78.46.17.48)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Status**: Healthy" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Verification" >> $GITHUB_STEP_SUMMARY | |
| echo "- Health endpoint: https://mqbase.io/health → OK" >> $GITHUB_STEP_SUMMARY | |
| - name: Notify on failure | |
| if: failure() | |
| run: | | |
| echo "## ❌ Deployment Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Check the logs above for details." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Rollback" >> $GITHUB_STEP_SUMMARY | |
| echo "To rollback, SSH to the server and run:" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY | |
| echo "cd /opt/mqbase" >> $GITHUB_STEP_SUMMARY | |
| echo "sudo docker compose -f compose.prod.yml down" >> $GITHUB_STEP_SUMMARY | |
| echo "sudo docker pull dotstartech/mqbase:<previous-version>" >> $GITHUB_STEP_SUMMARY | |
| echo "sudo docker compose -f compose.prod.yml up -d" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY |