name: Docker Build, Push & Security Scan on: push: branches: [main] tags: ['v*'] paths: - 'Dockerfile' - '.dockerignore' - 'package.json' - 'bun.lock*' - '.github/workflows/docker-build.yml' - 'docker-entrypoint.sh' - 'drizzle/**' - 'scripts/**' - 'src/**' pull_request: paths: - 'Dockerfile' - '.dockerignore' - 'package.json' - 'bun.lock*' - '.github/workflows/docker-build.yml' - 'docker-entrypoint.sh' - 'drizzle/**' - 'scripts/**' - 'src/**' schedule: - cron: '0 0 * * 0' # Weekly security scan on Sunday at midnight env: REGISTRY: ghcr.io IMAGE: ${{ github.repository }} SHA: ${{ github.event.pull_request.head.sha || github.event.after }} jobs: docker: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write packages: write security-events: write pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v4 with: ref: ${{ env.SHA }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - name: Log into registry if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # Login to Docker Hub for Docker Scout (optional - provides better vulnerability data) # Add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets to enable this - name: Log into Docker Hub uses: docker/login-action@v3 continue-on-error: true with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Extract version from tag if present - name: Extract version from tag id: tag_version run: | if [[ $GITHUB_REF == refs/tags/v* ]]; then TAG_VERSION="${GITHUB_REF#refs/tags/}" if [[ ! "$TAG_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then echo "::error::Release tag '${TAG_VERSION}' is invalid. Expected semver tag format like v1.2.3 or v1.2.3-rc.1" exit 1 fi APP_VERSION="${TAG_VERSION#v}" echo "VERSION=${TAG_VERSION}" >> $GITHUB_OUTPUT echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT echo "Using version tag: ${TAG_VERSION}" else echo "VERSION=latest" >> $GITHUB_OUTPUT echo "APP_VERSION=dev" >> $GITHUB_OUTPUT echo "No version tag, using 'latest'" fi # Keep version files aligned automatically for tag-based releases - name: Sync app version from release tag if: startsWith(github.ref, 'refs/tags/v') run: | VERSION="${{ steps.tag_version.outputs.APP_VERSION }}" echo "Syncing package.json version to ${VERSION}" jq --arg version "${VERSION}" '.version = $version' package.json > package.json.tmp mv package.json.tmp package.json echo "Version sync diff (package.json):" git --no-pager diff -- package.json # Extract metadata for Docker - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE }} labels: | org.opencontainers.image.revision=${{ env.SHA }} tags: | type=edge,branch=$repo.default_branch type=semver,pattern=v{{version}} type=sha,prefix=,suffix=,format=short type=raw,value=latest,enable={{is_default_branch}} type=raw,value=${{ steps.tag_version.outputs.VERSION }} type=ref,event=pr,prefix=pr- # Build and push Docker image - name: Build and push Docker image id: build-and-push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false # Disable provenance to avoid unknown/unknown sbom: false # Disable sbom to avoid unknown/unknown # Load image locally for security scanning (PRs only) - name: Load image for scanning if: github.event_name == 'pull_request' uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64 load: true tags: gitea-mirror:scan cache-from: type=gha provenance: false # Disable provenance to avoid unknown/unknown sbom: false # Disable sbom to avoid unknown/unknown # Wait for image to be available in registry - name: Wait for image availability if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository run: | echo "Waiting for image to be available in registry..." sleep 5 # Add comment to PR with image details - name: Comment PR with image tag if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const prNumber = context.payload.pull_request.number; const imageTag = `pr-${prNumber}`; const imagePath = `${{ env.REGISTRY }}/${{ env.IMAGE }}:${imageTag}`.toLowerCase(); const comment = `## 🐳 Docker Image Built Successfully Your PR image is available for testing: **Image Tag:** \`${imageTag}\` **Full Image Path:** \`${imagePath}\` ### Pull and Test \`\`\`bash docker pull ${imagePath} docker run -d \ -p 4321:4321 \ -e BETTER_AUTH_SECRET=your-secret-here \ -e BETTER_AUTH_URL=http://localhost:4321 \ --name gitea-mirror-test ${imagePath} \`\`\` ### Docker Compose Testing \`\`\`yaml services: gitea-mirror: image: ${imagePath} ports: - "4321:4321" environment: - BETTER_AUTH_SECRET=your-secret-here - BETTER_AUTH_URL=http://localhost:4321 - BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:4321 \`\`\` > 💡 **Note:** PR images are tagged as \`pr-\` and built for both \`linux/amd64\` and \`linux/arm64\`. > Production images (\`latest\`, version tags) use the same multi-platform set. --- 📦 View in [GitHub Packages](https://github.com/${{ github.repository }}/pkgs/container/gitea-mirror)`; github.rest.issues.createComment({ issue_number: prNumber, owner: context.repo.owner, repo: context.repo.repo, body: comment }); # Docker Scout comprehensive security analysis - name: Docker Scout - Vulnerability Analysis & Recommendations uses: docker/scout-action@v1 if: github.event_name != 'pull_request' with: command: cves,recommendations image: ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest sarif-file: scout-results.sarif summary: true exit-code: false only-severities: critical,high write-comment: true github-token: ${{ secrets.GITHUB_TOKEN }} # Docker Scout for Pull Requests (using local image) - name: Docker Scout - Vulnerability Analysis (PR) uses: docker/scout-action@v1 if: github.event_name == 'pull_request' with: command: cves,recommendations image: local://gitea-mirror:scan sarif-file: scout-results.sarif summary: true exit-code: false only-severities: critical,high write-comment: true github-token: ${{ secrets.GITHUB_TOKEN }} # Compare to latest for PRs and pushes - name: Docker Scout - Compare to Latest uses: docker/scout-action@v1 if: github.event_name == 'pull_request' with: command: compare image: local://gitea-mirror:scan to: ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest ignore-unchanged: true only-severities: critical,high write-comment: true github-token: ${{ secrets.GITHUB_TOKEN }} # Upload security scan results to GitHub Security tab - name: Upload Docker Scout scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 if: always() continue-on-error: true with: sarif_file: scout-results.sarif sync-version-main: name: Sync package.json version back to main if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest needs: docker permissions: contents: write steps: - name: Checkout default branch uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - name: Update package.json version on main env: TAG_VERSION: ${{ github.ref_name }} TARGET_BRANCH: ${{ github.event.repository.default_branch }} run: | if [[ ! "$TAG_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then echo "::error::Release tag '${TAG_VERSION}' is invalid. Expected semver tag format like v1.2.3 or v1.2.3-rc.1" exit 1 fi APP_VERSION="${TAG_VERSION#v}" echo "Syncing ${TARGET_BRANCH}/package.json to ${APP_VERSION}" jq --arg version "${APP_VERSION}" '.version = $version' package.json > package.json.tmp mv package.json.tmp package.json if git diff --quiet -- package.json; then echo "package.json on ${TARGET_BRANCH} already at ${APP_VERSION}; nothing to commit." exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add package.json git commit -m "chore: sync version to ${APP_VERSION}" git push origin "HEAD:${TARGET_BRANCH}"