Why Tech Platforms Don't Use GitHub Actions: The Case for GitHub Apps and Centralized Runners at Scale
DEV Community

Why Tech Platforms Don't Use GitHub Actions: The Case for GitHub Apps and Centralized Runners at Scale

Originally published at labitcode.com When a software team grows from 10 developers to 50, GitHub Actions feels like magic. You drop a .github/workflows/ci.yml file into your repository, define a few steps in YAML, and GitHub spins up an Azure-hosted virtual machine to build and test your code. There are no build servers to patch, no Jenkins masters to restart, and zero upfront infrastructure to maintain. Fast-forward to an enterprise organization with 3,000 developers, 1,200 microservices, and high-frequency deployment cadences. Suddenly, that same .github/workflows directory transforms into an operational quagmire: - Pipeline Drift Across Repositories: Even with "reusable workflows", platform teams spend hundreds of engineering hours opening pull requests across thousands of individual repositories just to bump a workflow version or apply a compliance patch. - Queue Saturation & Platform Outages: As engineering teams adopt AI-assisted coding tools, pull request frequencies surge exponentially. GitHub's shared hosted-runner scheduling engine frequently degrades, leaving hundreds of developers idling in runner queues. - The Self-Hosted Runner Trap: Organizations attempt to scale with Actions Runner Controller (ARC) on Kubernetes, only to discover that while compute is local, the orchestration control plane is still GitHub's closed backend. When GitHub's Actions API hiccups, self-hosted runners freeze. - Coarse Security Boundaries: Distributing sensitive deployment credentials and organization secrets into individual repository scopes increases the attack surface for supply chain breaches and script injections. Have you ever wondered how developer platforms like Vercel, Railway, Supabase, or CircleCI handle CI/CD across millions of repositories without ever asking you to commit a .github/workflows/deploy.yml ? They don't use GitHub Actions. They use GitHub Apps combined with centralized event-driven orchestration on private infrastructure. In this article, we dissect why large-scale engineering organizations are hitting the architectural ceiling of GitHub Actions, how the GitHub App architecture works under the hood, and how migrating to a centralized runner fleet delivers 10x faster builds, absolute governance, and zero workflow maintenance. 1. The Anatomy of GitHub Actions at Enterprise Scale To understand why modern platforms avoid GitHub Actions for mission-critical ALM (Application Lifecycle Management), we must first analyze the fundamental flaws of the GitHub Actions execution model when deployed across thousands of repositories. graph TD subgraph RepoSprawl["Decentralized Model: GitHub Actions Sprawl"] R1[Repo A: .github/workflows/ci.yml] R2[Repo B: .github/workflows/ci.yml] R3[Repo C: .github/workflows/ci.yml] R1 & R2 & R3 -->|Dispatches Jobs| GHControl[GitHub Actions Closed Orchestration Plane] GHControl -->|Queue Bottleneck| GHR[GitHub-Hosted / ARC Runners] end style RepoSprawl fill:#1e1b4b,stroke:#4338ca,stroke-width:2px,color:#fff The Fallacy of "Reusable Workflows" GitHub introduced Reusable Workflows (workflow_call ) to reduce YAML duplication. While an improvement over copy-pasting 200 lines of YAML across repositories, reusable workflows suffer from a critical flaw: caller-side coupling. Each repository must still maintain a "caller" workflow file: # Inside microservice-auth/.github/workflows/pipeline.yml name: Enterprise CI on: [push, pull_request] jobs: build-and-test: uses: my-org/shared-workflows/.github/workflows/standard-ci.yml@v2.4.1 secrets: inherit Consider what happens when: - A critical zero-day vulnerability is discovered in a build step or dependency scanner, requiring an immediate upgrade to @v2.5.0 . - A compliance mandate changes required status checks for SOC2 or ISO 27001. - A rogue repository admin overrides inputs, modifies trigger conditions, or simply points to @v1.0.0 to bypass slow security gates. To apply an atomic update, platform teams must orchestrate massive automated PR campaigns across thousands of repositories using custom scripts or bot accounts. You are left managing PR merges, broken branch protection rules, rebases, and merge conflicts. This is not infrastructure-as-code; it is distributed configuration debt. Closed Orchestration vs. Self-Hosted Illusions A common enterprise response to GitHub-hosted runner latency is deploying self-hosted runners via Kubernetes (such as Actions Runner Controller, or ARC). However, ARC only hosts the ephemeral execution container. The scheduler remains closed inside GitHub's infrastructure: [Git Push] โž” [GitHub Webhook Hub] โž” [GitHub Internal Queue] โž” [Long-Polling ARC Listener] โž” [Pod Spin-up] When GitHub's Actions infrastructure suffers from API rate limits, database degradation, or webhooks backlog, your local Kubernetes cluster sits completely idle. The runners cannot poll jobs that GitHub's scheduler has failed to dispatch. 2. The Paradigm Shift: GitHub Apps + Centralized Orchestration Platforms like Vercel and Netlify never ask you to manage a workflow file. When you push code or open a pull request, the platform detects your project, runs static analysis, executes tests, provisions preview environments, and reports status checks directly inside the GitHub Pull Request interface. How is this accomplished? Through the GitHub App Architecture. Instead of scattering workflow files across repositories, the organization creates and installs a single GitHub App across all organization repositories. How the GitHub App Model Works - Zero-Touch Repository Setup: Application repositories contain zero workflow files ( .github/workflows/ does not even need to exist). - Event-Driven Webhook Ingestion: When a developer pushes code, merges a branch, or opens a pull request, GitHub sends an HMAC-SHA256-signed webhook payload directly to the platform's central API Gateway. - Central State Machine & Queue: The gateway verifies the signature and dispatches the build job into a high-throughput queue (e.g., Temporal, AWS SQS, or Redis BullMQ). - Dedicated Ephemeral Compute: Worker nodes running on private cloud infrastructure (Firecracker microVMs, Nomad, or Kubernetes) execute the build pipelines against warm, persistent NVMe caches. - Real-Time Feedback via Checks API: The centralized worker interacts directly with the GitHub Checks API, creating rich check runs, posting annotations directly onto code diffs, and linking to dedicated observability dashboards. sequenceDiagram autonumber actor Dev as Developer participant GH as GitHub Enterprise participant App as Central ALM Gateway participant Queue as Event Queue (Redis/Temporal) participant Fleet as Dedicated Runner Fleet participant Checks as GitHub Checks API Dev->>GH: git push origin feature/auth GH->>App: POST /api/webhooks (event: push, HMAC signed) App->>App: Verify HMAC-SHA256 signature App->>Checks: Create Check Run ("Security & Unit Tests" - In Progress) App->>Queue: Push Job Spec { commit, repo, installationId } Queue->>Fleet: Lease Job & Boot Ephemeral MicroVM Fleet->>Fleet: Mount Warm Cache & Run Build/Tests Fleet->>Checks: Update Check Run (Annotations, Diff Comments, Success) Checks-->>GH: Render Green Check & Annotations in PR UI 3. Implementation: Centralized ALM Orchestrator Here are the fundamental building blocks of a centralized GitHub App orchestrator. 1. Ingesting Webhooks Safely import { Webhooks } from "@octokit/webhooks"; import { Octokit } from "@octokit/rest"; import { createAppAuth } from "@octokit/auth-app"; interface PipelinePayload { repository: string; commitSha: string; installationId: number; branch: string; } const webhooks = new Webhooks({ secret: process.env.GITHUB_WEBHOOK_SECRET!, }); webhooks.on("push", async ({ payload }) => { const repository = payload.repository.full_name; const commitSha = payload.after; const installationId = payload.installation?.id; const branch = payload.ref.replace("refs/heads/", ""); if (!installationId || commitSha === "0000000000000000000000000000000000000000") { return; // Ignore branch deletions } // Dispatch job to internal queue (Temporal / BullMQ / SQS) await dispatchPipelineJob({ repository, commitSha, installationId, branch, }); }); 2. Communicating via the GitHub Checks API export async function createPipelineCheck(payload: PipelinePayload) { const octokit = new Octokit({ authStrategy: createAppAuth, auth: { appId: process.env.GITHUB_APP_ID!, privateKey: process.env.GITHUB_PRIVATE_KEY!, installationId: payload.installationId, }, }); const [owner, repo] = payload.repository.split("/"); const check = await octokit.rest.checks.create({ owner, repo, name: "Enterprise ALM / Compliance & Tests", head_sha: payload.commitSha, status: "in_progress", started_at: new Date().toISOString(), output: { title: "Running Enterprise Validation Suite", summary: "Initializing secure microVM runner and checking pipeline policies.", }, }); return { octokit, checkId: check.data.id, owner, repo }; } 3. Posting Line-Level Annotations on Pull Requests export async function completePipelineCheck( octokit: Octokit, owner: string, repo: string, checkId: number, failures: Array ) { const hasFailures = failures.length > 0; await octokit.rest.checks.update({ owner, repo, check_run_id: checkId, status: "completed", conclusion: hasFailures ? "failure" : "success", completed_at: new Date().toISOString(), output: { title: hasFailures ? "Compliance & Test Failures Detected" : "All Checks Passed", summary: hasFailures ? Found ${failures.length} issues that violate enterprise engineering standards. : "Automated test suite, linting, and security scans completed successfully.", annotations: failures.map((f) => ({ path: f.path, start_line: f.line, end_line: f.line, annotation_level: "failure", message: f.message, title: "Enterprise Quality Gate Violation", })), }, }); } 4. Head-to-Head: GitHub Actions vs. Centralized GitHub App | Dimension | GitHub Actions (Standard Model) | Centralized GitHub App Platform | |---|---|---| | Pipeline Governance | Scatte

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.