Skip to content

[v0.142.7] Docker Image Build and Push (Production) #505

[v0.142.7] Docker Image Build and Push (Production)

[v0.142.7] Docker Image Build and Push (Production) #505

name: Unstract Docker Image Build and Push (Production)
on:
workflow_dispatch:
inputs:
tag:
description: "Docker image tag"
required: true
set_as_latest:
description: "Set as latest release"
type: boolean
default: false
required: false
release:
types:
- created
run-name: "[${{ github.event.release.tag_name || github.event.inputs.tag }}] Docker Image Build and Push (Production)"
jobs:
build-and-push:
runs-on: ubuntu-latest
strategy:
matrix:
service_name:
[
backend,
frontend,
platform-service,
prompt-service,
runner,
x2text-service,
]
steps:
- name: Checkout code for release
if: github.event_name == 'release'
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Checkout code for branch
if: github.event_name != 'release'
uses: actions/checkout@v4
# Set up QEMU for ARM64 emulation
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64/v8
# Set up Docker Buildx for better caching and multi-arch builds
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64/v8
# Log in to Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Set version tag based on event type
- name: Set version tag
id: set-tag
run: echo "DOCKER_VERSION_TAG=${{ github.event.release.tag_name || github.event.inputs.tag }}" >> $GITHUB_ENV
# Set up additional tags for release builds
- name: Set image tags
id: tags
run: |
# Check if service exists in the config
echo "Checking if service ${{ matrix.service_name }} exists in docker-compose.build.yaml"
if ! grep -q "^ ${{ matrix.service_name }}:" ./docker/docker-compose.build.yaml; then
echo "Service ${{ matrix.service_name }} not found in docker-compose.build.yaml" && exit 1
fi
# Set latest tag for releases or when explicitly requested
echo "SEMVER_IMAGE_TAG=unstract/${{ matrix.service_name }}:${{ env.DOCKER_VERSION_TAG }}" >> $GITHUB_ENV
# Set latest tag if it's a release or if set_as_latest is true
if [ "${{ github.event_name }}" = "release" ] || [ "${{ github.event.inputs.set_as_latest }}" = "true" ]; then
echo "LATEST_IMAGE_TAG=unstract/${{ matrix.service_name }}:latest" >> $GITHUB_ENV
else
echo "LATEST_IMAGE_TAG=" >> $GITHUB_ENV
fi
# Build and push using Docker Bake
- name: Build and push image
uses: docker/bake-action@v5
env:
VERSION: ${{ env.DOCKER_VERSION_TAG }}
with:
files: ./docker/docker-compose.build.yaml
targets: ${{ matrix.service_name }}
push: true
set: |
*.tags=${{ env.SEMVER_IMAGE_TAG }}
${{ env.LATEST_IMAGE_TAG && format('*.tags={0}', env.LATEST_IMAGE_TAG) || '' }}
*.context=.
*.args.VERSION=${{ env.DOCKER_VERSION_TAG }}
*.platform=linux/amd64,linux/arm64/v8
*.cache-from=type=gha,scope=${{ matrix.service_name }}
*.cache-to=type=gha,mode=max,scope=${{ matrix.service_name }}
# Capture build result and write to artifact
- name: Write build status
if: always()
run: |
mkdir -p build-status
cat > build-status/${{ matrix.service_name }}.json << EOF
{
"service": "${{ matrix.service_name }}",
"status": "${{ job.status }}",
"tag": "${{ env.DOCKER_VERSION_TAG }}"
}
EOF
# Upload status for summary job
- name: Upload build status
if: always()
uses: actions/upload-artifact@v4
with:
name: build-status-${{ matrix.service_name }}
path: build-status/${{ matrix.service_name }}.json
retention-days: 1
# Summary job that runs after all builds
build-summary:
needs: build-and-push
runs-on: ubuntu-latest
if: always()
steps:
# Download all build status artifacts
- name: Download build statuses
uses: actions/download-artifact@v4
with:
pattern: build-status-*
merge-multiple: true
path: build-status
- name: Generate build summary
id: summary
run: |
# Initialize variables
TOTAL_SERVICES=6
OVERALL_RESULT='${{ needs.build-and-push.result }}'
SUCCESS_COUNT=0
FAILED_COUNT=0
FAILED_SERVICES=""
SUCCESS_SERVICES=""
# Process individual service results if artifacts exist
if [ -d "build-status" ]; then
for status_file in build-status/*.json; do
if [ -f "$status_file" ]; then
SERVICE=$(jq -r '.service' "$status_file")
STATUS=$(jq -r '.status' "$status_file")
if [ "$STATUS" = "success" ]; then
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
SUCCESS_SERVICES="${SUCCESS_SERVICES}${SERVICE}, "
else
FAILED_COUNT=$((FAILED_COUNT + 1))
FAILED_SERVICES="${FAILED_SERVICES}${SERVICE}, "
fi
fi
done
# Trim trailing comma and space
SUCCESS_SERVICES=${SUCCESS_SERVICES%, }
FAILED_SERVICES=${FAILED_SERVICES%, }
fi
# Set overall status based on results
if [ "$OVERALL_RESULT" = "success" ]; then
SUMMARY_STATUS="✅ All Docker builds successful"
SUMMARY_COLOR="good"
ALL_SUCCESS="true"
elif [ "$OVERALL_RESULT" = "cancelled" ]; then
SUMMARY_STATUS="⚠️ Docker builds cancelled"
SUMMARY_COLOR="warning"
ALL_SUCCESS="false"
else
SUMMARY_STATUS="❌ Some Docker builds failed"
SUMMARY_COLOR="danger"
ALL_SUCCESS="false"
fi
echo "summary_status=$SUMMARY_STATUS" >> $GITHUB_OUTPUT
echo "summary_color=$SUMMARY_COLOR" >> $GITHUB_OUTPUT
echo "total_count=$TOTAL_SERVICES" >> $GITHUB_OUTPUT
echo "all_success=$ALL_SUCCESS" >> $GITHUB_OUTPUT
echo "overall_result=$OVERALL_RESULT" >> $GITHUB_OUTPUT
echo "success_count=$SUCCESS_COUNT" >> $GITHUB_OUTPUT
echo "failed_count=$FAILED_COUNT" >> $GITHUB_OUTPUT
echo "failed_services=$FAILED_SERVICES" >> $GITHUB_OUTPUT
echo "success_services=$SUCCESS_SERVICES" >> $GITHUB_OUTPUT
# Create a concise summary for Slack
if [ "$ALL_SUCCESS" = "true" ]; then
SLACK_DETAILS="All $TOTAL_SERVICES services built successfully ✅"
else
SLACK_DETAILS="❌ Failed: $FAILED_SERVICES | ✅ Succeeded: $SUCCESS_SERVICES"
fi
echo "slack_details=$SLACK_DETAILS" >> $GITHUB_OUTPUT
# Write to GitHub Summary
- name: Write GitHub Summary
run: |
echo "# 🐳 Docker Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Status**: ${{ steps.summary.outputs.summary_status }}" >> $GITHUB_STEP_SUMMARY
echo "**Tag**: ${{ github.event.release.tag_name || github.event.inputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "**Total Services**: ${{ steps.summary.outputs.total_count }}" >> $GITHUB_STEP_SUMMARY
# Show counts if available
if [ -n "${{ steps.summary.outputs.success_count }}" ] && [ "${{ steps.summary.outputs.success_count }}" != "0" ]; then
echo "**Successful**: ${{ steps.summary.outputs.success_count }}/${{ steps.summary.outputs.total_count }}" >> $GITHUB_STEP_SUMMARY
fi
if [ -n "${{ steps.summary.outputs.failed_count }}" ] && [ "${{ steps.summary.outputs.failed_count }}" != "0" ]; then
echo "**Failed**: ${{ steps.summary.outputs.failed_count }}/${{ steps.summary.outputs.total_count }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Show service status table
echo "## Service Build Status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Process and display individual service statuses
if [ -d "build-status" ]; then
echo "| Service | Status |" >> $GITHUB_STEP_SUMMARY
echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY
# Define services in order
for service in backend frontend platform-service prompt-service runner x2text-service; do
if [ -f "build-status/${service}.json" ]; then
STATUS=$(jq -r '.status' "build-status/${service}.json")
if [ "$STATUS" = "success" ]; then
echo "| ${service} | ✅ Success |" >> $GITHUB_STEP_SUMMARY
else
echo "| ${service} | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
fi
else
echo "| ${service} | ⚠️ Unknown |" >> $GITHUB_STEP_SUMMARY
fi
done
else
# Fallback if no artifacts (shouldn't happen but just in case)
if [ "${{ steps.summary.outputs.all_success }}" = "true" ]; then
echo "All services built successfully! ✅" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Build workflow result: **${{ steps.summary.outputs.overall_result }}**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Unable to determine individual service status. Please check the job logs above." >> $GITHUB_STEP_SUMMARY
fi
fi
# Show failed services if any
if [ -n "${{ steps.summary.outputs.failed_services }}" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "## ❌ Failed Services" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.summary.outputs.failed_services }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "[View Workflow Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
# Send summary notification to Slack
- name: Send Slack summary notification
if: always()
uses: slackapi/[email protected]
with:
webhook-type: incoming-webhook
payload: |
{
"text": "${{ steps.summary.outputs.summary_status }}",
"attachments": [
{
"color": "${{ steps.summary.outputs.summary_color }}",
"fields": [
{
"title": "Docker Tag",
"value": "${{ github.event.release.tag_name || github.event.inputs.tag }}",
"short": true
},
{
"title": "Total Services",
"value": "${{ steps.summary.outputs.total_count }}",
"short": true
},
{
"title": "Repository",
"value": "${{ github.repository }}",
"short": true
},
{
"title": "Workflow",
"value": "${{ github.workflow }}",
"short": true
},
{
"title": "Build Details",
"value": "${{ steps.summary.outputs.slack_details }}",
"short": false
},
{
"title": "Workflow Run",
"value": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"short": false
}
],
"footer": "GitHub Actions"
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}