A self-updating app portal for Kubernetes. Porthole watches the Ingress resources in your cluster and turns every app that opts in (via a label) into a tile on a central website — name, description, icon and link included.
Built for teams that constantly deploy tools and prototypes and want a single place to share them: deploy your app with an Ingress, add one label and a few annotations, and it shows up on the portal within seconds. Delete the Ingress and it disappears again. No portal config files, no redeploys.
┌──────────────┐ watch Ingresses ┌─────────────────────────┐
│ Porthole │ ◄────────────────── │ Kubernetes API server │
│ (web + API) │ label selector: └─────────────────────────┘
└──────┬───────┘ porthole/enabled=true
│ serves
▼
https://apps.example.com → tiles linking to every opted-in app
Your Deployment/Service stay completely ordinary. On the Ingress, add:
metadata:
labels:
porthole/enabled: "true" # opt in
annotations:
porthole.io/name: "My Prototype" # display name (default: ingress name)
porthole.io/description: "One-line pitch shown on the card"
porthole.io/group: "Prototypes" # section on the page (default: "Apps")
porthole.io/icon: "🧪" # emoji or image URL (default: 🚀)Optional annotations:
| Annotation | Purpose |
|---|---|
porthole.io/url |
Override the link (otherwise derived from the Ingress host/path; https when TLS is configured) |
porthole.io/weight |
Sort order within a group, higher first (default 0) |
A full copy-paste example lives in deploy/examples/whoami/.
-
Build and push the image:
docker build -t <your-registry>/porthole:latest . docker push <your-registry>/porthole:latest
-
Point the manifests at your environment: in
deploy/base/deployment.yamlset theimage, and indeploy/base/ingress.yamlset thehost(andingressClassName/TLS as needed). -
Apply:
kubectl apply -k deploy/base
The manifests create a porthole namespace, a ServiceAccount with a
read-only ClusterRole for Ingresses (get/list/watch — nothing else),
the Deployment, a Service and the portal's own Ingress.
The manifests are plain Kustomize. To adapt them per environment, create an overlay instead of editing the base:
deploy/
base/ # what's in this repo
overlays/
prod/
kustomization.yaml # resources: [../../base] + patches for host, image, replicas …
New apps never require touching this repo — they self-register through their own Ingress labels/annotations.
| Env var | Default | Purpose |
|---|---|---|
PORTHOLE_TITLE |
Porthole |
Page title |
PORTHOLE_LABEL_SELECTOR |
porthole/enabled=true |
Which Ingresses to show |
PORTHOLE_ANNOTATION_PREFIX |
porthole.io |
Annotation prefix to read |
PORTHOLE_NAMESPACES |
(all) | Comma-separated namespace allowlist |
PORTHOLE_DEMO |
false |
Serve built-in sample data (no cluster needed) |
GET /api/apps— JSON list of all discovered appsGET /healthz— liveness/readiness (reports watcher state)
python -m venv .venv && . .venv/bin/activate
pip install -r app/requirements.txt
# without a cluster (sample data):
PORTHOLE_DEMO=true uvicorn app.main:app --reload --port 8080
# against a real cluster (uses your kubeconfig):
uvicorn app.main:app --reload --port 8080Then open http://localhost:8080.
Tests:
pip install pytest
pytest