Port is a developer portal and internal developer platform (IDP) that lets platform engineering teams build software catalogs, manage service ownership, automate developer self-service, and track engineering standards across an organization. Development teams use Port to view their services' health, trigger deployments, manage environments, and track scorecards and initiatives. Port integrates with GitHub, GitLab, Jira, PagerDuty, Datadog, and dozens of other tools to build a unified internal developer experience. But Port's value depends entirely on its availability and data freshness: when Port is down or stale, developers cannot see service ownership, cannot trigger self-service actions, and cannot trust the catalog data they rely on for incident response and on-call handoffs.
Vigilmon monitors Port's API availability, webhook ingestion health, and catalog data freshness externally so your platform engineering team knows immediately when the developer portal has a problem.
Why Port Needs External Monitoring
Port runs as a SaaS platform, but your integration layer — the exporters, webhooks, and self-service actions that populate and interact with Port — has several critical components:
- Port API availability — Port's REST API powers all integrations, blueprints, and scorecards; downtime breaks every CI/CD integration
- Webhook delivery health — Port sends webhooks on entity changes; if webhook delivery fails, downstream automations stop silently
- Catalog data freshness — stale entity data means Port shows outdated service owners, incorrect health statuses, and incorrect environment states
- Self-service action execution — Port triggers backend actions via webhooks and Kafka; failed action backends cause developer requests to silently fail
- Port exporter health — Port's GitHub, GitLab, and Kubernetes exporters run in your infrastructure and push data to Port; when they fail, catalog data drifts
Vigilmon monitors both Port's SaaS endpoints and your integration layer so you know the full picture.
Step 1: Check Port API Availability
Port exposes a REST API at api.getport.io. Confirm your integration credentials and API access:
# Get an API token (client credentials flow)
curl -s -X POST 'https://api.getport.io/v1/auth/access_token' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET"
}' | jq -r .accessToken
# List blueprints (confirms API and organization access)
curl -s -H "Authorization: Bearer $TOKEN" \
'https://api.getport.io/v1/blueprints' | jq '.[].identifier'
# List entities of a blueprint (confirms catalog data access)
curl -s -H "Authorization: Bearer $TOKEN" \
'https://api.getport.io/v1/blueprints/service/entities?limit=1' | jq .
A 200 response with blueprint data confirms Port's API is reachable and your credentials are valid.
Step 2: Build a Health Proxy for Vigilmon
Port's API requires bearer token authentication, which Vigilmon's HTTP monitor cannot handle natively. Expose a health proxy that authenticates and queries Port:
Node.js Health Proxy
// port-health.js
const express = require('express');
const axios = require('axios');
const app = express();
const PORT_CLIENT_ID = process.env.PORT_CLIENT_ID;
const PORT_CLIENT_SECRET = process.env.PORT_CLIENT_SECRET;
const PORT_BLUEPRINT = process.env.PORT_BLUEPRINT || 'service';
const PORT_API_BASE = 'https://api.getport.io/v1';
let cachedToken = null;
let tokenExpiresAt = 0;
async function getAccessToken() {
if (cachedToken && Date.now() < tokenExpiresAt - 30000) {
return cachedToken;
}
const res = await axios.post(`${PORT_API_BASE}/auth/access_token`, {
clientId: PORT_CLIENT_ID,
clientSecret: PORT_CLIENT_SECRET,
}, { timeout: 10000 });
cachedToken = res.data.accessToken;
// Port tokens expire in 1 hour
tokenExpiresAt = Date.now() + 3600 * 1000;
return cachedToken;
}
app.get('/health/port', async (req, res) => {
try {
const token = await getAccessToken();
// Check blueprint list (API availability)
const blueprintsRes = await axios.get(`${PORT_API_BASE}/blueprints`, {
headers: { Authorization: `Bearer ${token}` },
timeout: 8000,
});
const blueprintCount = blueprintsRes.data?.length ?? 0;
if (blueprintCount === 0) {
return res.status(503).json({ status: 'degraded', reason: 'no_blueprints_found' });
}
// Check entity count for the primary blueprint (data freshness signal)
const entitiesRes = await axios.get(
`${PORT_API_BASE}/blueprints/${PORT_BLUEPRINT}/entities?limit=1`,
{ headers: { Authorization: `Bearer ${token}` }, timeout: 8000 }
);
const entityCount = entitiesRes.data?.entities?.length ?? 0;
const totalCount = entitiesRes.data?.total ?? 0;
return res.status(200).json({
status: 'ok',
blueprintCount,
blueprint: PORT_BLUEPRINT,
entityCount: totalCount,
});
} catch (err) {
const status = err.response?.status;
if (status === 401 || status === 403) {
return res.status(503).json({ status: 'down', reason: 'authentication_failed', httpStatus: status });
}
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3012, () => console.log('Port health proxy on :3012'));
Python Health Proxy (FastAPI)
# port_health.py
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import httpx, os, time
app = FastAPI()
PORT_CLIENT_ID = os.environ["PORT_CLIENT_ID"]
PORT_CLIENT_SECRET = os.environ["PORT_CLIENT_SECRET"]
PORT_BLUEPRINT = os.environ.get("PORT_BLUEPRINT", "service")
PORT_API_BASE = "https://api.getport.io/v1"
_token_cache = {"token": None, "expires_at": 0}
async def get_access_token(client: httpx.AsyncClient) -> str:
if _token_cache["token"] and time.time() < _token_cache["expires_at"] - 30:
return _token_cache["token"]
res = await client.post(f"{PORT_API_BASE}/auth/access_token", json={
"clientId": PORT_CLIENT_ID,
"clientSecret": PORT_CLIENT_SECRET,
})
res.raise_for_status()
_token_cache["token"] = res.json()["accessToken"]
_token_cache["expires_at"] = time.time() + 3600
return _token_cache["token"]
@app.get("/health/port")
async def port_health():
async with httpx.AsyncClient(timeout=10.0) as client:
try:
token = await get_access_token(client)
headers = {"Authorization": f"Bearer {token}"}
blueprints = await client.get(f"{PORT_API_BASE}/blueprints", headers=headers)
blueprint_count = len(blueprints.json())
if blueprint_count == 0:
return JSONResponse(status_code=503, content={
"status": "degraded", "reason": "no_blueprints_found"
})
entities = await client.get(
f"{PORT_API_BASE}/blueprints/{PORT_BLUEPRINT}/entities?limit=1",
headers=headers,
)
total = entities.json().get("total", 0)
return {"status": "ok", "blueprintCount": blueprint_count, "entityCount": total}
except httpx.HTTPStatusError as e:
if e.response.status_code in (401, 403):
return JSONResponse(status_code=503, content={
"status": "down", "reason": "authentication_failed"
})
return JSONResponse(status_code=503, content={"status": "down", "error": str(e)})
except Exception as e:
return JSONResponse(status_code=503, content={"status": "down", "error": str(e)})
Step 3: Monitor Catalog Data Freshness
Port's catalog is only valuable when its data is current. Stale entity data means outdated service owners, incorrect on-call assignments, and unreliable health statuses. Monitor entity update timestamps to detect when exporters have stopped syncing:
// Add to port-health.js
const MAX_ENTITY_AGE_HOURS = parseInt(process.env.MAX_ENTITY_AGE_HOURS || '24');
app.get('/health/port/freshness', async (req, res) => {
try {
const token = await getAccessToken();
// Get the most recently updated entity
const entitiesRes = await axios.get(
`${PORT_API_BASE}/blueprints/${PORT_BLUEPRINT}/entities?limit=5`,
{ headers: { Authorization: `Bearer ${token}` }, timeout: 10000 }
);
const entities = entitiesRes.data?.entities ?? [];
if (entities.length === 0) {
return res.status(503).json({
status: 'degraded',
reason: 'no_entities_found',
blueprint: PORT_BLUEPRINT,
});
}
// Find the most recently updated entity
const sorted = entities
.filter(e => e.updatedAt)
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
if (sorted.length === 0) {
return res.status(503).json({ status: 'degraded', reason: 'no_entities_with_timestamps' });
}
const mostRecentUpdate = new Date(sorted[0].updatedAt);
const ageMs = Date.now() - mostRecentUpdate.getTime();
const ageHours = ageMs / (1000 * 60 * 60);
if (ageHours > MAX_ENTITY_AGE_HOURS) {
return res.status(503).json({
status: 'degraded',
reason: 'catalog_data_stale',
mostRecentUpdate: mostRecentUpdate.toISOString(),
ageHours: Math.round(ageHours * 10) / 10,
thresholdHours: MAX_ENTITY_AGE_HOURS,
});
}
return res.status(200).json({
status: 'ok',
mostRecentUpdate: mostRecentUpdate.toISOString(),
ageHours: Math.round(ageHours * 10) / 10,
});
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
Set MAX_ENTITY_AGE_HOURS=2 for blueprints synced by the Kubernetes exporter (which syncs frequently) and MAX_ENTITY_AGE_HOURS=48 for manually-managed blueprints.
Step 4: Monitor Port Exporter Health (Kubernetes)
Port's Kubernetes exporter runs in your cluster and continuously syncs Kubernetes resources into Port's catalog. Monitor its liveness:
# port-exporter-health-check.yaml
# Kubernetes CronJob to verify the Port exporter is running and syncing
apiVersion: batch/v1
kind: CronJob
metadata:
name: port-exporter-health
namespace: port
spec:
schedule: "*/5 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: health-check
image: curlimages/curl:latest
command:
- sh
- -c
- |
# Check exporter pod is running
RUNNING=$(kubectl get pods -n port -l app=port-k8s-exporter \
--field-selector=status.phase=Running --no-headers 2>/dev/null | wc -l)
if [ "$RUNNING" -eq "0" ]; then
echo "ERROR: Port exporter has no running pods"
exit 1
fi
echo "OK: $RUNNING exporter pod(s) running"
# Ping Vigilmon heartbeat
curl -s "$VIGILMON_HEARTBEAT_URL" > /dev/null
env:
- name: VIGILMON_HEARTBEAT_URL
valueFrom:
secretKeyRef:
name: vigilmon-config
key: heartbeat-url
restartPolicy: OnFailure
Step 5: Configure Vigilmon HTTP Monitors
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Port health proxy:
https://your-app.example.com/health/port - Set the check interval to 2 minutes
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
10000ms(Port API calls include authentication)
- Status code:
- Under Alert channels, assign your platform team Slack channel
- Save the monitor
Add a data freshness monitor:
- URL:
https://your-app.example.com/health/port/freshness - Expected:
200, body contains"status":"ok" - Interval:
30 minutes - Alert channel: platform engineering Slack channel
Set up a heartbeat monitor for the Kubernetes exporter:
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
port-k8s-exporter-alive - Set the expected interval: 10 minutes
- Set the grace period: 20 minutes
- Alert channel: platform engineering on-call
Step 6: Alert Routing for Port Failures
| Monitor | Alert Channel | Priority |
|---|---|---|
| /health/port (API availability) | Slack + PagerDuty | P1 |
| /health/port/freshness (catalog data staleness) | Slack + email | P2 |
| Heartbeat: port-k8s-exporter-alive | Slack + email | P2 |
| Heartbeat: port-github-exporter-alive | Slack + email | P2 |
Port downtime is rarely a P1 on its own — it does not directly affect production traffic — but it becomes critical during incidents when on-call engineers cannot look up service ownership or trigger self-service runbooks. Configure your Port API monitor with a 30-minute notification delay during off-hours to reduce noise, but ensure that incident-time availability is monitored with zero tolerance.
Catalog staleness (P2) should escalate to P1 after 6+ hours — stale data for that long typically means an exporter has crashed and catalog drift is accelerating.
Summary
Port is the developer experience backbone — service catalog, self-service actions, scorecards, and engineering standards all depend on it. Vigilmon gives you the external monitoring layer that keeps Port reliable and your catalog data trustworthy:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /health/port | Port API availability and authentication |
| HTTP monitor on /health/port/freshness | Catalog entity data freshness |
| Heartbeat: port-k8s-exporter-alive | Kubernetes exporter liveness |
| Heartbeat: port-github-exporter-alive | GitHub exporter liveness |
Get started free at vigilmon.online — your first Port integration monitor is live in under two minutes.