Read more of this story at Slashdot.
Read more of this story at Slashdot.
It’s time to make Shift happen.
Game Off, GitHub’s annual month-long game jam, returns for its 14th year this November. Thousands of developers have joined us over the years to make everything from polished platformers to wonderfully weird experiments.
Whether you’re making your first game or your fiftieth, we’d love for you to join us.
The theme for Game Off 2026 is SHIFT!
Shift gears. Shift perspectives. Shift dimensions. Shift bits. Work the night shift. Or make a game where the only buttons are the Shift keys.
How you interpret the theme is entirely up to you.
You have until December 1, 2026, at 13:37 PST to build and submit a game inspired by SHIFT. Strong or loose inspirations apply!
Of course, you could also build a game about shifting tectonic plates, shape-shifting aliens, shifting alliances, shifting sands, or a particularly ambitious stick shift.
Don’t overthink it. Pick an interpretation that interests you and see where it takes you.
Pro tip: Start with one mechanic and one playable level. Get those working before adding the multiverse. Future you (and your bug tracker) will thank you.
Need another perspective? Try brainstorming with GitHub Copilot. Give it the theme, a few constraints, and see what it comes up with:
Act as an indie game designer. Suggest 10 original game concepts inspired by SHIFT, each with a unique core mechanic, genre, and technical twist. Prioritize ideas a solo developer could prototype in a weekend.
Work alone or form a team. Use whichever programming languages, game engines, frameworks, libraries, and tools make sense for your game.
Here’s the fun part: you decide who wins.
Once submissions close, it’s your turn to play and rate the games your fellow developers have created. Every vote helps determine which games take home the bragging rights.
Entries will be evaluated in the following categories:
Voting will end on January 1, 2027, at 13:37 PST. Winners will be announced on the GitHub Blog and GitHub social channels shortly thereafter.
Game Off is intentionally relaxed, but there are a few guidelines to help keep it fair and fun:
That’s it. Keep it creative, respectful, and fun… and make sure your final code is pushed before the deadline.
You don’t need to be an expert to participate. Many Game Off participants create their first game during the jam.
Some use established engines. Some build their own. Others make games for old consoles, unusual hardware, terminals, calculators, or platforms we assumed had been lost to time.
However you build your game, there’s no single correct way to play.
Here are a few engines and frameworks you might want to explore:
Looking for teammates or a place to share progress? The Game Off 2026 Community is a great place to ask questions, share progress, and look for teammates. There’s also a friendly community-run Discord server.
Game Off is the perfect opportunity to check it out. Version control pun fully intended.
Whether you spend November shifting pixels, shifting bits, shifting timelines, or shifting unfinished tasks into the final 24 hours, you’ll be part of a global community making games together.
So take a different perspective, change the rules, and see what happens when you press ⇧.
Good luck and have fun! Can’t wait to play, err, test all your games! <3
The post Game Off 2026: The theme is SHIFT appeared first on The GitHub Blog.
A practical architecture using KEDA autoscaling, GitHub App authentication, private Key Vault access, Microsoft Container Registry, and GitHub OIDC
Implementation guide and field learnings
|
What this architecture delivers Β· A repository-scoped runner that exists only for one workflow Β· Automatic scale-from-zero with an Azure Container Apps Event Job and KEDA Β· GitHub App authentication instead of a personal access token Β· Private secret retrieval using a managed identity, private endpoint and private DNS Β· Secretless Azure deployment from workflows through GitHub OIDC Β· No customer-managed Azure Container Registry for the runner image |
This article focuses on the final event-driven architecture and the implementation decisions that made it secure, repeatable and useful for real infrastructure workflows.
GitHub-hosted runners are the simplest option for many teams, but some workflows need private network access, organization-approved tooling or tighter control over the execution boundary. Long-running self-hosted virtual machines solve those needs, but introduce patching, capacity planning and idle cost.
Azure Container Apps Jobs provide a serverless execution boundary for finite tasks. With the KEDA GitHub runner scaler, an execution can start when a matching workflow is queued, register one ephemeral runner, process one job and terminate. Setting the minimum execution count to zero preserves scale-to-zero when the queue is empty.
The important design work begins around that lifecycle: how the runner image is prepared, how GitHub is authenticated, where secrets are stored, how private access is enforced, and which identity is allowed to deploy Azure resources.
Figure 1. End-to-end event-driven architecture and trust boundaries.
A matching GitHub Actions workflow enters the queue. The KEDA github-runner scaler polls GitHub using GitHub App authentication and starts an Azure Container Apps Event Job when the queue length reaches the configured target.
The event job pulls a Microsoft-maintained base image from Microsoft Container Registry. At startup, the container installs or verifies the required toolchain, obtains GitHub App material from a private Azure Key Vault, generates short-lived GitHub credentials and registers a repository-level ephemeral runner.
The workflow executes on that runner. When Azure access is needed, the workflow obtains a GitHub OIDC token and exchanges it through a federated credential on a separate Microsoft Entra deployment identity. Azure RBAC limits that identity to the intended deployment scope.
After one workflow, the runner exits and the Container Apps job completes. With no remaining matching work, the solution returns to zero executions.
Figure 2. Private secret retrieval and short-lived GitHub token exchange.
Figure 3. Image strategy trade-offs. Both options are valid; choose by operating requirements.
This implementation intentionally pulls a Microsoft base image from MCR and installs the required tools when the job starts. That removes Azure Container Registry, an image build pipeline and registry access configuration from the runner platform.
The cost of that simplification is startup latency. A pre-baked image can begin runner registration almost immediately because Git, Terraform, PowerShell and the GitHub runner are already present. Runtime bootstrap must download and install missing components before the runner becomes available. The observed delay will vary with image-pull time, package endpoint latency, tool size and regional network conditions, so measure queue-to-ready time with representative workflows rather than relying on a generic estimate.
Runtime bootstrap also introduces external dependencies. If a package repository, GitHub release endpoint or download source is unavailable, the runner cannot become ready. Pin versions, validate downloads, use bounded retries and record each bootstrap phase in logs.
A pre-baked image is preferable when startup time is strict, workflows run frequently, software must be scanned before execution, or image promotion and rollback are required. The MCR-plus-bootstrap model is attractive when the goal is to avoid a dedicated registry and custom image lifecycle, while accepting additional cold-start time.
|
Decision factor |
MCR + runtime bootstrap |
Pre-baked image + ACR |
|
Ready time |
Higher cold-start latency |
Typically faster |
|
Artifact control |
Depends on base-image and bootstrap pinning |
Immutable tag/digest and release promotion |
|
Platform footprint |
No dedicated ACR |
Requires ACR and image-pull authorization |
|
Maintenance |
Bootstrap script and download endpoints |
Dockerfile, build, scan, patch and publish |
|
Best fit |
Lower-frequency or simpler runner platform |
High-frequency or controlled enterprise image supply chain |
Replace the example values with approved environment-specific names and scopes. Run the PowerShell-oriented Azure CLI commands from Cloud Shell or a current Azure CLI workstation.
|
Security boundary before you begin Use a private GitHub repository. Do not place a GitHub App private key, installation token, runner registration token, Azure access token or client secret in source control. The only long-lived GitHub credential in this design is the GitHub App private key, which is stored in Azure Key Vault and used only to mint short-lived tokens. |
Use one naming block throughout the deployment. The values below are examples only.
$SubscriptionId = "<subscription-id>" $TenantId = "<tenant-id>" $Location = "centralindia" $ResourceGroup = "rg-aca-github-runner" $VnetName = "vnet-aca-github-runner" $VnetCidr = "10.50.0.0/16" $AcaSubnetName = "snet-aca" $AcaSubnetCidr = "10.50.1.0/27" $PeSubnetName = "snet-private-endpoints" $PeSubnetCidr = "10.50.2.0/27" $EnvironmentName = "cae-github-runner" $JobName = "caj-github-runner-event" $IdentityName = "id-github-runner" $KeyVaultName = "<globally-unique-key-vault-name>" $LogWorkspace = "law-aca-github-runner" $GitHubOwner = "<github-owner>" $GitHubRepo = "<repository-name>" $RunnerLabel = "aca-linux" $RunnerVersion = "<tested-runner-version>" $TerraformVersion = "<tested-terraform-version>" $PowerShellVersion = "<tested-powershell-version>" az account set --subscription $SubscriptionId az group create --name $ResourceGroup --location $Location
Use separate, non-overlapping subnets for the delegated Container Apps environment and the Key Vault private endpoint.
az network vnet create ` --resource-group $ResourceGroup ` --name $VnetName ` --location $Location ` --address-prefixes $VnetCidr az network vnet subnet create ` --resource-group $ResourceGroup ` --vnet-name $VnetName ` --name $AcaSubnetName ` --address-prefixes $AcaSubnetCidr ` --delegations Microsoft.App/environments az network vnet subnet create ` --resource-group $ResourceGroup ` --vnet-name $VnetName ` --name $PeSubnetName ` --address-prefixes $PeSubnetCidr ` --disable-private-endpoint-network-policies true $AcaSubnetId = az network vnet subnet show ` --resource-group $ResourceGroup ` --vnet-name $VnetName ` --name $AcaSubnetName ` --query id --output tsv
Validation: Confirm that the ACA subnet shows delegation to Microsoft.App/environments and that the private-endpoint subnet has private endpoint network policies disabled.
This identity is the bootstrap identity. Its steady-state purpose is to read the GitHub App material from Key Vault. Do not use it as the Terraform deployment identity.
|
Important Because public network access is disabled, secret creation must occur from an approved private network path or through an authorized automation process that can reach the vault privately. Adding a laptop public IP does not override PublicNetworkAccess=Disabled. |
In GitHub, create a GitHub App owned by the account or organization that owns the target repository. Disable webhooks unless another use case requires them. Grant only the permissions required by the KEDA scaler and runner registration APIs.
Record the App ID and installation ID. Generate a private key and protect the downloaded PEM file. The private key is the long-lived GitHub credential for this architecture.
Run the following commands only from a trusted system that can reach the Key Vault private endpoint and has temporary secret-management permission. Use separate PEM and base64 forms because the KEDA trigger consumes PEM content while the runtime bootstrap can safely reconstruct the file from base64.
$GitHubAppId = "<github-app-id>" $GitHubInstallationId = "<github-installation-id>" $GitHubPrivateKeyPath = "<local-path-to-private-key.pem>" $GitHubPrivateKeyPem = Get-Content ` -Path $GitHubPrivateKeyPath ` -Raw $GitHubPrivateKeyBase64 = [Convert]::ToBase64String( [System.IO.File]::ReadAllBytes($GitHubPrivateKeyPath) ) az keyvault secret set --vault-name $KeyVaultName ` --name "github-app-id" --value $GitHubAppId az keyvault secret set --vault-name $KeyVaultName ` --name "github-installation-id" --value $GitHubInstallationId az keyvault secret set --vault-name $KeyVaultName ` --name "github-app-private-key-pem" --value $GitHubPrivateKeyPem az keyvault secret set --vault-name $KeyVaultName ` --name "github-app-private-key-b64" --value $GitHubPrivateKeyBase64
Validation: List secret names and versions, but never print secret values in logs. Remove any temporary Secrets Officer assignment after the secrets have been written.
Validation: Confirm that the environment provisioning state is Succeeded and that the infrastructure subnet matches the delegated ACA subnet.
The MCR image is prepared at runtime. Add fail-fast behavior and phase markers for installation, Key Vault, GitHub authentication and runner registration.
set -euo pipefail log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*"; } log "Installing base packages" tdnf install -y git tar gzip unzip ca-certificates curl openssl log "Installing pinned Terraform and PowerShell versions" # Download only approved versions. Validate checksums before installation. # Install Terraform to /usr/local/bin and PowerShell to an approved path. log "Retrieving GitHub App material from private Key Vault" az login --identity --client-id "${UAMI_CLIENT_ID}" --allow-no-subscriptions >/dev/null APP_ID=$(az keyvault secret show --vault-name "${KEY_VAULT_NAME}" --name github-app-id --query value -o tsv) INSTALLATION_ID=$(az keyvault secret show --vault-name "${KEY_VAULT_NAME}" --name github-installation-id --query value -o tsv) PRIVATE_KEY_B64=$(az keyvault secret show --vault-name "${KEY_VAULT_NAME}" --name github-app-private-key-b64 --query value -o tsv) umask 077 printf '%s' "${PRIVATE_KEY_B64}" | base64 -d > /tmp/github-app-key.pem log "Generating a short-lived GitHub App JWT" # Create an RS256 JWT with iat slightly in the past and exp within GitHub limits. # Do not echo the JWT or private key. log "Requesting installation and registration tokens" # POST /app/installations/${INSTALLATION_ID}/access_tokens using the JWT. # POST /repos/${GITHUB_OWNER}/${GITHUB_REPO}/actions/runners/registration-token # using the installation token. log "Downloading and configuring the pinned GitHub Actions runner" mkdir -p /actions-runner cd /actions-runner # Download the tested runner release and validate its checksum before extraction. export RUNNER_ALLOW_RUNASROOT=1 ./config.sh \ --url "https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}" \ --token "${REGISTRATION_TOKEN}" \ --name "aca-${CONTAINER_APP_JOB_EXECUTION_NAME:-runner}" \ --labels "${RUNNER_LABEL}" \ --ephemeral \ --unattended \ --disableupdate rm -f /tmp/github-app-key.pem unset PRIVATE_KEY_B64 JWT INSTALLATION_TOKEN REGISTRATION_TOKEN log "Starting ephemeral runner" exec ./run.sh
|
Implementation note The abbreviated sections above should be implemented as tested functions in the deployment repository. Keep the JWT and API response parsing logic in source control, but never include private-key material or emitted tokens. Pin release URLs and validate checksums. |
Use an event-triggered job with minimum executions 0. The KEDA scale rule and runner registration must use the same repository and custom label. The following manifest fragment shows the required contract; property names can vary by the Container Apps API version, so generate the final resource through your approved Bicep, Terraform or ARM template and validate the deployed scale rule with Azure CLI.
triggerType: Event eventTriggerConfig: replicaCompletionCount: 1 parallelism: 1 replicaRetryLimit: 0 replicaTimeout: 3600 scale: pollingInterval: 30 minExecutions: 0 maxExecutions: 3 rules: - name: github-runner type: github-runner metadata: owner: "<github-owner>" runnerScope: repo repos: "<repository-name>" labels: "<custom-runner-label>" targetWorkflowQueueLength: "1" enableEtags: "true" applicationID: "<github-app-id>" installationID: "<github-installation-id>" authentication: appKey: "<ACA secret containing the GitHub App PEM>" template: containers: - name: github-runner image: mcr.microsoft.com/deploymentenvironments/runners/core:<tested-tag-or-digest> resources: cpu: 1.0 memory: 2Gi env: - name: GITHUB_OWNER value: "<github-owner>" - name: GITHUB_REPO value: "<repository-name>" - name: RUNNER_LABEL value: "<custom-runner-label>" - name: KEY_VAULT_NAME value: "<key-vault-name>" - name: UAMI_CLIENT_ID value: "<runtime-uami-client-id>"
The KEDA control plane needs the App ID, installation ID and PEM private key to inspect the queue. The runner container needs the App material independently to obtain its registration token. Treat these as two GitHub authentication consumers even when they use the same GitHub App.
Validation: Inspect properties.configuration.eventTriggerConfig.scale.rules and confirm owner, repository, scope, label, queue target, ETags, min/max executions and polling interval. Verify that the job has the UAMI attached.
This identity is used by workflow steps such as Terraform. It is intentionally separate from the runner UAMI.
$DeploymentAppName = "app-gh-aca-deployment" $DeploymentClientId = az ad app create ` --display-name $DeploymentAppName ` --query appId --output tsv az ad sp create --id $DeploymentClientId $DeploymentSpObjectId = az ad sp show ` --id $DeploymentClientId ` --query id --output tsv $DeploymentScope = az group show ` --name $ResourceGroup ` --query id --output tsv az role assignment create ` --assignee-object-id $DeploymentSpObjectId ` --assignee-principal-type ServicePrincipal ` --role Contributor ` --scope $DeploymentScope
Create the federated credential. Bind the subject to the exact branch or GitHub environment allowed to deploy.
@" { "name": "github-main", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:${GitHubOwner}/${GitHubRepo}:ref:refs/heads/main", "description": "GitHub Actions OIDC for the main branch", "audiences": ["api://AzureADTokenExchange"] } "@ | Set-Content -Path .\github-federated-credential.json az ad app federated-credential create ` --id $DeploymentClientId ` --parameters .\github-federated-credential.json
|
Least privilege Contributor at resource-group scope is a practical implementation starting point, not a universal target. Replace it with a narrower built-in or custom role when the workflowβs required Azure operations are known. Do not grant this principal Key Vault secret-reading access unless the workflow genuinely needs it. |
Store the identifiers as GitHub repository or environment variables/secrets according to your governance model. There is no AZURE_CLIENT_SECRET.
If production deployment requires approvals, target a protected GitHub environment and bind the federated credential subject to that environment instead of directly to a branch.
Before triggering: Confirm that current event-job executions are zero and no repository runner is permanently online.
Queue detection: Trigger the workflow and verify that it waits for the custom label rather than running on another self-hosted runner.
KEDA scale-up: Within the configured polling and platform startup interval, confirm that Container Apps creates an event-job execution.
Private bootstrap: Verify logs show managed identity login, private Key Vault retrieval and successful GitHub token exchange without printing credentials.
Runner registration: Confirm the workflow displays the ACA runner name and matching custom label.
OIDC deployment: Confirm azure/login succeeds without a client secret and Terraform operates only within the assigned Azure scope.
Ephemeral cleanup: After the workflow finishes, confirm the runner deregisters, the Container Apps execution completes and execution count returns to zero.
Repeatability: Run the workflow again. A new execution and new ephemeral runner should perform the second run.
az containerapp job execution list ` --resource-group $ResourceGroup ` --name $JobName ` --query "[].{Name:name,Status:properties.status,Start:properties.startTime,End:properties.endTime}" ` --output table az containerapp job show ` --resource-group $ResourceGroup ` --name $JobName ` --query "properties.configuration.eventTriggerConfig.scale.rules" ` --output json
Measure cold-start stages separately to identify where queue-to-ready latency is introduced.
If runtime bootstrap repeatedly dominates the workflow duration, move the tested bootstrap into a versioned, scanned pre-baked image. That optimization changes the image supply chain and startup time, but it does not require changing the GitHub App, private Key Vault, KEDA, ephemeral-runner or OIDC trust model.
Assuming common tools existed β Minimal base images may not include Git, PowerShell, Terraform, Python, jq or even familiar diagnostic commands. Verify the image rather than assuming.
Root runner configuration β Runner configuration can reject root execution unless the supported override is set. Treat this as an explicit base-image decision.
Testing a private vault from a laptop β A client IP allow-list does not override disabled public network access. Test the private path from the workload network.
Storage account Terraform smoke test β A resource can be created successfully through ARM while later provider data-plane checks fail because shared-key authentication is prohibited. Separate control-plane success from data-plane authorization.
Trying to observe an ephemeral runner manually β A short workflow may register and disappear too quickly for the runner page to be useful. Use workflow logs and Container Apps execution history as authoritative evidence.
Conflating runner and deployment identity β Successful runner registration does not prove Azure deployment authentication. Test GitHub OIDC and Azure RBAC independently.
|
Symptom |
First checks |
|
Workflow remains queued |
Check workflow labels, KEDA labels, owner, repo, runner scope, GitHub App permissions, installation ID and scaler logs. |
|
Job starts but no runner appears |
Check bootstrap logs, Key Vault DNS resolution, private endpoint connectivity, UAMI attachment, Key Vault RBAC and GitHub token exchange responses. |
|
Runner registers but workflow does not start |
Confirm the custom label and repository scope; verify no alternate self-hosted runner is matching the job. |
|
Azure login fails |
Check id-token: write, federated credential subject/audience/issuer, client ID, tenant ID and branch/environment conditions. |
|
Terraform authorization fails |
Inspect Azure role assignment principal, scope and propagation; distinguish ARM failures from resource data-plane failures. |
|
Cold start is too high |
Measure image pull, OS package installation, Terraform/PowerShell download and runner registration separately; consider a pre-baked image if startup dominates runtime. |
Running GitHub Actions runners on Azure Container Apps is more than replacing a virtual machine with a container. The durable architecture comes from treating queue detection, runner registration, secret retrieval and Azure deployment as separate trust flows.
The resulting pattern is straightforward: KEDA starts an event job only when matching work exists; a private managed identity path retrieves GitHub App material; short-lived credentials register an ephemeral repository runner; GitHub OIDC provides a separate, secretless path to Azure; and the entire execution disappears after one workflow.
The MCR runtime-bootstrap strategy removes the need for a dedicated runner registry and custom image pipeline, but it increases cold-start latency and dependency on external download endpoints. That trade-off should be measured and revisited as usage grows. The architecture remains the same if a future requirement justifies moving the tested bootstrap into a pre-baked image.
The job market has been rough on tech leaders lately, and picking the wrong company can cost you a year or two of your career. In this episode, Bob Galen and Josh Anderson each share their top 5 things they look for when evaluating a company during hiring. (Bob brought 6, plus 3 more he doesn't care about.)
Happy Sunday, and happy "Saints have a bye week so they can't lose" day. That may be bitter and I shouldn't write off their season yet but... maybe next year? Of course, this is just one more kinda crappy thing going on amongst a giant pile of other crappy things so - let's just get right to the links, shall we?
Everyone knows the job market is hell now, and to be honest, hell may be a bit too kind. Now imagine you are considering a job, download a take home test, and discover it's actually a hack attempt. Appaji C. details the attack here: "I Inspected My Take-Home Interview Project. It Was a Whole Operation."
Of special note, I love the Halloween theme on his blog. It may be gone by the time you read this, but it's gorgeous.
Next up is a fun little post (and rant) from Dan Q. titled, "Your 'App' Could Have Been a Webpage (so I fixed it for you...)". Now, this may be hard to believe, but Dan was asked to download an app that was absolutely clearly something that should have been just a web page. Shocker, right? But instead of just complaining about it, Dan went to work to inspect the app and tear out the important information. This then turned into a script he could use to generate his own HTML. More of this, please.
I've been saying this now for maybe 10 years, but CSS has moved at light speed to the point where anything seems possible. This is great for everyone. Carmen Ansio created a beautiful tutorial on recreating a modernist tile look using CSS: "Modernist tiles with CSS Grid". The post goes into detail about the methods used, and has multiple interactive elements that are really well done.
Credit for this find comes from the excellent newsletter by Cassidy Williams.
To be honest, I'm not really feeling fun, so here's a beautiful and sad song.
Play Video