# Gitea Actions: Docker Build, Push, Tag, and Notify # # This workflow provides a complete CI/CD pipeline for Docker-based projects: # - Validates semantic versioning on PRs # - Builds Docker images on every push/PR # - Pushes to Docker Hub on main branch merges # - Creates git tags for releases # - Sends Discord notifications on success/failure # # Template created: 2026-02-04 # Updated: 2026-02-05 (added git tagging) # For: Paper Dynasty Discord bot (reference implementation) name: Build Docker Image on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest steps: # ============================================== # 1. CHECKOUT CODE # ============================================== - name: Checkout code uses: actions/checkout@v4 # ============================================== # 2. SEMANTIC VERSION VALIDATION (PRs only) # ============================================== # Enforces proper semantic versioning: # - Blocks PRs that don't bump VERSION file # - Validates version changes follow semver rules # - Prevents skipping versions or going backwards # # Valid bumps: # - Patch: 1.2.3 → 1.2.4 (bug fixes) # - Minor: 1.2.3 → 1.3.0 (new features) # - Major: 1.2.3 → 2.0.0 (breaking changes) # # Invalid bumps: # - 1.2.3 → 1.4.0 (skipped minor version) # - 1.2.3 → 1.2.0 (went backwards) # - 1.2.3 → 1.3.1 (didn't reset patch) # - name: Check VERSION was bumped (semantic versioning) if: github.event_name == 'pull_request' run: | # Get VERSION from this PR branch PR_VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0") # Get VERSION from main branch git fetch origin main:main MAIN_VERSION=$(git show main:VERSION 2>/dev/null || echo "0.0.0") echo "Semantic Version Check" echo "Main branch version: $MAIN_VERSION" echo "PR branch version: $PR_VERSION" echo "" # Parse versions into components IFS='.' read -r MAIN_MAJOR MAIN_MINOR MAIN_PATCH <<< "$MAIN_VERSION" IFS='.' read -r PR_MAJOR PR_MINOR PR_PATCH <<< "$PR_VERSION" # Remove any non-numeric characters MAIN_MAJOR=${MAIN_MAJOR//[!0-9]/} MAIN_MINOR=${MAIN_MINOR//[!0-9]/} MAIN_PATCH=${MAIN_PATCH//[!0-9]/} PR_MAJOR=${PR_MAJOR//[!0-9]/} PR_MINOR=${PR_MINOR//[!0-9]/} PR_PATCH=${PR_PATCH//[!0-9]/} # Check if VERSION unchanged if [ "$PR_VERSION" = "$MAIN_VERSION" ]; then echo "ERROR: VERSION file has not been updated!" echo "" echo "Please update the VERSION file in your PR." echo "Current version: $MAIN_VERSION" exit 1 fi # Validate semantic version bump VALID=false BUMP_TYPE="" # Check for major version bump (X.0.0) if [ "$PR_MAJOR" -eq $((MAIN_MAJOR + 1)) ] && [ "$PR_MINOR" -eq 0 ] && [ "$PR_PATCH" -eq 0 ]; then VALID=true BUMP_TYPE="major" # Check for minor version bump (x.X.0) elif [ "$PR_MAJOR" -eq "$MAIN_MAJOR" ] && [ "$PR_MINOR" -eq $((MAIN_MINOR + 1)) ] && [ "$PR_PATCH" -eq 0 ]; then VALID=true BUMP_TYPE="minor" # Check for patch version bump (x.x.X) elif [ "$PR_MAJOR" -eq "$MAIN_MAJOR" ] && [ "$PR_MINOR" -eq "$MAIN_MINOR" ] && [ "$PR_PATCH" -eq $((MAIN_PATCH + 1)) ]; then VALID=true BUMP_TYPE="patch" fi if [ "$VALID" = true ]; then echo "Valid $BUMP_TYPE version bump: $MAIN_VERSION → $PR_VERSION" else echo "ERROR: Invalid semantic version change!" echo "" echo "Current version: $MAIN_VERSION" echo "PR version: $PR_VERSION" echo "" echo "Valid version bumps:" echo " - Patch: $MAIN_MAJOR.$MAIN_MINOR.$((MAIN_PATCH + 1))" echo " - Minor: $MAIN_MAJOR.$((MAIN_MINOR + 1)).0" echo " - Major: $((MAIN_MAJOR + 1)).0.0" echo "" echo "Common issues:" echo " - Skipping versions (e.g., 2.5.0 → 2.7.0)" echo " - Going backwards (e.g., 2.5.0 → 2.4.0)" echo " - Not resetting lower components (e.g., 2.5.0 → 2.6.1)" exit 1 fi # ============================================== # 3. DOCKER BUILDX SETUP # ============================================== - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # ============================================== # 4. DOCKER HUB LOGIN (main branch only) # ============================================== # Requires secrets in Gitea: # - DOCKERHUB_USERNAME: Your Docker Hub username # - DOCKERHUB_TOKEN: Docker Hub access token (not password!) # # To create token: # 1. Go to hub.docker.com # 2. Account Settings → Security → New Access Token # 3. Copy token to Gitea repo → Settings → Secrets → Actions # - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # ============================================== # 5. EXTRACT METADATA # ============================================== # Reads VERSION file and generates image tags: # - version: From VERSION file (e.g., "1.2.3") # - sha_short: First 7 chars of commit SHA # - version_sha: Combined version+commit (e.g., "v1.2.3-a1b2c3d") # - branch: Current branch name # - timestamp: ISO 8601 format for Discord # - name: Extract metadata id: meta run: | VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0") SHA_SHORT=$(echo ${{ github.sha }} | cut -c1-7) echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT echo "version_sha=v${VERSION}-${SHA_SHORT}" >> $GITHUB_OUTPUT echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT # ============================================== # 6. BUILD AND PUSH DOCKER IMAGE # ============================================== # Creates 3 tags for each build: # - latest: Always points to newest build # - v{VERSION}: Semantic version from VERSION file # - v{VERSION}-{COMMIT}: Version + commit hash for traceability # # Example tags: # - yourusername/yourrepo:latest # - yourusername/yourrepo:v1.2.3 # - yourusername/yourrepo:v1.2.3-a1b2c3d # # Push behavior: # - PRs: Build only (test), don't push # - Main: Build and push to Docker Hub # # CUSTOMIZE: Replace "yourusername/yourrepo" with your Docker Hub repo # - name: Build Docker image uses: docker/build-push-action@v5 with: context: . push: ${{ github.ref == 'refs/heads/main' }} tags: | yourusername/yourrepo:latest yourusername/yourrepo:v${{ steps.meta.outputs.version }} yourusername/yourrepo:${{ steps.meta.outputs.version_sha }} cache-from: type=registry,ref=yourusername/yourrepo:buildcache cache-to: type=registry,ref=yourusername/yourrepo:buildcache,mode=max # ============================================== # 7. BUILD SUMMARY # ============================================== # Creates a formatted summary visible in Actions UI # Shows: image tags, build details, push status # - name: Build Summary run: | echo "## Docker Build Successful" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY echo "- \`yourusername/yourrepo:latest\`" >> $GITHUB_STEP_SUMMARY echo "- \`yourusername/yourrepo:v${{ steps.meta.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`yourusername/yourrepo:${{ steps.meta.outputs.version_sha }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY echo "- Branch: \`${{ steps.meta.outputs.branch }}\`" >> $GITHUB_STEP_SUMMARY echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "- Timestamp: \`${{ steps.meta.outputs.timestamp }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ "${{ github.ref }}" == "refs/heads/main" ]; then echo "**Pushed to Docker Hub**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Pull with: \`docker pull yourusername/yourrepo:latest\`" >> $GITHUB_STEP_SUMMARY else echo "_PR build - image not pushed to Docker Hub_" >> $GITHUB_STEP_SUMMARY fi # ============================================== # 8. CREATE GIT TAG # ============================================== # Creates immutable git tag after successful build # - Only tags when pushed to Docker Hub (main branch) # - Tag matches VERSION file (single source of truth) # - Enables version history: git tag -l # - Allows version checkout: git checkout v1.2.3 # - Can trigger additional workflows (releases, changelogs) # # Note: If using deployment workflow, you may want to move # tagging to after successful deployment instead of after build # - name: Create Git Tag if: success() && github.ref == 'refs/heads/main' run: | VERSION=$(cat VERSION) # Configure git git config user.name "Gitea Actions" git config user.email "actions@git.manticorum.com" # Create annotated tag with build info git tag -a "v${VERSION}" -m "Release v${VERSION} Built: ${{ steps.meta.outputs.timestamp }} Commit: ${{ github.sha }} Author: ${{ github.actor }} Docker image: yourusername/yourrepo:v${VERSION} Built and tagged via Gitea Actions" # Push tag to repository git push origin "v${VERSION}" echo "Created and pushed tag v${VERSION}" # ============================================== # 9. DISCORD NOTIFICATION - SUCCESS # ============================================== # Sends green embed to Discord on successful builds # # Only fires on main branch pushes (not PRs) # # Setup: # 1. Create webhook in Discord channel: # Right-click channel → Edit → Integrations → Webhooks → New # 2. Copy webhook URL # 3. Replace the URL below # # CUSTOMIZE: # - Replace webhook URL with yours # - Replace "Your Project" in title # - Replace Docker Hub URLs with your repo # - name: Discord Notification - Success if: success() && github.ref == 'refs/heads/main' run: | curl -H "Content-Type: application/json" \ -d '{ "embeds": [{ "title": "Your Project Build Successful", "description": "Docker image built, tagged, and pushed to Docker Hub", "color": 3066993, "fields": [ { "name": "Version", "value": "`v${{ steps.meta.outputs.version }}`", "inline": true }, { "name": "Git Tag", "value": "[v${{ steps.meta.outputs.version }}](${{ github.server_url }}/${{ github.repository }}/releases/tag/v${{ steps.meta.outputs.version }})", "inline": true }, { "name": "Image Tag", "value": "`${{ steps.meta.outputs.version_sha }}`", "inline": true }, { "name": "Branch", "value": "`${{ steps.meta.outputs.branch }}`", "inline": true }, { "name": "Commit", "value": "`${{ steps.meta.outputs.sha_short }}`", "inline": true }, { "name": "Author", "value": "${{ github.actor }}", "inline": true }, { "name": "Docker Hub", "value": "[yourusername/yourrepo](https://hub.docker.com/r/yourusername/yourrepo)", "inline": false }, { "name": "View Run", "value": "[Click here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})", "inline": false } ], "timestamp": "${{ steps.meta.outputs.timestamp }}" }] }' \ YOUR_DISCORD_WEBHOOK_URL_HERE # ============================================== # 10. DISCORD NOTIFICATION - FAILURE # ============================================== # Sends red embed to Discord on build failures # # Only fires on main branch pushes (not PRs) # # CUSTOMIZE: # - Replace webhook URL with yours # - Replace "Your Project" in title # - name: Discord Notification - Failure if: failure() && github.ref == 'refs/heads/main' run: | TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) curl -H "Content-Type: application/json" \ -d '{ "embeds": [{ "title": "Your Project Build Failed", "description": "Docker build encountered an error", "color": 15158332, "fields": [ { "name": "Branch", "value": "`${{ github.ref_name }}`", "inline": true }, { "name": "Commit", "value": "`${{ github.sha }}`", "inline": true }, { "name": "Author", "value": "${{ github.actor }}", "inline": true }, { "name": "View Logs", "value": "[Click here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})", "inline": false } ], "timestamp": "'"$TIMESTAMP"'" }] }' \ YOUR_DISCORD_WEBHOOK_URL_HERE # ============================================== # CUSTOMIZATION CHECKLIST # ============================================== # Before using this template in a new project: # # ✅ Replace "yourusername/yourrepo" with your Docker Hub repository (3 locations) # ✅ Replace "Your Project" in Discord notification titles # ✅ Replace Discord webhook URLs (both success and failure) # ✅ Replace git config email in Create Git Tag step # ✅ Add secrets to Gitea repo: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN # ✅ Create VERSION file in repo root with initial version (e.g., "1.0.0") # ✅ Update branch name if not using "main" # # Optional customizations: # - Adjust runner labels (runs-on) if using self-hosted runners # - Modify version validation rules if you don't want strict semver # - Add additional notification channels (Slack, email, etc.) # - Move git tagging to after deployment if using deployment workflow # - Customize Discord embed colors, fields, or formatting # # ============================================== # TROUBLESHOOTING # ============================================== # Common issues and solutions: # # 1. VERSION validation failing unexpectedly # - Ensure VERSION file exists in repo root # - Check file contains only version number (no 'v' prefix or extra text) # - Verify version follows semver: MAJOR.MINOR.PATCH # # 2. Docker Hub push failing # - Verify DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets are set # - Check Docker Hub token has push permissions # - Ensure repository name matches your Docker Hub repo exactly # # 3. Git tag push failing # - Error "tag already exists": You're trying to release the same version twice # Solution: Bump VERSION file to next version # - Error "permission denied": Gitea Actions may not have push permissions # Solution: Check repo settings → Actions → Allow push to repository # - Tag created but not visible: Check you pushed to correct remote # Solution: Verify with "git ls-remote --tags origin" # # 4. Discord notifications not appearing # - Test webhook URL manually with curl # - Check webhook still exists in Discord channel settings # - Verify timestamp format is ISO 8601 (YYYY-MM-DDTHH:MM:SSZ) # - Look for HTTP error codes in Actions logs # # 5. Build cache not working # - Uses registry-based caching (pushes cache layers to Docker Hub) # - Requires Docker Hub login to read/write cache # - Cache tag is "yourusername/yourrepo:buildcache" # - To clear cache: delete the buildcache tag on Docker Hub # # ==============================================