Dockerized copilot-api-js reverse proxy that exposes GitHub Copilot's API as OpenAI/Anthropic compatible endpoints. Secured with Caddy reverse proxy (Bearer token auth + UI basic auth), with optional Dev Tunnel or Tailscale networking for remote device access.
# Full setup (generates tokens, OAuth login, configures Claude Code) (token + ui-password + login + setup-claude)
./copilotproxy.ps1 init
# Start the proxy (local only)
./copilotproxy.ps1 startThe proxy will be available at http://localhost:4141. UI is available at http://localhost:4141/ui.
Dev Tunnel runs locally on your host machine and tunnels port 4141 to another device.
On host:
./copilotproxy.ps1 devtunnel-auth # One-time: login + create tunnel (saved to .env)
./copilotproxy.ps1 start # Start the proxy
./copilotproxy.ps1 devtunnel-start # Host the tunnel in background
./copilotproxy.ps1 setup-claude-remote # Start setup approval serverOn remote device:
- Open the tunnel URL in your browser (printed by
devtunnel-start, e.g.https://<id>-4141.<region>.devtunnels.ms/setup) - Sign in with the same Microsoft/GitHub account that owns the tunnel
- Click Download and approve on the host machine when prompted
- Review, then run the script:
cat ~/Downloads/claude-copilot-proxy.sh # Review first
sh ~/Downloads/claude-copilot-proxy.sh # Installs devtunnel, connects, configures ClaudeThe setup script handles everything: installs devtunnel CLI if needed, logs in, starts devtunnel connect in the background with auto-reconnect, and configures Claude Code.
Manage the connection on the remote device:
sh claude-copilot-proxy.sh reconnect # Restart devtunnel connect
sh claude-copilot-proxy.sh stop # Stop devtunnel connect
sh claude-copilot-proxy.sh disable # Remove proxy config from ClaudeTailscale runs as a sidecar container β the proxy container stays unprivileged.
On host:
./copilotproxy.ps1 tailscale-auth # One-time Tailscale login
./copilotproxy.ps1 tailscale-start # Start with Tailscale sidecar
./copilotproxy.ps1 setup-claude-remote # Start setup approval serverOn remote device (must have Tailscale installed and joined to the same tailnet):
- Navigate to http://copilot-proxy:4141/setup for step-by-step instructions
- Click Download and approve on the host machine when prompted
- Review, then run the script:
cat ~/Downloads/claude-copilot-proxy.sh # Review first
sh ~/Downloads/claude-copilot-proxy.sh # Configures Claude CodeApprove on the host machine when prompted.
| Command | Description |
|---|---|
init |
Full setup: token + ui-password + login + setup-claude |
login |
GitHub OAuth login (interactive device flow) |
token |
Generate proxy auth token (saved to .env) |
ui-password |
Set or change the UI basic auth password |
setup-claude |
Configure Claude Code to use this proxy |
setup-claude-remote |
Start approval server for remote device setup |
| Command | Description |
|---|---|
start |
Start the proxy locally (detached) |
stop |
Stop all containers |
restart |
Restart the proxy |
logs |
Tail container logs |
build |
Rebuild the container |
Dev Tunnel runs locally on the host (not in Docker). Requires the devtunnel CLI (winget install Microsoft.devtunnel).
| Command | Description |
|---|---|
devtunnel-auth |
Login + create tunnel (saved to .env) |
devtunnel-start |
Host tunnel in background |
devtunnel-stop |
Stop the tunnel |
devtunnel-status |
Show tunnel status + tail logs |
Tailscale runs as a separate sidecar container β the proxy container stays unprivileged.
| Command | Description |
|---|---|
tailscale-auth |
Interactive Tailscale login |
tailscale-start |
Start proxy + Tailscale sidecar |
tailscale-stop |
Stop proxy + Tailscale sidecar |
tailscale-build |
Rebuild both containers |
copilot-api-js has no built-in authentication or access control β anyone who can reach it gets full access to your Copilot API. Caddy sits in front as a security layer to lock it down:
- Bearer token auth on all API endpoints (v1/models, chat/completions, etc.)
- Basic auth on UI, models, and history pages
- CORS headers stripped β copilot-api-js adds
Access-Control-Allow-Origin: *by default; Caddy strips these headers to enforce same-origin policy, preventing any external website from making requests to your proxy - copilot-proxy binds to localhost β only Caddy can reach it, not the network directly
- Health endpoint (
/health) is unauthenticated for monitoring /setupserves a static instructions page (no credentials);/setup.shonly works when the approval server is running and requires interactive approval
Both remote access options add a network-level authentication layer on top of Caddy's auth:
- Dev Tunnel β Tunnels are private by default: only the Microsoft/GitHub account that created the tunnel can
devtunnel connectto it. Remote devices must authenticate withdevtunnel loginusing the same account. Widening access (org/public) is not allowed or endorsed by this project β the proxy is intended for single-user access only. See Dev Tunnel access control for details. - Tailscale β Only devices joined to your tailnet can reach the proxy. Tailscale uses WireGuard for encrypted point-to-point connections. No ports are exposed to the public internet. Sharing the proxy with others via Tailscale sharing or ACLs is not allowed or endorsed by this project.
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β shared network namespace β
β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β Caddy (reverse proxy) β β
β β :4141 β Bearer auth, basic auth, CORS β β
β β /setup β static HTML instructions β β
β β /setup.sh β setup-server (approval) β β
β β /* β copilot-api-js β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β copilot-api-js (bun) β β
β β :4142 (localhost only, via Caddy) β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β setup-server (ephemeral, on-demand) β β
β β :4143 β interactive device approval β β
β β serves remote-setup.sh after approval β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββ β (optional)
β β tailscale sidecar (NET_ADMIN) β β
β β publishes :4141 to tailnet β β
β βββββββββββββββββββββββββββββββββββββββββββββ β
β β
β devtunnel (runs on host, not Docker) β (optional)
β tunnels :4141 to remote devices β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
- Caddyfile β Reverse proxy config: Bearer auth, basic auth, CORS stripping, /setup routing
- setup.html β Static setup instructions page served by Caddy at /setup
- Dockerfile β Multi-stage build: clones and builds copilot-api-js at pinned commit
- Dockerfile.tailscale β Lightweight sidecar based on
tailscale/tailscale:v1.96.5 - docker-compose.yaml β Local-only, no elevated privileges
- docker-compose.tailscale.yaml β Overlay that adds sidecar +
network_mode: service:tailscale
Edit config.yaml to customize model overrides, rate limiting, timeouts, and more. See the upstream config.example.yaml for all options.
The default config upgrades model aliases:
model_overrides:
opus: claude-opus-4.6-1m
haiku: claude-sonnet-4.6
claude-haiku-4.5: claude-sonnet-4.6Running in a container isolates npm/bun dependencies from your host machine, mitigating supply chain risks. The proxy handles your GitHub token β keeping that in an isolated container with no host filesystem access is a good security practice.
Images are published to GHCR on every push to main:
ghcr.io/nathan815/copilot-proxy:latest
ghcr.io/nathan815/copilot-proxy-tailscale:latest