Skip to content

Runner Group Management

Runner groups are security boundaries. Organize by trust level. Restrict by default. Enforce with workflow controls.

The Risk

Without runner groups, all self-hosted runners are available to all repositories. One compromised repository means access to production runners with elevated permissions. Malicious workflows can target high-value runners for lateral movement.

Why Runner Groups Matter

Self-hosted runners have different security profiles. Production runners have network access to production systems. Development runners are isolated. GPU runners cost money. Compliance runners must audit every job.

Without groups: All runners available to all repositories. No trust boundaries. No access control.

With groups: Security boundaries between runner types. Repository allow-lists. Workflow restrictions. Audit trails.

Runner Group Organization Strategies

Organize runners by security requirements, compliance needs, and operational constraints.

flowchart TB
    A["All Repositories"] --> B{"Trust Level"}

    B --> C["Public Repos"]
    B --> D["Internal Repos"]
    B --> E["Private Repos"]

    C --> C1["public-runners group<br/>No self-hosted access"]

    D --> D1["dev-runners group<br/>Non-production access"]
    D --> D2["test-runners group<br/>Isolated test networks"]

    E --> E1["prod-runners group<br/>Production deployment"]
    E --> E2["compliance-runners group<br/>Audited workloads"]
    E --> E3["gpu-runners group<br/>High-cost resources"]

    %% Ghostty Hardcore Theme
    style C1 fill:#f92572,color:#1b1d1e
    style D1 fill:#e6db74,color:#1b1d1e
    style D2 fill:#e6db74,color:#1b1d1e
    style E1 fill:#a6e22e,color:#1b1d1e
    style E2 fill:#a6e22e,color:#1b1d1e
    style E3 fill:#fd971e,color:#1b1d1e

Strategy 1: Trust-Based Organization

Organize by repository trust level and workflow sensitivity.

Group Name Trust Level Repository Access Network Scope Credentials Use Case
public-runners Untrusted Public repos only Internet-only None Never use self-hosted for public repos
dev-runners Low Development repos Isolated dev network Development service accounts Feature development, testing
staging-runners Medium Staging repos Staging network Staging credentials Pre-production validation
prod-runners High Production repos Production network Production OIDC Production deployments
compliance-runners Highest Compliance-approved repos Audited networks Minimal credentials HIPAA, PCI-DSS, SOC2 workloads

Best Practice: Never allow self-hosted runners for public repositories. External contributors can submit malicious workflows that execute on your infrastructure.

Strategy 2: Workload-Based Organization

Organize by job type and resource requirements.

Group Name Job Type Repository Access Resource Profile Cost Model Use Case
ci-runners CI/CD All repos 8 CPU, 16GB RAM Standard Build, test, lint
deploy-runners Deployment Release repos only 4 CPU, 8GB RAM Standard Cloud deployments
gpu-runners ML/AI ML repos only 16 CPU, 64GB RAM, 1 GPU High-cost Model training, inference
build-runners Compilation Build repos 16 CPU, 32GB RAM Standard Large codebases, monorepo builds
integration-runners Integration tests Test repos 8 CPU, 16GB RAM Standard Database integration, API tests

Best Practice: Isolate high-cost runners (GPU, high-memory) to prevent accidental usage from unauthorized repositories. Monitor for cost anomalies.

Strategy 3: Environment-Based Organization

Organize by deployment environment and protection rules.

Group Name Environment Protection Rules Approval Required Network Access Use Case
dev-runners Development None No Dev VPC Rapid iteration
staging-runners Staging Branch protection No Staging VPC Pre-prod testing
prod-runners Production Environment protection Yes Prod VPC Production deployments
dr-runners Disaster Recovery Manual trigger only Yes DR VPC Failover scenarios

Best Practice: Combine runner groups with GitHub environment protection rules. Require manual approval before jobs execute on production runners.

Strategy 4: Compliance-Based Organization

Organize by regulatory requirements and audit needs.

Group Name Compliance Scope Audit Logging Data Classification Network Isolation Use Case
pci-runners PCI-DSS Full audit logs to SIEM Cardholder data Segmented PCI network Payment processing
hipaa-runners HIPAA Encrypted logs, BAA PHI HIPAA-compliant VPC Healthcare data
fedramp-runners FedRAMP CloudWatch + Splunk CUI FedRAMP-authorized VPC Government workloads
sox-runners SOX Immutable logs Financial data Audited network Financial reporting
standard-runners None Standard GitHub logs Public/internal Standard network Non-regulated workloads

Best Practice: Dedicated compliance runners with enhanced logging, immutable audit trails, and network segmentation per regulatory requirements.

Repository Access Restrictions

Control which repositories can use which runner groups.

GitHub Enterprise Organization Settings

Runner groups are configured at the organization level with repository access controls.

Configuration Path: Organization Settings → Actions → Runner groups

Restriction Levels

Level 1: All Repositories (Least Secure)

All repositories in the organization can access the runner group.

Use Case: Development runners for non-sensitive workloads.

Risk: Compromised repository gains access to all runners in the group.

# Runner group configuration (Settings UI)
Group: dev-runners
Access: All repositories
Workflow restrictions: None

Explicit allow-list of repositories that can access the runner group.

Use Case: Production runners, compliance runners, high-cost runners.

Risk: Lower risk, but requires maintenance as new repositories are created.

# Runner group configuration (Settings UI)
Group: prod-runners
Access: Selected repositories
Repositories:
  - org/production-api
  - org/production-web
  - org/production-infra
Workflow restrictions: Selected workflows
Level 3: Private Repositories Only

Only private repositories can access the runner group. Public repositories are blocked.

Use Case: Internal runners that should never execute public repository code.

Risk: Lower risk, but does not prevent compromised private repositories from accessing runners.

# Runner group configuration (Settings UI)
Group: internal-runners
Access: Private repositories
Workflow restrictions: None

API-Based Configuration

Automate runner group configuration using GitHub API.

#!/bin/bash
# Create runner group with repository restrictions

set -euo pipefail

ORG="your-organization"
GROUP_NAME="prod-runners"
RUNNER_GROUP_ID="123"
ALLOWED_REPOS=(
  "production-api"
  "production-web"
  "production-infra"
)

# Create runner group
gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  "/orgs/${ORG}/actions/runner-groups" \
  -f name="${GROUP_NAME}" \
  -f visibility="selected" \
  -F allows_public_repositories=false

# Add repositories to runner group
for repo in "${ALLOWED_REPOS[@]}"; do
  REPO_ID=$(gh api "/repos/${ORG}/${repo}" --jq '.id')

  gh api \
    --method PUT \
    -H "Accept: application/vnd.github+json" \
    "/orgs/${ORG}/actions/runner-groups/${RUNNER_GROUP_ID}/repositories/${REPO_ID}"

  echo "Added ${repo} to runner group ${GROUP_NAME}"
done

Repository Access Verification

Audit which repositories can access which runner groups.

#!/bin/bash
# Audit runner group repository access

set -euo pipefail

ORG="your-organization"

# List all runner groups
echo "==> Auditing runner group access for ${ORG}"

gh api "/orgs/${ORG}/actions/runner-groups" --paginate --jq '.runner_groups[]' | while read -r group; do
  GROUP_ID=$(echo "$group" | jq -r '.id')
  GROUP_NAME=$(echo "$group" | jq -r '.name')
  VISIBILITY=$(echo "$group" | jq -r '.visibility')

  echo ""
  echo "Runner Group: ${GROUP_NAME} (${VISIBILITY})"

  if [[ "$VISIBILITY" == "selected" ]]; then
    # List repositories with access
    gh api "/orgs/${ORG}/actions/runner-groups/${GROUP_ID}/repositories" --paginate \
      | jq -r '.repositories[].full_name' \
      | while read -r repo; do
        echo "  - ${repo}"
      done
  else
    echo "  - Access: All repositories"
  fi
done

Workflow Restrictions for Sensitive Runners

Control which workflows can execute on specific runner groups.

Why Workflow Restrictions?

Repository access controls specify which repositories can use runners. Workflow restrictions specify which workflow files within those repositories can execute on those runners.

Scenario: Production runners should only execute deployment workflows, not arbitrary CI workflows.

Without workflow restrictions: Any workflow file in allowed repositories can use production runners.

With workflow restrictions: Only approved workflow files (e.g., .github/workflows/deploy-production.yml) can use production runners.

Workflow Restriction Configuration

Configuration Path: Organization Settings → Actions → Runner groups → Workflow access

Option 1: No Restrictions (Default)

All workflows in allowed repositories can use the runner group.

Use Case: Development runners, non-sensitive workloads.

Risk: Any workflow file can target these runners.

Explicit allow-list of workflow files that can use the runner group.

Use Case: Production runners, compliance runners, high-value runners.

Risk: Requires maintenance as new deployment workflows are created.

# Runner group configuration (Settings UI)
Group: prod-runners
Workflow access: Selected workflows
Allowed workflows:
  - org/production-api/.github/workflows/deploy-production.yml@refs/heads/main
  - org/production-web/.github/workflows/deploy-production.yml@refs/heads/main
  - org/production-infra/.github/workflows/terraform-apply.yml@refs/heads/main

Format: {owner}/{repo}/.github/workflows/{workflow}.yml@{ref}

Best Practice: Pin workflows to refs/heads/main to prevent malicious branches from bypassing restrictions.

Workflow Restriction Patterns

Pattern 1: Production Deployment Workflows Only

Restrict production runners to deployment workflows verified by security team.

# prod-runners group configuration
Allowed workflows:
  - org/app-api/.github/workflows/deploy-prod.yml@refs/heads/main
  - org/app-web/.github/workflows/deploy-prod.yml@refs/heads/main
  - org/app-worker/.github/workflows/deploy-prod.yml@refs/heads/main

Enforcement: CI workflows, test workflows, and feature branch workflows cannot use production runners.

Pattern 2: Compliance Workflows with Audit Trail

Restrict compliance runners to audited workflows with immutable logs.

# hipaa-runners group configuration
Allowed workflows:
  - org/patient-portal/.github/workflows/deploy-hipaa.yml@refs/heads/main
  - org/ehr-integration/.github/workflows/deploy-hipaa.yml@refs/heads/main

Additional Controls:

  • Workflows require manual approval (environment protection rules)
  • All jobs logged to immutable SIEM
  • Network isolated to HIPAA-compliant VPC

Pattern 3: Cost-Control for GPU Runners

Restrict expensive GPU runners to approved ML training workflows.

# gpu-runners group configuration
Allowed workflows:
  - org/ml-training/.github/workflows/train-model.yml@refs/heads/main
  - org/ml-inference/.github/workflows/batch-inference.yml@refs/heads/main

Monitoring: Alert on unexpected GPU runner usage or cost spikes.

API-Based Workflow Restrictions

Automate workflow restriction configuration using GitHub API.

```bash

Comments