There are a few ways to deploy the Copilot Metrics Viewer, depending on the type of metrics (Organization/Enterprise) and the level of control required.
The app runs in a Docker container, so it can be deployed anywhere containers are hosted (AWS, GCP, Azure, Kubernetes, etc.).
The application supports two operating modes:
The simplest setup — the web app fetches metrics directly from GitHub's Copilot Usage Metrics API on each page load, returning the latest 28-day rolling window.
| Component | Description | Required |
|---|---|---|
| Web App | Nuxt 3 dashboard (port 3000/80) | Yes |
Adds a PostgreSQL database and sync service for persistent storage. This enables metrics beyond the 28-day API window, per-user time-series history, and full historical team views.
| Component | Description | Required |
|---|---|---|
| Web App | Nuxt 3 dashboard (port 3000/80) | Yes |
| PostgreSQL | Database for historical metrics storage | Yes |
| Sync Service | Scheduled job that downloads metrics from GitHub API to PostgreSQL | Yes |
The sync service runs daily (2 AM UTC by default) and downloads the latest Copilot usage metrics from GitHub's API. The web app reads from the database for fast, reliable dashboard rendering.
GitHub's Copilot Usage Metrics API does not provide team-level endpoints. Instead, this application derives team metrics by:
- Downloading per-user daily metrics from the organization/enterprise endpoint
- Resolving team membership via the GitHub Teams API
- Filtering and aggregating per-user data in-memory for each team
This approach works in Historical mode only:
- Direct API mode: Team-scoped views are not available (no per-user data stored to filter)
- Historical mode: Team data covers the full stored history, enabling long-term team trend analysis
Review available Nuxt Deployment Options.
Warning
Copilot Metrics Viewer requires a backend, hence it cannot be deployed as a purely static web app.
The Metrics Viewer can be integrated with GitHub application authentication, which authenticates the user and verifies their permissions to view the metrics. This option is recommended since it doesn't use Personal Access Tokens. The downside of using a GitHub application is that it can only authorize users to view metrics at the organization level (no support for Enterprise).
For Enterprise level authentication review Github OAuth Apps.
With a Personal Access Token, user credentials are not verified, and the application simply renders Copilot metrics fetched using the PAT stored in the backend.
By default Azure Deployments deploy a web app available on the public Internet without authentication (unless GitHub app is used).
Application can be easily secured in azure using built-in features like Authentication settings on ACA/AppService (EasyAuth on Azure). Azure Container Apps and App Services allow for adding IP restrictions on ingress. Both can also be deployed using private networking architectures.
Options below provide most basic and cost effective ways of hosting copilot-metrics-viewer.
The simplest way to deploy is to use the "one-click" option that creates resources in Azure. The deployment includes:
- Azure Container App with a consumption environment
- Azure Container App Job (sync service, daily schedule)
- Azure Database for PostgreSQL Flexible Server (Burstable B1ms)
- Azure Log Analytics Workspace
Application will use a pre-built docker image hosted in GitHub registry: ghcr.io/github-copilot-resources/copilot-metrics-viewer.
Prerequisites: Contributor permission to a resource group in Azure and a subscription with the Microsoft.App and Microsoft.DBforPostgreSQL resource providers enabled.
Important
Estimated cost for running this in Azure is about $15/month (Container Apps ~$1 + PostgreSQL Burstable B1ms ~$12 + Log Analytics ~$2).
-
Option 1 - Using a Personal Access Token in the Backend:
-
Option 2 - Using GitHub App Registration and GitHub Authentication:
When using this method, register your app in Github first.
Important
Important: After deploying Option 2, the redirect URI needs to be updated with the URL of the deployed container app.
Go to: https://github.com/organizations/<your-org>/settings/apps/<your-app> or in the UI to the settings of the registered application and add the following redirect URL: https://<your-container-app-name-and-region>.azurecontainerapps.io/auth/github
Caution
When deploying to a private network, specify a subnet (at least /23) for the Azure Container Apps Environment. App deployment does not create any DNS entries for the application, in order to create a private DNS Zone linked to provided Virtual Network, follow up the deployment with DNS deployment targeting same resource group:
If more control over the deployed container image is needed, an infrastructure-as-code option has been provided using Azure Bicep. The application can be deployed using the Azure Developer CLI (azd).
In this scenario, the container is built from the source code locally, which provides additional opportunities to modify, scan, etc.
Prerequisites:
- Contributor permission to a subscription in Azure with the
Microsoft.AppandMicrosoft.DBforPostgreSQLresource providers enabled. - Permissions for creating role assignments.
- Azure CLI (az), Azure Developer CLI (azd) and Docker installed locally.
Important
Estimated cost for running this in Azure is about $25/month (Container Apps ~$1 + Container Registry ~$5 + PostgreSQL ~$12 + monitoring ~$5).
The deployment creates:
- Azure Resource Group
- Azure Container App with a consumption environment
- Azure Container App Job (sync service)
- Azure Container Registry
- Azure Database for PostgreSQL Flexible Server
- Azure Log Analytics Workspace
- Azure Application Insights
- Azure Key Vault
Run azd up and follow the prompts. You will be asked for:
- GitHub PAT token (or OAuth client credentials)
- GitHub scope (organization/enterprise)
- Organization/enterprise name
- PostgreSQL admin password
The recommended way to run the application locally or in any Docker-capable environment. Docker Compose manages the web app, PostgreSQL database, and sync service together.
No GitHub token needed — great for trying out the dashboard:
docker compose up web
# Open http://localhost:3000/orgs/your-org?mock=trueThe simplest setup — metrics come directly from the GitHub API (28-day rolling window):
export NUXT_GITHUB_TOKEN=github_pat_... # Fine-grained PAT with "Copilot metrics" permission
export NUXT_PUBLIC_GITHUB_ORG=your-org
export NUXT_PUBLIC_IS_DATA_MOCKED=false
docker compose up web
# Open http://localhost:3000/orgs/your-orgNote
Team-scoped views (e.g., /orgs/your-org/teams/your-team) require Historical mode with PostgreSQL. Without the database, team metrics cannot be computed because team data is derived by filtering per-user records stored in the database. The Teams Comparison tab is available to browse teams, but individual team drill-down requires Historical mode.
Adds PostgreSQL for persistent storage — enables metrics beyond 28 days, per-user time-series history, and full historical team views:
export NUXT_GITHUB_TOKEN=github_pat_... # Fine-grained PAT with "Copilot metrics" permission
export NUXT_PUBLIC_GITHUB_ORG=your-org
export NUXT_PUBLIC_IS_DATA_MOCKED=false
export ENABLE_HISTORICAL_MODE=true
# Start web app + database
docker compose up web db
# In a separate terminal, run initial sync to populate the database
docker compose run --rm sync
# Open http://localhost:3000/orgs/your-orgThe sync service downloads all available historical data on first run. Subsequent runs (or the daily schedule) only sync the latest day.
export NUXT_GITHUB_TOKEN=github_pat_...
export NUXT_PUBLIC_SCOPE=enterprise
export NUXT_PUBLIC_GITHUB_ENT=your-enterprise
export NUXT_PUBLIC_IS_DATA_MOCKED=false
export ENABLE_HISTORICAL_MODE=true
docker compose up web db
docker compose run --rm sync| Service | Purpose | Profile |
|---|---|---|
db |
PostgreSQL 15 for metrics storage | default |
web |
Main Nuxt 3 dashboard (port 3000) | default |
sync |
Standalone sync service (API → PostgreSQL) | default |
playwright |
E2E tests with mock data (3 browsers) | test |
sync-seed |
Seeds DB with mock data for storage pipeline | test |
playwright-storage |
E2E tests reading from DB, no token needed | test |
Mock data tests (all tests across 3 browsers):
docker compose run --rm playwright
# Results saved to ./test-results/Storage pipeline tests (full sync → DB → dashboard):
# Phase 1: Seed the database with mock data
docker compose run --rm sync-seed
# Phase 2: Verify dashboard reads from DB (no GitHub token)
docker compose run --rm playwright-storagedocker compose down # Stop all services
docker compose down -v # Stop and remove volumes (delete all data)Kubernetes manifests are provided in the k8s/ directory:
k8s/deployment.yaml— Web app Deployment + Service with health probesk8s/cronjob.yaml— Sync service CronJob (daily at 2 AM)
- A PostgreSQL database (managed service recommended: AWS RDS, Azure Database for PostgreSQL, Google Cloud SQL)
- Container images from GHCR:
ghcr.io/github-copilot-resources/copilot-metrics-viewer:latestghcr.io/github-copilot-resources/copilot-metrics-viewer-sync:latest
- Create the secrets:
kubectl create secret generic copilot-metrics-secrets \
--from-literal=github-token="ghp_your_token_here" \
--from-literal=session-password="at_least_32_characters_long_random_string" \
--from-literal=database-url="postgresql://user:pass@your-db-host:5432/copilot_metrics"-
Edit the manifests to set your organization/enterprise name.
-
Apply:
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/cronjob.yamlThe web app provides dedicated health check endpoints:
/api/live— Liveness probe (application is alive and responsive)/api/ready— Readiness probe (application ready to serve traffic)/api/health— General health status
These endpoints respond in ~200ms without making external API calls and do not require authentication.
Note
Using these dedicated health endpoints instead of the root / path avoids triggering GitHub API calls during health checks.
When running in Historical mode, the web app exposes a manual sync endpoint for backfilling or repairing data. If the app is configured with NUXT_GITHUB_TOKEN, the Authorization header is optional (the server uses its own token).
Note: The GitHub Copilot Metrics API provides historical data well beyond the 28-day rolling window. The 1-day endpoint supports dates going back many months, so
sync-date,sync-range, andsync-gapscan all backfill historical data. The 28-day limit only applies tosync-last-28(which uses the bulk download endpoint).
POST /api/admin/sync
Common parameters (body JSON or query string — Content-Type: application/json is optional):
| Parameter | Description |
|---|---|
scope |
organization or enterprise |
githubOrg |
Organization slug (org scope) |
githubEnt |
Enterprise slug (enterprise scope) |
action |
One of the actions below (default: sync-date) |
sync-date — Download and store metrics for a single day (supports any historical date).
curl -X POST http://localhost:3000/api/admin/sync \
-H "Content-Type: application/json" \
-d '{"action":"sync-date","scope":"organization","githubOrg":"your-org","date":"2026-01-15"}'
# → {"action":"sync-date","result":{"success":true,"date":"2026-01-15","metricsCount":1}}sync-last-28 — Download the latest 28-day report and store any new days. Most efficient for keeping the database current (1 API call for 28 days).
curl -X POST http://localhost:3000/api/admin/sync \
-H "Content-Type: application/json" \
-d '{"action":"sync-last-28","scope":"organization","githubOrg":"your-org"}'
# → {"action":"sync-last-28","success":true,"totalDays":28,"savedDays":27,"skippedDays":1,"errors":[]}sync-range — Download and store all days in a date range (one API call per day). Use for initial historical backfill.
curl -X POST http://localhost:3000/api/admin/sync \
-H "Content-Type: application/json" \
-d '{"action":"sync-range","scope":"organization","githubOrg":"your-org","since":"2026-01-01","until":"2026-03-31"}'sync-gaps — Like sync-range but skips dates already present in the database. Uses bulk download for recent gaps and the 1-day endpoint for older gaps.
curl -X POST http://localhost:3000/api/admin/sync \
-H "Content-Type: application/json" \
-d '{"action":"sync-gaps","scope":"organization","githubOrg":"your-org","since":"2026-01-01","until":"2026-04-20"}'
# → {"action":"sync-gaps","gapsDetected":82,"gapsFilled":80,"outsideWindow":0,"failureCount":2,"results":[...]}| Variable | Description | Required |
|---|---|---|
NUXT_GITHUB_TOKEN |
GitHub PAT with Copilot metrics permission | Yes (unless OAuth) |
NUXT_PUBLIC_SCOPE |
organization or enterprise (legacy team-organization/team-enterprise have been removed; existing values are auto-normalized) |
Yes |
NUXT_PUBLIC_GITHUB_ORG |
GitHub organization slug | For org scope |
NUXT_PUBLIC_GITHUB_ENT |
GitHub enterprise slug | For enterprise scope |
NUXT_SESSION_PASSWORD |
Session encryption key (min 32 chars) | Yes |
DATABASE_URL |
PostgreSQL connection string | Historical mode only |
ENABLE_HISTORICAL_MODE |
true to read metrics from database |
Historical mode only |
SYNC_ENABLED |
true for sync service, false for web app |
Historical mode only |
SYNC_DAYS_BACK |
Days to sync (default: 1 for daily, 28 for bulk) | Sync only |
NUXT_PUBLIC_USING_GITHUB_AUTH |
true to enable GitHub OAuth |
Optional |
NUXT_OAUTH_GITHUB_CLIENT_ID |
GitHub App client ID | For OAuth |
NUXT_OAUTH_GITHUB_CLIENT_SECRET |
GitHub App client secret | For OAuth |
While it is possible to run the API Proxy without GitHub app registration and with a hardcoded token, it is not the recommended way.
To register a new GitHub App, follow these steps:
Tip
Navigate using link: replace <your_org> with your organization name and open this link:
https://github.com/organizations/<your_org>/settings/apps
or navigate using UI:
-
Go to your organization's settings.
-
Navigate to "Developer settings".
-
Select "GitHub Apps".
-
Click "New GitHub App".
-
Set a unique name.
-
Provide a home page URL: your company URL or just
http://localhost. -
Add a callback URL for
http://localhost:3000/auth/github. (We'll add the real redirect URL after the application is deployed.) -
Uncheck the "Webhook -> Active" checkbox.
-
Set the permissions:
- Select Organization permissions.
- Under Members, select Access: Read-only.
- Under Copilot Metrics, select Access: Read-only.
- Under Copilot Seat Management, select Access: Read-only.
-
Click on 'Create GitHub App' and, in the following page, click on 'Generate a new client secret'.
-
Note the
Client IDandClient Secret(copy it to a secure location). This is required for the application to authenticate with GitHub. -
Install the app in the organization:
- Go to "Install App".
- Select your organization.

