Skip to main content
The MergeWatch dashboard is a Next.js application that provides a web UI for managing reviews, repositories, and settings. It authenticates users with GitHub OAuth and connects to the MergeWatch backend API.
MergeWatch landing page

Deployment

In self-hosted mode, the dashboard ships as a second service in your docker-compose.yml alongside the MergeWatch server and Postgres.

Docker Compose configuration

The dashboard service is already included in the default docker-compose.yml. Here is the relevant section:
docker-compose.yml
dashboard:
  image: ghcr.io/santthosh/mergewatch-dashboard:latest
  ports:
    - "3001:3001"
  environment:
    NEXT_PUBLIC_API_URL: http://mergewatch:3000
    GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
    GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
  depends_on:
    - mergewatch

Environment variables

Add these to your .env file:
VariableRequiredDescription
GITHUB_CLIENT_IDYesGitHub App’s OAuth Client ID
GITHUB_CLIENT_SECRETYesGitHub App’s OAuth Client Secret
NEXT_PUBLIC_API_URLYesURL of the MergeWatch server (use the Docker service name, e.g. http://mergewatch:3000)
NEXTAUTH_SECRETYesRandom secret for NextAuth session signing. Generate with openssl rand -base64 32
NEXTAUTH_URLYesPublic URL of the dashboard, e.g. http://localhost:3001

Start the dashboard

The dashboard starts automatically with the rest of the stack:
docker compose up -d
Access the dashboard at http://localhost:3001.

Upgrade

docker compose pull dashboard
docker compose up -d dashboard

GitHub authentication

MergeWatch sign-in page
The dashboard uses NextAuth.js with the GitHub provider. The same GitHub App that handles webhooks also authenticates users — there is no separate OAuth app.

GitHub scopes requested

MergeWatch requests the minimum scopes needed:
ScopeWhy
read:userGet authenticated user’s profile and username
user:emailDisplay user identity in the dashboard header
The GitHub App installation permissions (not OAuth scopes) handle repository access — the OAuth login is purely for identity, not code access.

Organization support

  • Org installation — when a GitHub App is installed on an org, any org member with admin rights can access the dashboard for that installation
  • Access check — the dashboard calls GET /orgs/{org}/members/{username} with role=admin to verify the user is an org admin before showing settings controls
  • Multiple installations — a single user can have multiple installations (personal + multiple orgs). The dashboard shows all installations and lets them switch between them
  • No separate org concept — GitHub’s own org membership model is the source of truth for access control

Session model

Sessions are stored as encrypted JWT cookies (default NextAuth behavior). No session database is needed. The JWT contains:
  • GitHub user ID and username
  • OAuth access token (for GitHub API calls from the frontend)
  • Installation IDs the user has access to
Session expiry defaults to 30 days.

Connecting dashboard to backend

The dashboard calls your MergeWatch backend API. All API calls are authenticated using the user’s GitHub session — the backend validates the GitHub token on each request. Set NEXT_PUBLIC_API_URL to point at your MergeWatch server. For self-hosted, this is the Docker service name (e.g. http://mergewatch:3000). For SaaS, this is your API Gateway URL.

Troubleshooting

Run npm ci locally and confirm the build passes before pushing. Check that package-lock.json is committed.
The NEXTAUTH_SECRET or NEXTAUTH_URL environment variables are missing or incorrect. Confirm NEXTAUTH_URL exactly matches your dashboard URL (no trailing slash).
NEXT_PUBLIC_API_URL is missing or pointing to the wrong backend URL. For self-hosted, verify the MergeWatch server container is running. For SaaS, check the API Gateway URL.
Add your dashboard URL to your GitHub App’s Callback URLs in GitHub App settings (Settings > Developer settings > GitHub Apps > your app > General > Callback URL).
Confirm the GITHUB_APP_ID matches your deployed GitHub App. Mismatches cause installation lookups to return empty results.