grafana-azure
$
npx mdskill add microsoft/agentic-journeys/grafana-azureDeploy Grafana OSS to Azure Container Apps using Bicep and Azure Developer CLI (azd).
SKILL.md
.github/skills/grafana-azureView on GitHub ↗
---
name: grafana-azure
description: Deploy Grafana OSS to Azure Container Apps. Use when deploying Grafana for metrics, logs, and traces visualization with optional PostgreSQL backend.
---
# Grafana Azure Deployment Skill
Deploy Grafana OSS to Azure Container Apps using Bicep and Azure Developer CLI (azd).
> **Reproducibility Verified**: This deployment has been tested multiple times from scratch. Deploy time: ~2 minutes.
## Overview
Grafana is an open-source observability platform for metrics, logs, and traces visualization. This skill deploys Grafana OSS (not Azure Managed Grafana) to Azure Container Apps.
**`azure.yaml` has no `services:` block.** The Container App's public image is set in Bicep, so azd has nothing to package. A service with `image:` makes azd pull the image through local Docker, which fails on Windows hosts running Windows containers and makes Docker a requirement. See the [container-apps-deployment skill](../container-apps-deployment/SKILL.md#public-images-no-azd-service).
## Prerequisites and Portability
Require Azure CLI, Azure Developer CLI 1.28.0 or later, and Node.js LTS or later for portable verification. Don't require OpenSSL, Bash command substitution, or host-specific shell scripts. See `../../../docs/tool-installation.md`.
## Critical: Infrastructure Generation
This skill provides Grafana-specific configuration only. Infrastructure (Bicep, azure.yaml) should be generated fresh each time by the official `azure-prepare` → `azure-validate` → `azure-deploy` pipeline. Do NOT rely on pre-existing infra code.
### Validate the pinned AVM interface
Check parameters, outputs, and defaults against the exact version in each `br/public:avm/...:<version>` reference before generating module calls. The upstream repository's `main` branch may describe a newer interface than the published version. For example, the Grafana run hit `BCP037` because workspace module `0.9.1` didn't accept the `features` parameter shown in newer source. Use the pinned version's supported configuration rather than assuming the latest source applies. Compile Bicep and run `azd provision --preview --no-prompt` before deployment.
### Defer Log Analytics lookups until its module completes
When a monitoring module creates Log Analytics, put the Container Apps environment in a separate module with an explicit dependency on the monitoring module. Pass the workspace name to that later module, and resolve the existing workspace's `customerId` and `listKeys()` inside it.
Don't resolve those values in the parent deployment and pass them as module parameters. A constant-name workspace lookup can be evaluated before the monitoring module finishes, causing `ResourceNotFound` on a fresh deployment even when preview succeeds. The verified pattern is: monitoring module completes, environment module resolves workspace settings and creates the environment, then the Container App uses the environment module's resource-ID output.
## Critical: Subscription Context
**ALWAYS set AZURE_SUBSCRIPTION_ID explicitly before running `azd up`.** Read it with `az account show --query id -o tsv`, then pass the returned value to `azd env set AZURE_SUBSCRIPTION_ID <subscription-id>`.
Without this, azd and Azure MCP tools will fail silently or produce incomplete deployments. The `azure_deploy_app_logs` tool also requires subscription context.
## Critical: Bicep Output Naming
Bicep outputs MUST use SCREAMING_SNAKE_CASE (e.g., `GRAFANA_URL`, `GRAFANA_FQDN`) for azd to map them into environment values. Without this, `azd env get-value` returns "key not found".
## Architecture
```mermaid
graph TB
subgraph RG["Azure Resource Group"]
LA["Log Analytics Workspace"]
subgraph CAE["Container Apps Environment"]
GF["Grafana Container App<br/>Port 3000 · SQLite (default)<br/>Scale 0-1 replicas"]
end
end
LA -->|logs & metrics| CAE
```
## Quick Start (Verified)
```text
# 1. Register providers (one-time per subscription)
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
# 2. Create environment
azd env new my-grafana-env
# 3. Set required variables (replace placeholders)
azd env set AZURE_SUBSCRIPTION_ID "<subscription-id>"
azd env set AZURE_LOCATION "westus"
azd env set GRAFANA_ADMIN_PASSWORD "<securely-generated-secret>"
# 4. Deploy (~2 minutes)
azd up
# 5. Access Grafana
azd env get-value GRAFANA_URL
# Login: admin / <your GRAFANA_ADMIN_PASSWORD>
```
**Deployment time breakdown:**
- Resource Group: ~4s
- Log Analytics: ~25s
- Container Apps Environment: ~38s
- Grafana Container App: ~10s
- **Total: ~2 minutes**
## Environment Variables
Grafana is configured via environment variables in the Container App:
| Variable | Description | Value |
|----------|-------------|-------|
| `GF_SECURITY_ADMIN_USER` | Admin username | From parameter |
| `GF_SECURITY_ADMIN_PASSWORD` | Admin password | From secret |
| `GF_SERVER_HTTP_PORT` | HTTP port | 3000 |
| `GF_SERVER_ROOT_URL` | Public URL | Auto-configured |
| `GF_AUTH_ANONYMOUS_ENABLED` | Anonymous access | false |
See [config/environment-variables.md](config/environment-variables.md) for full list.
## Health Probes
| Type | Path | Port | Interval |
|------|------|------|----------|
| Liveness | /api/health | 3000 | 30s |
| Readiness | /api/health | 3000 | 10s |
| Startup | /api/health | 3000 | 10s (30 failures allowed) |
## Outputs
After deployment:
- **GRAFANA_URL**: Public HTTPS URL
- **GRAFANA_FQDN**: Container App FQDN
- **GRAFANA_ADMIN_USER**: Admin username
## Verification
Run the checked-in verifier from the repository root:
```text
node .github/scripts/verify-grafana.mjs
```
The verifier reads `GRAFANA_URL` through `azd`, requires HTTP 200 from `/api/health`, asserts `database: "ok"`, and prints the deployment URL. Verify authenticated browser login separately with the deployed admin credentials. Retrieve a generated password only in a private terminal, and never print or paste it into the agent session or shared logs.
For SQLite, also inspect the deployed Container App's `properties.template.scale.maxReplicas` through Azure CLI and require it to equal `1`. The HTTP verifier doesn't check scaling configuration.
## Scaling
- **Min replicas**: 0 (scale to zero when idle)
- **Max replicas**: 1 (required for SQLite)
- **Scaling rule**: HTTP concurrent requests (10 per replica)
Generate `maxReplicas: 1` whenever Grafana uses SQLite, including when SQLite is persisted on Azure Files. Separate container-local databases cause inconsistent dashboards, users, and sessions across replicas. Keep this cap when temporarily setting `minReplicas: 1` for verification. Multiple Grafana replicas require a shared PostgreSQL or MySQL database; don't add one to the default SQLite lab.
## Storage Considerations
By default, Grafana uses SQLite which stores data in the container. For production:
1. Add Azure Files for persistent storage
2. Or use PostgreSQL/MySQL backend
## Tear Down
```bash
azd down --force --purge
```
**Note:** Teardown takes 3-5 minutes (Container Apps environment deletion is slow).
## Azure MCP Tools
Use these Azure MCP Server tools for Grafana deployments:
| Tool | When to Use |
|------|-------------|
| `azure_bicep_schema` | Get latest schemas for `Microsoft.App/containerApps` and `Microsoft.App/managedEnvironments` |
| `azure_deploy_architecture` | Generate Mermaid architecture diagrams for the Grafana deployment |
| `azure_deploy_plan` | Validate the deployment plan before `azd up` — use `target=ContainerApp` |
| `azure_deploy_app_logs` | Fetch container logs from Log Analytics when troubleshooting startup or 502 issues |
## Troubleshooting
See [troubleshooting.md](troubleshooting.md) for common issues and lessons learned.