diff --git a/README.md b/README.md
index 20ba61d31e..ccf4d19a4e 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,33 @@
# Appwrite's fork of Grafana OnCall
-Grafana archived OnCall OSS on 2026-03-24. [Appwrite](https://appwrite.io) maintains this
-fork for its own use — we keep it working for our needs, but offer no support or
-roadmap commitments. Engine images are published to
-[`ghcr.io/appwrite/grafana-oncall`](https://github.com/appwrite/grafana-oncall/pkgs/container/grafana-oncall).
+Grafana archived [OnCall OSS](https://github.com/grafana/oncall) on 2026-03-24 at v1.16.11.
+[Appwrite](https://appwrite.io) maintains this fork for its own use.
+
+## What's different from upstream
+
+- **It runs on current Grafana.** Grafana 13 ships React 19, which removed `ReactDOM.findDOMNode`.
+ Upstream's plugin reaches it through three libraries, so escalation chains, notification policies
+ and the rotation modals crash on mount. Fixed by moving drag-and-drop to `@dnd-kit`, passing
+ `nodeRef` to `react-draggable`, and replacing `react-transition-group` with a CSS animation.
+- **Insights renders.** Its `alert_groups_total` variable was seeded multi-valued and URL-synced, so
+ a restored value interpolated a regex where PromQL wants a metric name, breaking every panel.
+- **End-to-end tests run against Grafana 12 and 13**, where upstream tested 10 and 11.
+- **~290 security patches.** Dependabot alerts went from 309 to 18.
+- **Django 5.2 LTS**, where upstream is on 4.2 — its extended support ended in April 2026.
+- **`fcm-django` from PyPI** rather than a tarball of a now-archived fork.
+- **Releases ship both halves**: multi-arch engine images to
+ [`ghcr.io/appwrite/grafana-oncall`](https://github.com/appwrite/grafana-oncall/pkgs/container/grafana-oncall)
+ and a plugin archive attached to each
+ [release](https://github.com/appwrite/grafana-oncall/releases). Upstream's catalog build is frozen
+ at 1.16.11.
+
+## What it does not offer
+
+- No support and no roadmap commitments. We keep it working for our needs.
+- The plugin is unsigned, since signing needs a grafana.com access policy, so it needs
+ `GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=grafana-oncall-app`.
+- The helm chart is not published to a helm repository; install it from a checkout.
+- The mobile app relied on Grafana Cloud's push relay, which went away with the archival.
## Grafana OnCall
@@ -46,7 +70,7 @@ We prepared multiple environments:
1. Download [`docker-compose.yml`](docker-compose.yml):
```bash
- curl -fsSL https://raw.githubusercontent.com/grafana/oncall/dev/docker-compose.yml -o docker-compose.yml
+ curl -fsSL https://raw.githubusercontent.com/appwrite/grafana-oncall/main/docker-compose.yml -o docker-compose.yml
```
2. Set variables:
@@ -128,36 +152,38 @@ Here are some API calls that can be made to help if you are having difficulty co
## Update version
-To update your Grafana OnCall hobby environment:
+The engine and plugin are released together and are not meant to be mixed across versions, so one
+variable moves both. Set it to the [release](https://github.com/appwrite/grafana-oncall/releases) you
+want in `.env`:
```shell
-# Update Docker image
-docker-compose pull engine
+echo "ONCALL_VERSION=1.19.1" >> .env
-# Re-deploy
+docker-compose pull
docker-compose up -d
```
-After updating the engine, you'll also need to click the "Update" button on the [plugin version page](http://localhost:3000/plugins/grafana-oncall-app?page=version-history).
-See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin-management/#update-a-plugin) for more
-info on updating Grafana plugins.
+The plugin is not in the grafana.com catalog, so it does not update through the plugin page. Grafana
+only installs a plugin that is not already present, so to move the plugin you also have to clear the
+old copy from its volume:
+
+```shell
+docker-compose exec grafana rm -rf /var/lib/grafana/plugins/grafana-oncall-app
+docker-compose restart grafana
+```
## Join community
[
](https://slack.grafana.com/)
[
](https://community.grafana.com/)
-Have a question, comment or feedback? Don't be afraid to [open an issue](https://github.com/grafana/oncall/issues/new/choose)!
-
-## Stargazers over time
-
-[](https://starchart.cc/grafana/oncall)
+Have a question, comment or feedback? Don't be afraid to [open an issue](https://github.com/appwrite/grafana-oncall/issues/new/choose)!
## Further Reading
-- _Automated migration from other on-call tools_ - [Migrator](https://github.com/grafana/oncall/tree/dev/tools/migrators)
+- _Automated migration from other on-call tools_ - [Migrator](https://github.com/appwrite/grafana-oncall/tree/main/tools/migrators)
- _Documentation_ - [Grafana OnCall](https://grafana.com/docs/oncall/latest/)
- _Overview Webinar_ - [YouTube](https://www.youtube.com/watch?v=7uSe1pulgs8)
-- _How To Add Integration_ - [How to Add Integration](https://github.com/grafana/oncall/tree/dev/engine/config_integrations/README.md)
+- _How To Add Integration_ - [How to Add Integration](https://github.com/appwrite/grafana-oncall/tree/main/engine/config_integrations/README.md)
- _Blog Post_ - [Announcing Grafana OnCall, the easiest way to do on-call management](https://grafana.com/blog/2021/11/09/announcing-grafana-oncall/)
- _Presentation_ - [Deep dive into the Grafana, Prometheus, and Alertmanager stack for alerting and on-call management](https://grafana.com/go/observabilitycon/2021/alerting/?pg=blog)
diff --git a/docker-compose-developer.yml b/docker-compose-developer.yml
index 7b4143f260..38aefd0cc8 100644
--- a/docker-compose-developer.yml
+++ b/docker-compose-developer.yml
@@ -8,8 +8,7 @@ x-oncall-build: &oncall-build-args
target: ${ONCALL_IMAGE_TARGET:-dev}
labels: *oncall-labels
cache_from:
- - grafana/oncall:latest
- - grafana/oncall:dev
+ - ghcr.io/appwrite/grafana-oncall:latest
x-oncall-volumes: &oncall-volumes
- ./engine:/etc/app
diff --git a/docker-compose-mysql-rabbitmq.yml b/docker-compose-mysql-rabbitmq.yml
index a00d701672..280c7563d2 100644
--- a/docker-compose-mysql-rabbitmq.yml
+++ b/docker-compose-mysql-rabbitmq.yml
@@ -22,7 +22,7 @@ x-environment: &oncall-environment
services:
engine:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
restart: always
ports:
- "8080:8080"
@@ -39,7 +39,7 @@ services:
condition: service_started
celery:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
restart: always
command: sh -c "./celery_with_exporter.sh"
environment: *oncall-environment
@@ -54,7 +54,7 @@ services:
condition: service_started
oncall_db_migration:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
command: python manage.py migrate --noinput
environment: *oncall-environment
depends_on:
@@ -143,7 +143,11 @@ services:
GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
- GF_INSTALL_PLUGINS: grafana-oncall-app
+ # The catalog build is grafana's archived one, so install this fork's plugin archive.
+ # ONCALL_VERSION pins the engine too: the plugin and engine are released together
+ # and are not meant to be mixed across versions.
+ # yamllint disable-line rule:line-length
+ GF_INSTALL_PLUGINS: https://github.com/appwrite/grafana-oncall/releases/download/v${ONCALL_VERSION:-1.19.1}/grafana-oncall-app-${ONCALL_VERSION:-1.19.1}.zip;grafana-oncall-app
GF_AUTH_MANAGED_SERVICE_ACCOUNTS_ENABLED: true
deploy:
resources:
diff --git a/docker-compose.yml b/docker-compose.yml
index 3f6aecdbdb..4ded47cede 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,7 +16,7 @@ x-environment: &oncall-environment
services:
engine:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
restart: always
ports:
- "8080:8080"
@@ -31,7 +31,7 @@ services:
condition: service_healthy
celery:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
restart: always
command: sh -c "./celery_with_exporter.sh"
environment: *oncall-environment
@@ -44,7 +44,7 @@ services:
condition: service_healthy
oncall_db_migration:
- image: grafana/oncall
+ image: ghcr.io/appwrite/grafana-oncall:v${ONCALL_VERSION:-1.19.1}
command: python manage.py migrate --noinput
environment: *oncall-environment
volumes:
@@ -93,7 +93,11 @@ services:
GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
- GF_INSTALL_PLUGINS: grafana-oncall-app
+ # The catalog build is grafana's archived one, so install this fork's plugin archive.
+ # ONCALL_VERSION pins the engine too: the plugin and engine are released together
+ # and are not meant to be mixed across versions.
+ # yamllint disable-line rule:line-length
+ GF_INSTALL_PLUGINS: https://github.com/appwrite/grafana-oncall/releases/download/v${ONCALL_VERSION:-1.19.1}/grafana-oncall-app-${ONCALL_VERSION:-1.19.1}.zip;grafana-oncall-app
GF_AUTH_MANAGED_SERVICE_ACCOUNTS_ENABLED: true
volumes:
- grafana_data:/var/lib/grafana
diff --git a/helm/oncall/Chart.yaml b/helm/oncall/Chart.yaml
index efa02053da..31afed1091 100644
--- a/helm/oncall/Chart.yaml
+++ b/helm/oncall/Chart.yaml
@@ -2,8 +2,8 @@ apiVersion: v2
name: oncall
description: Developer-friendly incident response with brilliant Slack integration
type: application
-version: 1.15.6
-appVersion: v1.15.6
+version: 1.19.1
+appVersion: v1.19.1
dependencies:
- name: cert-manager
version: v1.8.0
diff --git a/helm/oncall/README.md b/helm/oncall/README.md
index 993f0649e0..6405924682 100644
--- a/helm/oncall/README.md
+++ b/helm/oncall/README.md
@@ -5,7 +5,7 @@ It will deploy Grafana OnCall engine and celery workers, along with RabbitMQ clu
It will also deploy cert manager and nginx ingress controller, as Grafana OnCall backend might need to be externally available
to receive alerts from other monitoring systems. Grafana OnCall engine acts as a backend and can be connected to the
Grafana frontend plugin named Grafana OnCall.
-Architecture diagram can be found [here](https://raw.githubusercontent.com/grafana/oncall/dev/docs/img/architecture_diagram.png)
+Architecture diagram can be found [here](https://raw.githubusercontent.com/appwrite/grafana-oncall/main/docs/img/architecture_diagram.png)
## Production usage
@@ -23,12 +23,13 @@ Here are the instructions on how to set up your own [ingress](#set-up-external-a
## Install
-### Prepare the repo
+### Prepare the chart
+
+This chart is not published to a helm repository, so install it from a checkout:
```bash
-# Add the repository
-helm repo add grafana https://grafana.github.io/helm-charts
-helm repo update
+git clone https://github.com/appwrite/grafana-oncall.git
+cd grafana-oncall
```
### Installing the helm chart
@@ -40,7 +41,7 @@ helm install \
--set base_url=example.com \
--set grafana."grafana\.ini".server.domain=example.com \
release-oncall \
- grafana/oncall
+ ./helm/oncall
```
Follow the `helm install` output to finish setting up Grafana OnCall backend and Grafana OnCall frontend plugin e.g.
@@ -83,7 +84,7 @@ helm upgrade \
--set base_url=example.com \
--set grafana."grafana\.ini".server.domain=example.com \
release-oncall \
- grafana/oncall
+ ./helm/oncall
```
### Passwords and external secrets
@@ -382,9 +383,8 @@ externalRedis:
## Update
```bash
-# Add & upgrade the repository
-helm repo add grafana https://grafana.github.io/helm-charts
-helm repo update
+# Pull the latest chart
+git pull
# Re-deploy
helm upgrade \
@@ -393,12 +393,21 @@ helm upgrade \
--set base_url=example.com \
--set grafana."grafana\.ini".server.domain=example.com \
release-oncall \
- grafana/oncall
+ ./helm/oncall
```
-After re-deploying, please also update the Grafana OnCall plugin on the plugin version page.
-See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin-management/#update-a-plugin) for
-more info on updating Grafana plugins.
+The plugin is not in the grafana.com catalog, so it does not update through the plugin page. The
+bundled Grafana installs it from this fork's release archive, and the chart derives that version from
+`image.tag` (falling back to the chart's `appVersion`) — so bumping the engine moves the plugin with
+it, and the two cannot end up on different versions.
+
+Grafana only installs a plugin that is not already present, so with `grafana.persistence` enabled the
+old copy has to be cleared before it will reinstall:
+
+```bash
+kubectl exec deploy/release-oncall-grafana -- rm -rf /var/lib/grafana/plugins/grafana-oncall-app
+kubectl rollout restart deploy/release-oncall-grafana
+```
## Uninstall
diff --git a/helm/oncall/templates/grafana-plugin-env.yaml b/helm/oncall/templates/grafana-plugin-env.yaml
new file mode 100644
index 0000000000..5965aa485d
--- /dev/null
+++ b/helm/oncall/templates/grafana-plugin-env.yaml
@@ -0,0 +1,19 @@
+{{- if .Values.grafana.enabled }}
+{{/*
+ The plugin is not in the grafana.com catalog, so the bundled Grafana installs it from this fork's
+ release archive. The version is taken from the same expression as the engine image, so overriding
+ image.tag moves both halves: they are released together and are not meant to be mixed.
+
+ This is a ConfigMap because subchart values cannot be templated — grafana.envValueFrom points at
+ it, and the grafana chart runs tpl over that block so it can resolve the release-scoped name.
+*/}}
+{{- $version := .Values.image.tag | default .Chart.AppVersion }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Release.Name }}-oncall-plugin
+ labels:
+ app: {{ include "oncall.name" . }}
+data:
+ GF_INSTALL_PLUGINS: https://github.com/appwrite/grafana-oncall/releases/download/{{ $version }}/grafana-oncall-app-{{ trimPrefix "v" $version }}.zip;grafana-oncall-app
+{{- end }}
diff --git a/helm/oncall/tests/__snapshot__/integrations_deployment_test.yaml.snap b/helm/oncall/tests/__snapshot__/integrations_deployment_test.yaml.snap
index d055982f49..2e1f46d445 100644
--- a/helm/oncall/tests/__snapshot__/integrations_deployment_test.yaml.snap
+++ b/helm/oncall/tests/__snapshot__/integrations_deployment_test.yaml.snap
@@ -102,7 +102,7 @@ detached_integrations.enabled=true -> should create integrations deployment:
value: ""
- name: ROOT_URLCONF
value: engine.integrations_urls
- image: grafana/oncall:v1.3.39
+ image: ghcr.io/appwrite/grafana-oncall:v1.3.39
imagePullPolicy: Always
livenessProbe:
httpGet:
diff --git a/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap b/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap
index b3eb11b572..ac411fdcc3 100644
--- a/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap
+++ b/helm/oncall/tests/__snapshot__/telegram_polling_deployment_test.yaml.snap
@@ -84,7 +84,7 @@ telegramPolling.enabled=true -> should create telegram polling deployment:
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: telegram-polling
securityContext: {}
diff --git a/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap b/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap
index 5c6e4adb7e..02b62b9bd1 100644
--- a/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap
+++ b/helm/oncall/tests/__snapshot__/wait_for_db_test.yaml.snap
@@ -76,7 +76,7 @@ database.type=mysql -> should create initContainer for MySQL database (default):
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
@@ -164,7 +164,7 @@ database.type=mysql -> should create initContainer for MySQL database (default):
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
@@ -252,7 +252,7 @@ database.type=mysql -> should create initContainer for MySQL database (default):
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
@@ -343,7 +343,7 @@ database.type=postgresql -> should create initContainer for PostgreSQL database:
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
@@ -433,7 +433,7 @@ database.type=postgresql -> should create initContainer for PostgreSQL database:
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
@@ -523,7 +523,7 @@ database.type=postgresql -> should create initContainer for PostgreSQL database:
value: amqp
- name: RABBITMQ_VHOST
value: ""
- image: grafana/oncall:v1.2.36
+ image: ghcr.io/appwrite/grafana-oncall:v1.2.36
imagePullPolicy: Always
name: wait-for-db
resources:
diff --git a/helm/oncall/tests/image_deployments_test.yaml b/helm/oncall/tests/image_deployments_test.yaml
index c7a1ae0959..23ee0bfb96 100644
--- a/helm/oncall/tests/image_deployments_test.yaml
+++ b/helm/oncall/tests/image_deployments_test.yaml
@@ -15,7 +15,7 @@ tests:
asserts:
- equal:
path: spec.template.spec.containers[0].image
- value: grafana/oncall:1.2.36
+ value: ghcr.io/appwrite/grafana-oncall:1.2.36
- equal:
path: spec.template.spec.containers[0].imagePullPolicy
value: Always
diff --git a/helm/oncall/values.yaml b/helm/oncall/values.yaml
index 826e0a5be3..4a10edac63 100644
--- a/helm/oncall/values.yaml
+++ b/helm/oncall/values.yaml
@@ -15,7 +15,7 @@ imagePullSecrets: []
image:
# Grafana OnCall docker image repository
- repository: grafana/oncall
+ repository: ghcr.io/appwrite/grafana-oncall
tag:
pullPolicy: Always
@@ -642,13 +642,21 @@ grafana:
accessControlOnCall: false
env:
GF_AUTH_MANAGED_SERVICE_ACCOUNTS_ENABLED: true
+ # this fork's plugin carries no grafana.com signature
+ GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: grafana-oncall-app
persistence:
enabled: true
# Disable psp as PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
rbac:
pspEnabled: false
- plugins:
- - grafana-oncall-app
+ # The plugin archive comes from the ConfigMap this chart renders, which derives the version from
+ # image.tag/appVersion — so the engine and plugin cannot end up on different versions. Setting
+ # grafana.plugins here instead would install the catalog build, which is grafana's archived one.
+ envValueFrom:
+ GF_INSTALL_PLUGINS:
+ configMapKeyRef:
+ name: '{{ .Release.Name }}-oncall-plugin'
+ key: GF_INSTALL_PLUGINS
extraVolumes:
- name: provisioning
configMap: