Skip to content

fix: stop existing container before deployment #4

fix: stop existing container before deployment

fix: stop existing container before deployment #4

Workflow file for this run

# =============================================================================
# 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