Skip to content

feat(devops): add out-of-the-box prometheus metrics and grafana observability stack#9344

Open
vishva-ux wants to merge 1 commit into
makeplane:previewfrom
vishva-ux:feature/prometheus-grafana-metrics
Open

feat(devops): add out-of-the-box prometheus metrics and grafana observability stack#9344
vishva-ux wants to merge 1 commit into
makeplane:previewfrom
vishva-ux:feature/prometheus-grafana-metrics

Conversation

@vishva-ux

@vishva-ux vishva-ux commented Jul 2, 2026

Copy link
Copy Markdown

Description

This PR integrates a self-contained monitoring and observability pipeline for local developer and self-hosted deployments. It adds a Prometheus metrics exporter directly to the Django API and mounts a local Prometheus + Grafana stack in Docker Compose to display live performance analytics.

Key Changes:

  1. Backend Instrumentation: Added django-prometheus dependency to base.txt and integrated django_prometheus middleware to track API request speeds and database query times.
  2. DevOps Orchestration: Added prometheus (port 9091) and grafana (port 3002) services to docker-compose-local.yml with host-isolated volume persistence.
  3. Grafana Provisioning: Configured automated datasource discovery and dashboard loading so the stack initializes without manual configuration. Created a pre-configured dashboard displaying request counts and API latencies.

Type of Change

  • Feature (non-breaking change which adds functionality)

Test Scenarios

  1. Ran docker compose -f docker-compose-local.yml up -d --build to compile dependencies.
  2. Verified the http://localhost:8000/metrics endpoint is live and serving standard Prometheus metrics.
  3. Verified the Prometheus target scraper is healthy (up) at http://localhost:9091/targets.
  4. Opened Grafana dashboard at http://localhost:3002 (login: admin/admin) and verified the custom Plane API Analytics dashboard is receiving and rendering real-time performance graphs.

References

  • Resolves request for local instance/self-hosted observability templates.

Summary by CodeRabbit

  • New Features

    • Added local monitoring support with Prometheus and Grafana.
    • Introduced a prebuilt dashboard to view API traffic and latency trends.
    • Added local service setup for metrics collection and visualization.
  • Bug Fixes

    • Enabled metric endpoints so API performance data can be collected in development environments.
  • Chores

    • Updated local development and container configuration to include the new monitoring services.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Prometheus-based monitoring for the API service. It integrates django-prometheus middleware and metrics endpoint into the Django app, and adds Prometheus and Grafana services with provisioning configuration and a dashboard to the local Docker Compose stack.

Changes

Prometheus and Grafana monitoring integration

Layer / File(s) Summary
Django Prometheus instrumentation
apps/api/requirements/base.txt, apps/api/plane/settings/local.py, apps/api/plane/urls.py
Adds django-prometheus dependency, registers it in INSTALLED_APPS and MIDDLEWARE, and exposes a /metrics URL route.
Prometheus scrape configuration
deployments/prometheus/prometheus.yml
Configures Prometheus global intervals and a scrape job for the plane-api target.
Grafana provisioning and dashboard
deployments/grafana/provisioning/datasources/datasource.yml, deployments/grafana/provisioning/dashboards/dashboard.yml, deployments/grafana/dashboards/plane-dashboard.json
Provisions a default Prometheus datasource, a file-based dashboard provider, and a dashboard with request rate and latency panels.
Docker Compose service wiring
docker-compose-local.yml
Adds prometheus and grafana services with bind-mounts, environment variables, port mappings, and persistent volumes.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PlaneAPI
  participant Prometheus
  participant Grafana

  Prometheus->>PlaneAPI: Scrape /metrics on api:8000
  PlaneAPI-->>Prometheus: Return request rate/latency metrics
  Grafana->>Prometheus: Query metrics via datasource
  Prometheus-->>Grafana: Return time-series data
  Grafana-->>Grafana: Render plane-dashboard.json panels
Loading

Estimated code review effort

Estimated code review effort: 2 (Simple) | ~12 minutes

Suggested labels

Suggested labels: devops, monitoring, infrastructure

Suggested reviewers

Suggested reviewers: DevOps/infrastructure maintainers familiar with the Docker Compose and API settings

Poem:
A rabbit hops through metrics streams,
Prometheus scrapes and Grafana gleams,
Dashboards glow with request and rate,
Middleware watches early and late,
Now every hop is tracked in style! 🐰📊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding Prometheus metrics and Grafana observability support.
Description check ✅ Passed The description includes the required sections and provides concrete change, type, test, and reference details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/api/requirements/base.txt (1)

78-80: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Bump django-prometheus to the current stable release

django-prometheus==2.3.1 is several releases behind; the current stable release is 2.5.0. If there isn’t a compatibility reason to stay pinned here, update the version to keep the dependency current.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/requirements/base.txt` around lines 78 - 80, The dependency pin for
django-prometheus in the requirements file is outdated. Update the version in
the existing django-prometheus entry to the current stable release, keeping the
package name and pin format intact, and make sure any related dependency
constraints in apps/api/requirements/base.txt remain consistent if there are
compatibility concerns.
docker-compose-local.yml (1)

166-168: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Hardcoded Grafana admin credential inconsistent with rest of file.

Every other service here (plane-mq, plane-minio, plane-db) sources credentials via env_file/${VAR} interpolation, but Grafana's admin password is hardcoded as admin directly in the compose file.

🔧 Proposed fix
     environment:
-      - GF_SECURITY_ADMIN_PASSWORD=admin
+      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
       - GF_USERS_ALLOW_SIGN_UP=false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose-local.yml` around lines 166 - 168, The Grafana service is
using a hardcoded admin password in its environment block, unlike the other
services that source secrets from env files or variable interpolation. Update
the Grafana configuration to use an interpolated variable or env_file-backed
value for GF_SECURITY_ADMIN_PASSWORD, and keep the existing
GF_USERS_ALLOW_SIGN_UP setting unchanged so the credential handling matches the
rest of the compose file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/plane/urls.py`:
- Line 23: The Prometheus metrics endpoint is currently mounted publicly through
the root include in the URL configuration, so update the URL wiring in the
`urlpatterns` setup to stop exposing `django_prometheus.urls` to unauthenticated
external callers. Move the metrics route behind an internal-only path or protect
it with access control at the proxy/app layer, and make sure the change is
applied where the `include("django_prometheus.urls")` entry is registered.

In `@deployments/grafana/provisioning/datasources/datasource.yml`:
- Around line 3-8: The Prometheus datasource provisioning in datasource.yml is
missing a fixed uid, which can break dashboard bindings that reference the
datasource by uid. Update the datasource entry to set the uid explicitly to
Prometheus so the provisioned datasource matches the references used by
plane-dashboard.json and remains stable across Grafana restarts.

---

Nitpick comments:
In `@apps/api/requirements/base.txt`:
- Around line 78-80: The dependency pin for django-prometheus in the
requirements file is outdated. Update the version in the existing
django-prometheus entry to the current stable release, keeping the package name
and pin format intact, and make sure any related dependency constraints in
apps/api/requirements/base.txt remain consistent if there are compatibility
concerns.

In `@docker-compose-local.yml`:
- Around line 166-168: The Grafana service is using a hardcoded admin password
in its environment block, unlike the other services that source secrets from env
files or variable interpolation. Update the Grafana configuration to use an
interpolated variable or env_file-backed value for GF_SECURITY_ADMIN_PASSWORD,
and keep the existing GF_USERS_ALLOW_SIGN_UP setting unchanged so the credential
handling matches the rest of the compose file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a005e0dc-d074-4819-a97e-799f5a97141a

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbf14a and b8b4d48.

📒 Files selected for processing (8)
  • apps/api/plane/settings/local.py
  • apps/api/plane/urls.py
  • apps/api/requirements/base.txt
  • deployments/grafana/dashboards/plane-dashboard.json
  • deployments/grafana/provisioning/dashboards/dashboard.yml
  • deployments/grafana/provisioning/datasources/datasource.yml
  • deployments/prometheus/prometheus.yml
  • docker-compose-local.yml

Comment thread apps/api/plane/urls.py
path("api/instances/", include("plane.license.urls")),
path("api/v1/", include("plane.api.urls")),
path("auth/", include("plane.authentication.urls")),
path("", include("django_prometheus.urls")),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether nginx or ingress configs expose the api's / or /metrics path externally
rg -n "metrics" -g '*.conf' -g 'nginx*' -g 'docker-compose*.yml'

Repository: makeplane/plane

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map likely deployment and routing files, then inspect the relevant ones.
git ls-files | rg '(^|/)(urls\.py|docker-compose.*\.yml|docker-compose.*\.yaml|nginx.*|.*\.conf|.*ingress.*|.*helm.*|values.*\.ya?ml)$'

echo
echo '--- urls.py references to django_prometheus ---'
rg -n "django_prometheus|metrics" apps api . --glob '!*node_modules/*' --glob '!*dist/*' --glob '!*build/*'

echo
echo '--- compose / ingress / nginx references to api exposure ---'
rg -n "api:|ports:|expose:|django_prometheus|metrics|ingress|nginx|proxy_pass|server_name|host:" \
  docker-compose* . \
  --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**'

Repository: makeplane/plane

Length of output: 21403


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- apps/api/plane/urls.py ---'
sed -n '1,120p' apps/api/plane/urls.py

echo
echo '--- docker-compose.yml ---'
sed -n '1,240p' docker-compose.yml

echo
echo '--- docker-compose-local.yml ---'
sed -n '1,240p' docker-compose-local.yml

echo
echo '--- apps/admin/nginx/nginx.conf ---'
sed -n '1,220p' apps/admin/nginx/nginx.conf

echo
echo '--- apps/space/nginx/nginx.conf ---'
sed -n '1,220p' apps/space/nginx/nginx.conf

echo
echo '--- apps/web/nginx/nginx.conf ---'
sed -n '1,220p' apps/web/nginx/nginx.conf

echo
echo '--- deployments/prometheus/prometheus.yml ---'
sed -n '1,220p' deployments/prometheus/prometheus.yml

Repository: makeplane/plane

Length of output: 11941


/metrics is exposed without auth in apps/api/plane/urls.py:23.

django_prometheus.urls is mounted at the root, and docker-compose-local.yml publishes api:8000 to the host, so these internal request/DB metrics are reachable by anyone who can hit that port. Lock /metrics to the internal network or add proxy auth if the path is forwarded in production.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/plane/urls.py` at line 23, The Prometheus metrics endpoint is
currently mounted publicly through the root include in the URL configuration, so
update the URL wiring in the `urlpatterns` setup to stop exposing
`django_prometheus.urls` to unauthenticated external callers. Move the metrics
route behind an internal-only path or protect it with access control at the
proxy/app layer, and make sure the change is applied where the
`include("django_prometheus.urls")` entry is registered.

Comment on lines +3 to +8
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== datasource.yml ==\n'
cat -n deployments/grafana/provisioning/datasources/datasource.yml

printf '\n== dashboard refs ==\n'
python3 - <<'PY'
import json, re, pathlib
p = pathlib.Path('deployments/grafana/dashboards/plane-dashboard.json')
data = json.loads(p.read_text())
text = p.read_text()

# Find occurrences of datasource references around uid/name keys
for key in ['uid', 'name']:
    print(f"\nOccurrences of '{key}':")
    for m in re.finditer(rf'"{key}"\s*:\s*"[^"]+"', text):
        start = max(0, m.start()-120)
        end = min(len(text), m.end()+120)
        snippet = text[start:end]
        print(snippet.replace('\n', ' '))
PY

Repository: makeplane/plane

Length of output: 2195


🌐 Web query:

Grafana provisioning datasource uid auto-generated if omitted dashboard datasource uid references Grafana 8.3

💡 Result:

When you omit the uid field in a Grafana datasource provisioning file, Grafana automatically generates a random UID for that datasource [1][2][3]. Starting in Grafana 8.3, Grafana shifted from referencing datasources by name to referencing them by UID within dashboard JSON definitions [4][5][6]. Because of this change, failing to explicitly specify a uid in your provisioning file can lead to significant issues [4][5]. If the UID is omitted, Grafana generates a new, random UID each time the system is redeployed or restarted [4][5]. Consequently, existing dashboards—which store a reference to a specific UID—will fail to find the datasource because the UID has changed, effectively "bricking" the dashboards [5][7][8]. To ensure stability and portability of dashboards across different Grafana environments (such as development, testing, and production), it is highly recommended to explicitly define a static, consistent uid for every provisioned datasource [4][7][9]. Example of explicit UID provisioning: datasources: - name: MyDataSource type: postgres uid: my_consistent_uid_001 #... other configuration options By manually setting the uid, you ensure that dashboard references remain valid across deployments, even if you rename the datasource later [4][6].

Citations:


🌐 Web query:

site:grafana.com datasource provisioning uid Grafana dashboard uid reference Prometheus

💡 Result:

When provisioning data sources in Grafana, you can explicitly define a unique identifier (UID) in your configuration file to ensure it remains consistent across different environments [1][2][3]. This UID is critical for dashboards, as they must reference this identifier to correctly link to the intended data source [4][5]. To provision a data source with a specific UID, include the uid field in your YAML provisioning file: apiVersion: 1 datasources: - name: My Prometheus type: prometheus uid: my-prometheus-uid # Explicitly defined UID access: proxy url: http://prometheus:9090 In your dashboard JSON files, the datasource field can reference this data source in two primary ways: 1. By Name: You can use the name of the data source as a string (e.g., "datasource": "My Prometheus") [5]. 2. By UID: You can use an object that specifies the type and the UID, or simply the UID string depending on your Grafana version and specific configuration, to ensure the dashboard points to the exact data source regardless of its display name [4][5]. To avoid issues where dashboards fail to find the data source (especially during migrations or when moving between systems), it is best practice to always provide a static, explicit UID in your data source provisioning YAML rather than allowing Grafana to generate a random one [1][5]. If you are exporting dashboards for use across different systems, ensuring the data source UID is identical in both environments prevents broken references [6][2]. Note that in recent versions of Grafana, there has been a move toward enforcing stricter UID formats to improve consistency [7]. Always verify that your defined UID is unique within your Grafana organization [2].

Citations:


Add a stable uid to the Prometheus datasource
deployments/grafana/dashboards/plane-dashboard.json binds panels to uid: "Prometheus", but this provisioning file lets Grafana generate a different UID on startup. Add uid: Prometheus here so the dashboard keeps resolving the datasource.

🔧 Proposed fix
 datasources:
   - name: Prometheus
     type: prometheus
     access: proxy
     url: http://prometheus:9090
+    uid: Prometheus
     isDefault: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
uid: Prometheus
isDefault: true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deployments/grafana/provisioning/datasources/datasource.yml` around lines 3 -
8, The Prometheus datasource provisioning in datasource.yml is missing a fixed
uid, which can break dashboard bindings that reference the datasource by uid.
Update the datasource entry to set the uid explicitly to Prometheus so the
provisioned datasource matches the references used by plane-dashboard.json and
remains stable across Grafana restarts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant