ci: Add GitHub Actions workflow to build and push Docker image #88
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
| name: Build and Push to GHCR | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - start-io | |
| - 'release-*' | |
| tags: | |
| - 'v*' | |
| pull_request: | |
| branches: | |
| - main | |
| - start-io | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Custom tag for the image' | |
| required: false | |
| type: string | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write # for cosign signing | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # for git describe | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: linux/amd64 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| driver-opts: | | |
| image=moby/buildkit:latest | |
| network=host | |
| - name: Log in to GitHub Container Registry | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| # Branch events | |
| type=ref,event=branch | |
| # Tag events | |
| type=ref,event=tag | |
| # PR events | |
| type=ref,event=pr | |
| # Semantic versions | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} | |
| # Short SHA | |
| type=sha,prefix={{branch}}-,enable=${{ github.ref_type == 'branch' }} | |
| # Custom tag from workflow_dispatch | |
| type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != '' }} | |
| # Latest tag for main branch | |
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | |
| # OCI-specific tags | |
| type=raw,value=oci-latest,enable=${{ github.ref == 'refs/heads/start-io' }} | |
| flavor: | | |
| latest=false | |
| labels: | | |
| org.opencontainers.image.title=Karpenter OCI | |
| org.opencontainers.image.description=Karpenter with OCI provider support for Oracle Kubernetes Engine | |
| org.opencontainers.image.vendor=StartApp | |
| maintainer=StartApp Team <support@startapp.com> | |
| - name: Generate build args | |
| id: buildargs | |
| run: | | |
| VERSION="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] || 'dev' }}" | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | |
| echo "git_commit=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT | |
| - name: Build and push Docker image | |
| id: build | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./Dockerfile | |
| platforms: linux/amd64 | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| annotations: ${{ steps.meta.outputs.annotations }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| VERSION=${{ steps.buildargs.outputs.version }} | |
| BUILD_DATE=${{ steps.buildargs.outputs.build_date }} | |
| GIT_COMMIT=${{ steps.buildargs.outputs.git_commit }} | |
| provenance: true | |
| sbom: true | |
| - name: Install Cosign | |
| if: github.event_name != 'pull_request' | |
| uses: sigstore/cosign-installer@v3 | |
| - name: Sign container image | |
| if: github.event_name != 'pull_request' | |
| env: | |
| COSIGN_EXPERIMENTAL: 1 | |
| run: | | |
| # Sign the image with keyless signing | |
| echo "${{ steps.meta.outputs.tags }}" | while IFS= read -r tag; do | |
| echo "Signing $tag" | |
| cosign sign --yes "$tag" | |
| done | |
| - name: Generate SBOM | |
| if: github.event_name != 'pull_request' | |
| uses: anchore/sbom-action@v0 | |
| with: | |
| image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} | |
| format: spdx-json | |
| output-file: sbom.spdx.json | |
| - name: Attach SBOM to image | |
| if: github.event_name != 'pull_request' | |
| env: | |
| COSIGN_EXPERIMENTAL: 1 | |
| run: | | |
| echo "Attaching SBOM to image" | |
| cosign attach sbom --sbom sbom.spdx.json \ | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} | |
| - name: Create Release Summary | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| echo "## Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Image Tags" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Labels" >> $GITHUB_STEP_SUMMARY | |
| echo '```json' >> $GITHUB_STEP_SUMMARY | |
| echo '${{ steps.meta.outputs.json }}' | jq -r '.labels' >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Build Arguments" >> $GITHUB_STEP_SUMMARY | |
| echo "- Version: \`${{ steps.buildargs.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- Build Date: \`${{ steps.buildargs.outputs.build_date }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- Git Commit: \`${{ steps.buildargs.outputs.git_commit }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Security" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Image signed with Cosign" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ SBOM attached" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Provenance included" >> $GITHUB_STEP_SUMMARY | |
| vulnerability-scan: | |
| needs: build-and-push | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'pull_request' | |
| permissions: | |
| contents: read | |
| packages: read | |
| security-events: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| steps: | |
| - name: Wait for image availability | |
| run: | | |
| echo "Waiting 30 seconds for image to be available in registry..." | |
| sleep 30 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@master | |
| continue-on-error: true | |
| with: | |
| image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH' | |
| exit-code: '0' # Don't fail on vulnerabilities, just report them | |
| - name: Check if SARIF file exists | |
| id: check_sarif | |
| run: | | |
| if [ -f "trivy-results.sarif" ] && [ -s "trivy-results.sarif" ]; then | |
| echo "sarif_exists=true" >> $GITHUB_OUTPUT | |
| echo "SARIF file exists and is not empty" | |
| else | |
| echo "sarif_exists=false" >> $GITHUB_OUTPUT | |
| echo "SARIF file does not exist or is empty - skipping upload" | |
| fi | |
| - name: Upload Trivy scan results to GitHub Security | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: steps.check_sarif.outputs.sarif_exists == 'true' | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| - name: Create Vulnerability Report | |
| if: always() | |
| run: | | |
| echo "## Vulnerability Scan Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Install trivy if not available | |
| if ! command -v trivy &> /dev/null; then | |
| curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin | |
| fi | |
| # Try multiple possible tags | |
| IMAGE_FOUND=false | |
| for TAG in "${{ github.ref_name }}" "oci-latest" "start-io-${GITHUB_SHA:0:7}"; do | |
| echo "Trying tag: $TAG" >> $GITHUB_STEP_SUMMARY | |
| if trivy image --format table --severity CRITICAL,HIGH,MEDIUM \ | |
| "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}" >> $GITHUB_STEP_SUMMARY 2>&1; then | |
| IMAGE_FOUND=true | |
| break | |
| fi | |
| done | |
| if [ "$IMAGE_FOUND" = false ]; then | |
| echo "⚠️ Could not scan image - it may not be available yet or tags may be different" >> $GITHUB_STEP_SUMMARY | |
| echo "Available tags should include: start-io, oci-latest, start-io-<sha>" >> $GITHUB_STEP_SUMMARY | |
| fi |