Skip to content

Instantly share code, notes, and snippets.

@shaoyanji
Created April 29, 2026 05:58
Show Gist options
  • Select an option

  • Save shaoyanji/f71868d4240ab9f3f5f8c5c44024a970 to your computer and use it in GitHub Desktop.

Select an option

Save shaoyanji/f71868d4240ab9f3f5f8c5c44024a970 to your computer and use it in GitHub Desktop.

GitHub Release Workflow Lessons Learned

Session Summary

This session involved:

  1. Minimizing Go CLI dependencies (removing goldmark library)
  2. Setting up GitHub Actions CI/CD for multi-platform builds
  3. Creating first release with proper binary distribution

Mistakes Made & Lessons Learned

1. Committed Binary to Git

Mistake: Built binary (<x or any name>-cli) was committed to the repository.

Why it's bad:

  • Bloates repository size
  • Binaries are platform-specific and shouldn't be in source control
  • Conflicts with CI/CD build process
  • Makes cloning slower

Fix:

git rm --cached mdquery-cli
echo "mdquery-cli" >> .gitignore

Best Practice: Always add .gitignore before building, exclude:

  • Compiled binaries
  • Build artifacts
  • Platform-specific files

2. Wrong GitHub Actions Trigger

Mistake: Used release event trigger instead of tag push.

Original:

on:
  release:
    types: [created]

Why it's bad:

  • Requires manual release creation in GitHub UI first
  • Doesn't work with automated tag-based releases
  • Extra manual step defeats automation purpose

Fix:

on:
  push:
    tags:
      - "v*"

Best Practice: Use tag-based releases for automation:

git tag v0.0.1
git push origin v0.0.1

This automatically triggers the workflow and creates the release.


3. Binary Naming Collision

Mistake: All platform builds used same binary name (mdquery-cli), causing overwrites.

Original:

- name: Build binary
  run: |
    BINARY_NAME=mdquery-cli
    if [ "$GOOS" = "windows" ]; then
      BINARY_NAME=mdquery-cli.exe
    fi
    go build -ldflags="-s -w" -o $BINARY_NAME

Why it's bad:

  • Each matrix iteration overwrites the same file
  • Only the last built binary remains
  • Release ends up with single binary instead of multi-platform set

Fix:

- name: Build binary
  run: |
    BINARY_NAME=mdquery-cli-${{ matrix.goos }}-${{ matrix.goarch }}
    if [ "$GOOS" = "windows" ]; then
      BINARY_NAME=mdquery-cli-${{ matrix.goos }}-${{ matrix.goarch }}.exe
    fi
    go build -ldflags="-s -w" -o $BINARY_NAME

Best Practice: Always include platform/architecture in binary names:

  • mdquery-cli-linux-amd64
  • mdquery-cli-darwin-arm64
  • mdquery-cli-windows-amd64.exe

Clean Release Workflow Template

1. Project Setup

# Initialize .gitignore first
cat > .gitignore << 'EOF'
# Binaries
*cli
*.exe

# Build artifacts
*.o
*.a
*.so

# Test coverage
*.out
coverage.html
EOF

# Build and test locally
go build -o myapp
./myapp --help
go test -v

2. GitHub Actions Workflow

name: Build and Release

on:
  push:
    branches: [main, master]
    tags:
      - "v*"
  pull_request:
    branches: [main, master]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        goos: [linux, darwin, windows]
        goarch: [amd64, arm64]
        exclude:
          - goos: windows
            goarch: arm64

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.21"

      - name: Download dependencies
        run: go mod download

      - name: Run tests
        run: go test -v

      - name: Build binary
        env:
          GOOS: ${{ matrix.goos }}
          GOARCH: ${{ matrix.goarch }}
          CGO_ENABLED: 0
        run: |
          BINARY_NAME=myapp-${{ matrix.goos }}-${{ matrix.goarch }}
          if [ "$GOOS" = "windows" ]; then
            BINARY_NAME=myapp-${{ matrix.goos }}-${{ matrix.goarch }}.exe
          fi
          go build -ldflags="-s -w" -o $BINARY_NAME

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
          path: myapp-${{ matrix.goos }}-${{ matrix.goarch }}*

  release:
    if: startsWith(github.ref, 'refs/tags/v')
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: ./artifacts

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: ./artifacts/**/*
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3. Release Process

# Make changes
git add .
git commit -m "Your commit message"

# Tag and push
git tag v0.0.1
git push origin main
git push origin v0.0.1

This single command sequence:

  • Builds all platform binaries
  • Runs tests
  • Creates GitHub release
  • Attaches all binaries to release
  • Generates release notes automatically

Key Takeaways

  1. Never commit binaries - Set up .gitignore before first build
  2. Use tag-based releases - Automate the entire process with git push v*
  3. Unique binary names - Always include platform/architecture in filename
  4. Test locally first - Ensure builds work before pushing
  5. Use CGO_ENABLED=0 - Creates static binaries for portability
  6. Strip debug symbols - Use -ldflags="-s -w" for smaller binaries
  7. Generate release notes - Let GitHub create changelog from commits

Quick Reference

Clean up accidental binary commit:

git rm --cached binary-name
echo "binary-name" >> .gitignore
git add .gitignore
git commit -m "Remove binary from git"

Fix release workflow:

# Wrong: release event
on:
  release:
    types: [created]

# Right: tag push
on:
  push:
    tags:
      - "v*"

Fix binary naming:

# Wrong: all builds produce same name
go build -o myapp

# Right: platform-specific names
go build -o myapp-${GOOS}-${GOARCH}

Proper release command:

git tag v1.0.0
git push origin v1.0.0

This single command triggers the entire automated release process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment