Install PanDev Metrics on-prem
TL;DR. PanDev Metrics on-prem ships as a distribution archive containing a ready-to-run Docker Compose stack and a bundled Helm chart. You configure a single
.envfile, then either rundocker compose up -dorhelm install. The stack is three components — backend (:8080), workspace UI (:8090), and PostgreSQL 16 — and PostgreSQL is included in the distribution. Audience: admin.
Before you begin
Pick one deployment path and have its prerequisites ready, plus the distribution archive:
- Docker Compose path — a single host with Docker Engine ≥ 20.10 and Docker Compose ≥ v2.0 (v2.20+ recommended).
- Helm path — a Kubernetes 1.28+ cluster with Helm 3 and
kubectlconfigured for it.
In both cases you also need:
- The PanDev Metrics distribution archive from your account manager.
- An x86_64 host (default image tags) or an arm64 host (
-armtags) — see system requirements for the CPU-instruction baseline. - Outbound HTTPS to your Git provider and task tracker — egress is minimal but cannot be disabled.
- A reverse proxy for any non-local install. Don't expose the stack's ports (
8080/8090) directly to the public network — publish only the reverse proxy over TLS (443) and keep the container ports on a private network or behind a firewall. You'll need a reachable hostname and a TLS certificate (a self-signed certificate with client-side validation disabled also works). - Mutual reachability with integrations. If you connect cloud Git providers or task trackers, the backend's public URL must also be reachable from them so their webhooks deliver (real-time sync). Integration connectivity is two-way — see Network and ports.
What you get in the archive
Your account manager provides a distribution archive. Unpack it into a working directory:
sudo mkdir -p /opt/pandev
sudo chown "$USER":"$USER" /opt/pandev
cd /opt/pandev
unzip /tmp/pandev-metrics-distribution.zip -d .
The archive contains two self-contained deployment options plus configuration:
.
├── docker-compose/
│ ├── .env # the file you edit
│ └── docker-compose.yml # server + workspace + PostgreSQL
└── helm-chart/
├── Chart.yaml
├── values.yaml # all Helm parameters, with comments
├── README.md
└── templates/
You do not need to install or pre-provision PostgreSQL separately — it is part of both the Compose stack and the Helm chart. Choose one deployment path:
- Docker Compose — a single host with Docker. The simplest path and the default for most installs.
- Helm / Kubernetes — an existing Kubernetes cluster (1.28+).
PanDev Metrics on-prem is a single-organization deployment. Do not plan around multi-tenant separation — that capability is Cloud-only. Air-gapped deployments are not supported: the backend needs minimal outbound HTTPS to your Git provider and task tracker.
The three components
Both deployment paths run the same three images:
| Component | Image | Port | Role |
|---|---|---|---|
| Server (backend) | pandevofficial/pandev-metrics | 8080 | REST API for the UI and IDE plugins; runs Flyway migrations on startup |
| Workspace (frontend) | pandevofficial/pandev-metrics-backoffice | 8090 → container 80 | The web UI; a static React bundle served by Nginx |
| PostgreSQL | postgres:16 (Compose) / public.ecr.aws/bitnami/postgresql:16 (Helm) | 5432 | System of record for all persistent data |
The bundled PostgreSQL is version 16; the backend is also compatible with PostgreSQL 17 if you supply an external or managed database.
The backend is a native image built with GraalVM, so it is published per CPU architecture. The default tags target x86_64; images with the -arm suffix target arm64 (including Apple Silicon). Pick the tag that matches your host — see system requirements for the exact CPU-instruction baseline.
Deploy with Docker Compose
Step 1 — Review the Compose file
Switch into the Compose directory and look at what will run:
cd /opt/pandev/docker-compose
docker-compose.yml defines three services on a private bridge network. The server reads its database connection from the POSTGRES_* variables, the workspace reads API_BASE_URL, and PostgreSQL persists data to a named volume (postgres-data) so it survives restarts:
services:
pandev-metrics-server:
image: pandevofficial/pandev-metrics:5.8.0
ports: ["8080:8080"]
environment:
DB_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
DB_USERNAME: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_SCHEMA: public
pandev-metrics-workspace:
image: pandevofficial/pandev-metrics-backoffice:2.8.0
ports: ["8090:80"]
environment:
API_BASE_URL: ${API_BASE_URL}
postgres:
image: postgres:16
ports: ["5432:5432"]
volumes: [postgres-data:/var/lib/postgresql/data]
Notice that the server's DB_URL, DB_USERNAME, and DB_PASSWORD are assembled automatically from the POSTGRES_* values you set in .env. You do not edit docker-compose.yml for a standard install.
Step 2 — Configure the .env file
The archive ships a ready .env file in the docker-compose/ directory. As delivered it contains evaluation defaults — this is the complete set of variables, there is nothing else to configure:
# --- PostgreSQL ---
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# --- Frontend ---
API_BASE_URL=http://localhost:8080
For anything beyond a local trial, change at least the database password and the public API URL — for example:
POSTGRES_DB=pandev_metrics
POSTGRES_USER=pandev
POSTGRES_PASSWORD=<STRONG_DB_PASSWORD>
API_BASE_URL=https://metrics.example.com
| Variable | Purpose | Notes |
|---|---|---|
POSTGRES_DB | Database name created on first start | Any valid identifier |
POSTGRES_USER | Database user the backend connects as | Created automatically inside the postgres container |
POSTGRES_PASSWORD | Password for that user | Use a strong value; this is the only DB secret |
API_BASE_URL | The backend URL the browser uses to reach the API | See the note below — this is the one variable people get wrong |
:::info About API_BASE_URL
The workspace is a single-page app that runs in the user's browser. API_BASE_URL is baked into the UI as the address the browser calls for every API request — so it must be reachable from the user's machine, not from inside Docker.
- Testing on the same machine only:
http://localhost:8080(the shipped default) works. - Real deployment: set it to the public URL of the backend, e.g.
https://metrics.example.com(your reverse proxy) orhttp://<host-ip>:8080. An internal name likehttp://pandev-metrics-server:8080will not resolve in the browser. :::
Treat .env as a secret: chmod 600 .env and keep it out of version control. The shipped defaults (postgres/postgres/postgres) are for local evaluation only — always change POSTGRES_PASSWORD before exposing the install.
Step 3 — (arm64 only) switch to the -arm images
If your host is arm64 (for example, Apple Silicon), edit docker-compose.yml and select the -arm tags, which are commented out next to the default x86_64 tags:
image: pandevofficial/pandev-metrics:5.8.0-arm
# ...
image: pandevofficial/pandev-metrics-backoffice:2.8.0-arm
On x86_64 hosts, leave the default tags as shipped.
Step 4 — Pull images and start the stack
docker compose pull
docker compose up -d
On first start the backend runs Flyway migrations against the empty database, then boots. Expect 1–3 minutes before it is ready. Watch progress:
docker compose logs -f pandev-metrics-server
You will see org.flywaydb.core.internal.command.DbMigrate lines followed by the application startup banner.
Step 5 — Verify
Check that all three containers are healthy. PostgreSQL has a built-in health check; the other two should be Up:
docker compose ps
NAME STATUS
pandev-metrics-server Up
pandev-metrics-workspace Up
postgres Up (healthy)
Then open the workspace UI in a browser at http://<host>:8090. You should see the PanDev Metrics login screen, and it should reach the API on port 8080 without errors. The first administrator is created during initial setup — follow First login.
Ports 8080 (backend API) and 8090 (workspace UI) are published to the host. The Spring actuator (health/metrics) listens on port 9090 inside the container and is not published by the Compose file — use docker compose ps and the logs to judge backend health, not an external :9090 call. The shipped Compose file also publishes PostgreSQL on 5432; for any non-local install, bind it to 127.0.0.1 or block it at the firewall — the backend reaches the database over the internal Docker network, so 5432 does not need to be exposed.
Step 6 — (recommended) Put a reverse proxy in front
For anything beyond local testing, terminate TLS at a reverse proxy and route two names: the UI to the workspace (:8090) and the API to the backend (:8080). Set API_BASE_URL to the public API name.
# Workspace UI
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/ssl/certs/pandev.crt;
ssl_certificate_key /etc/ssl/private/pandev.key;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Backend API
server {
listen 443 ssl http2;
server_name metrics.example.com; # matches API_BASE_URL
ssl_certificate /etc/ssl/certs/pandev.crt;
ssl_certificate_key /etc/ssl/private/pandev.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
See Network and ports for the full proxy, TLS, and firewall reference.
Deploy with Helm
The bundled chart in helm-chart/ deploys the same three components on Kubernetes 1.28+ (Helm 3). PostgreSQL is included as a StatefulSet, or you can point the chart at an external database.
Step 1 — Install
From the unpacked archive, install the chart and pass the public URLs for the API and the UI. The chart uses serverUrl and workspaceUrl to wire up Ingress hosts and to inject API_BASE_URL into the workspace automatically:
cd /opt/pandev
helm install pandev-metrics ./helm-chart \
--namespace metrics --create-namespace \
--set serverUrl=https://metrics.example.com \
--set workspaceUrl=https://app.example.com \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set postgresql.auth.password=<STRONG_DB_PASSWORD>
For anything beyond a quick trial, prefer a values.yaml file over long --set chains.
Step 2 — Key values
These are the parameters most installs touch. Run helm show values ./helm-chart for the complete, commented list — every parameter is documented there and in the helm-chart/README.md shipped in the archive.
| Value | Default | Purpose |
|---|---|---|
serverUrl | "" | Public URL of the backend; also becomes the workspace's API_BASE_URL |
workspaceUrl | "" | Public URL of the UI |
ingress.enabled / ingress.className | false / "" | Create an Ingress and pick the controller (e.g. nginx) |
server.image.tag / workspace.image.tag | 5.8.0 / 2.8.0 | Image versions; append -arm for arm64 nodes |
postgresql.enabled | true | Use the bundled PostgreSQL StatefulSet |
postgresql.auth.password | "" | Password for the bundled database (generated if left empty) |
postgresql.auth.username / .database | postgres / pandev_metrics_db | User and database name of the bundled PostgreSQL |
postgresql.primary.persistence.enabled | false | Persistent storage for the database — see the warning below |
externalDatabase.* | — | Used when postgresql.enabled=false |
:::danger Enable persistence for production
By default postgresql.primary.persistence.enabled=false, which backs the database with an emptyDir — all data is lost when the pod restarts. For any real install, enable a PersistentVolumeClaim:
postgresql:
auth:
password: "<STRONG_DB_PASSWORD>"
primary:
persistence:
enabled: true
size: 20Gi
# storageClass: "your-storage-class"
:::
Step 3 — (optional) Use an external database
To run against a managed or external PostgreSQL instead of the bundled one, disable the built-in database and fill in externalDatabase:
postgresql:
enabled: false
externalDatabase:
host: "db.internal"
port: 5432
user: "pandev"
password: "<STRONG_DB_PASSWORD>"
database: "pandev_metrics_db"
Step 4 — Verify
kubectl get pods -n metrics
helm status pandev-metrics -n metrics
Pods become Ready as their probes pass — the backend pod on its management port (9090), the workspace and PostgreSQL pods on their own checks. The release notes printed by Helm tell you the resolved URLs. Then open workspaceUrl in a browser and complete First login.
Upgrades
The upgrade path is the same for both deployments: bump the image tags and let Flyway migrate the schema on startup. Take a backup first (see below).
- Docker Compose — edit the image tags in
docker-compose.yml, thendocker compose pull && docker compose up -d. - Helm —
helm upgrade pandev-metrics ./helm-chart -n metrics -f values.yamlwith the newserver.image.tag/workspace.image.tag.
Flyway applies any pending schema changes automatically; you never run migrations by hand.
Backups and disaster recovery
A regular pg_dump of the PanDev Metrics database is sufficient — continuous archiving is not required.
docker compose exec postgres \
sh -c 'pg_dump -Fc -U "$POSTGRES_USER" "$POSTGRES_DB"' > pandev_metrics_$(date +%F).dump
The variables expand inside the postgres container (where Compose sets them), so the quoting matters — running pg_dump -U "$POSTGRES_USER" directly from the host would send empty values.
For an external database, run pg_dump from a host that can reach it. To recover, restore with pg_restore, then bring the stack back up — migrations align automatically on the next start.
Troubleshooting
Backend exits with FATAL: password authentication failed
POSTGRES_PASSWORD was changed after the database volume was already initialized. PostgreSQL only sets the password on the first start. Either set the password back to the original value, or reset the database: docker compose down -v (this deletes the postgres-data volume and all data) and docker compose up -d.
UI loads but every API call fails (blank dashboards, network errors)
API_BASE_URL is not reachable from the browser. It must be the public address of the backend, not an internal Docker hostname. Fix it in .env and recreate the workspace: docker compose up -d --force-recreate pandev-metrics-workspace.
Backend container exits immediately with Illegal instruction (often on a VM)
The backend is a GraalVM native image and needs a baseline of x86_64 CPU instructions (AVX2, FMA, BMI2, …). A hypervisor masking host CPU features is the usual cause. Pass the host CPU through to the VM — see system requirements → virtualization. On arm64 hosts, use the -arm image tags instead.
Helm: database data disappears after a pod restart
Persistence is disabled by default (emptyDir). Set postgresql.primary.persistence.enabled=true with a size and storageClass, then helm upgrade. See the persistence warning above.
FAQ
Do I need to install PostgreSQL myself?
No. PostgreSQL ships inside the distribution — as a container in Docker Compose and as a StatefulSet in the Helm chart. You only provide it externally if you deliberately choose postgresql.enabled=false (Helm) and point at your own instance.
Do I need to pre-create database tables?
No. The backend runs Flyway migrations automatically on first start and on every upgrade.
Does PanDev Metrics run on ARM / Apple Silicon?
Yes. Use the image tags with the -arm suffix (...:5.8.0-arm, ...:2.8.0-arm), which are native arm64 builds. The default tags are x86_64.
Can I disable outbound network access?
No. PanDev Metrics needs minimal outbound HTTPS to your Git provider and task tracker. Egress is minimal but cannot be disabled, and air-gapped deployments are not supported.
Which Kubernetes versions are supported?
Kubernetes 1.28+ with Helm 3. The chart and the Compose stack expose the same configuration shape.
Next steps
- First login — create the initial administrator account
- LDAP integration — connect your corporate directory for SSO
- Network and ports — finalize the reverse proxy and firewall rules
Related
- Reference: System requirements
- Concept: On-prem architecture