Skip to content

feat: GitHub Copilot authentication in headless containerised deployments #144

@raykao

Description

@raykao

feat: GitHub Copilot authentication in headless containerised deployments

Problem

copilot-bridge relies on an authenticated GitHub Copilot session, which is currently managed by the Copilot CLI on the host machine. When running inside a Docker container, there is no browser, no interactive terminal for device flow, and no persistent host credential store available by default.

This is a blocking issue for the containerised deployment architecture - without a solution, agent containers cannot authenticate to GitHub Copilot and the bridge cannot function.

Motivation

For containerised deployments (and headless servers generally), authentication needs to:

  • Work without human interaction on every container start
  • Not bake credentials into the container image
  • Support rotation without rebuilding images
  • Work the same way on Linux and macOS hosts

The current interactive gh auth login flow works on a developer workstation but is not viable for a container that starts automatically on boot or restart.

Background: How Copilot auth works today

The Copilot CLI authenticates via GitHub OAuth and stores a token in the host credential store (e.g. macOS Keychain, Linux libsecret, or ~/.config/gh/hosts.yml). This token is then used by the bridge to make Copilot API calls.

Inside a container, none of these stores are available unless explicitly mounted.

Proposed Solution

Recommended: GitHub PAT via 1Password + op inject

Since copilot-bridge already uses op inject to render config.json.tpl at startup (see issue #142), the GitHub PAT is simply another secret in the same 1Password vault. The entrypoint renders it alongside the bot tokens and writes it to the expected credential location:

config.json.tpl:

{
  "github": {
    "token": "{{ op://Vault/copilot-bridge/github-pat }}"
  }
}

entrypoint.sh:

# op inject renders all secrets including the GitHub PAT
op inject -i /config/config.json.tpl -o /tmp/config.json

# Wire the PAT into the gh CLI credential store
GITHUB_PAT=$(node -e "console.log(require('/tmp/config.json').github.token)")
mkdir -p /home/node/.config/gh
printf "github.com:\n  oauth_token: %s\n" "$GITHUB_PAT" \
  > /home/node/.config/gh/hosts.yml

No additional secrets mechanism needed - consistent with the established pattern, rotatable in one place, no host coupling.

The PAT must have Copilot scope and be tied to a licensed human GitHub account (see "Considered and rejected" below for why service accounts are not viable).

Considered and Rejected

Option A: Mount host credential file

Mount ~/.config/gh/hosts.yml from the host into the container as a read-only volume:

volumes:
  - ~/.config/gh/hosts.yml:/home/node/.config/gh/hosts.yml:ro

Rejected because: couples the container tightly to the host user session, reduces isolation, and breaks if the host token expires or is rotated. Acceptable as a quick-start shortcut for local development only - not suitable for production.

Option C: GitHub App or service account

Register a GitHub App and use short-lived installation tokens instead of a PAT.

Rejected because: GitHub Copilot access is restricted to licensed human accounts. GitHub Apps and service accounts cannot be granted Copilot access. This option is not viable regardless of implementation complexity.

Deliverables

  • Document all three options in a "Containerised Auth" section of the docs
  • Update entrypoint.sh to wire the GitHub PAT from the rendered config into the gh CLI credential store
  • Update config.json.tpl to include a github.token field with an op:// reference
  • Update docker-compose.yml example to show the full flow

Reported By

Agent (automated) - drafted collaboratively with user raykao

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions