Skip to content

Pre-Harden VM Images

Use Packer to create hardened VM images with security controls baked in. Pre-configured images reduce provisioning time and ensure consistent security baselines across all ephemeral runners.

Kubernetes-Based Ephemeral Runners (ARC)

kind: Namespace
metadata:
  name: actions-runner-system
---

# Install cert-manager (required for ARC)

# helm repo add jetstack <https://charts.jetstack.io>

# helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true

# Install ARC controller

# helm repo add actions-runner-controller <https://actions-runner-controller.github.io/actions-runner-controller>

# helm install actions-runner-controller actions-runner-controller/actions-runner-controller \

# --namespace actions-runner-system \

# --set authSecret.github_token=<GITHUB_PAT>

Ephemeral Runner Deployment

Configure runner pools with ephemeral mode enabled.

# arc-ephemeral-runners.yml
# Ephemeral runner deployment for ARC

apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: ephemeral-runners
  namespace: actions-runner-system
spec:
  replicas: 3  # Minimum runners available
  template:
    spec:
      repository: my-org/my-repo
      ephemeral: true  # Critical: Destroy pod after single job
      labels:
        - self-hosted
        - ephemeral
        - kubernetes
        - arc

      # Pod security context
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault

      # Container security
      containerMode: kubernetes
      containers:
        - name: runner
          image: ghcr.io/actions/actions-runner:latest
          imagePullPolicy: Always
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              memory: "512Mi"
              cpu: "500m"
            limits:
              memory: "2Gi"
              cpu: "2000m"
          volumeMounts:
            - name: work
              mountPath: /runner/_work
      volumes:
        - name: work
          emptyDir:
            sizeLimit: 8Gi

ARC Horizontal Autoscaler

Scale runners based on job queue depth.

# arc-autoscaler.yml
# Scale runners based on pending GitHub Actions jobs

apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
  name: ephemeral-runners-autoscaler
  namespace: actions-runner-system
spec:
  scaleTargetRef:
    name: ephemeral-runners
  minReplicas: 0  # Scale to zero when idle
  maxReplicas: 20
  metrics:
    - type: TotalNumberOfQueuedAndInProgressWorkflowRuns
      repositoryNames:
        - my-org/my-repo
  scaleDownDelaySecondsAfterScaleOut: 300  # Wait 5 minutes before scaling down
  scaleUpTriggers:
    - githubEvent:
        workflowJob: {}
      duration: 5m  # Scale up for 5 minutes after trigger

Network Policies for ARC Runners

Restrict network access for runner pods.

# arc-network-policy.yml
# Deny-by-default network policy for runner pods

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ephemeral-runner-network-policy
  namespace: actions-runner-system
spec:
  podSelector:
    matchLabels:
      app: ephemeral-runners
  policyTypes:
    - Egress
  egress:
    # Allow DNS
    - to:
        - namespaceSelector:
            matchLabels:
              name: kube-system
      ports:
        - protocol: UDP
          port: 53

    # Allow GitHub API
    - to:
        - ipBlock:
            cidr: 140.82.112.0/20
        - ipBlock:
            cidr: 143.55.64.0/20
      ports:
        - protocol: TCP
          port: 443

    # Allow package registries (add as needed)
    - ports:
        - protocol: TCP
          port: 443

    # Deny cloud metadata endpoints
    - to:
        - ipBlock:
            cidr: 169.254.169.254/32
      ports: []  # Empty ports = deny all

Pod Security Standards

Enforce restricted security policies for runner pods.

# arc-pod-security.yml
# Pod Security Admission for runner namespace

apiVersion: v1
kind: Namespace
metadata:
  name: actions-runner-system
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

State Isolation Best Practices

Ensure zero state leakage between jobs.

Filesystem Cleanup Verification

```bash

!/bin/bash

/opt/runner-orchestrator/verify-cleanup.sh

Verify ephemeral runner destroys all state

set -euo pipefail

RUNNER_ID="${1:?Runner ID required}"

echo "==> Verifying cleanup for runner: ${RUNNER_ID}"

Check container is destroyed

if podman ps -a | grep -q "${RUNNER_ID}"; then echo "ERROR: Container ${RUNNER_ID} still exists" exit 1 fi

Check no filesystem artifacts remain

if [[ -d "/tmp/runner-${RUNNER_ID}" ]]; then

Comments