Azure Optimizations
The Azure Optimizations module scans your Azure subscription for cost reduction opportunities using Azure Cost Management and Azure Advisor APIs. It stores findings as "opportunities" in the shared database and surfaces them in the Strawly UI.
What it scans
- Rightsizing recommendations — virtual machines and services that are over-provisioned relative to actual usage
- Termination opportunities — idle or unused resources that can be stopped or deleted
- Azure Advisor cost recommendations
Results appear in the Azure Optimizations section of the Strawly UI with estimated savings, severity, and status tracking.
Prerequisites
Before enabling this module, you need an Azure Service Principal with read-only access to your subscription.
Create a Service Principal
az ad sp create-for-rbac --name strawly-readonly --role Reader --scopes /subscriptions/<subscription-id>
This outputs a JSON object with appId (client ID), password (client secret), and tenant (tenant ID). Save these values.
Assign Cost Management Reader
The Reader role alone does not grant access to cost data. Also assign Cost Management Reader:
az role assignment create \
--assignee <app-id> \
--role "Cost Management Reader" \
--scope /subscriptions/<subscription-id>
Find your subscription ID
az account show --query id --output tsv
Enable the module
-
Open
strawly-deployment.ymland setenabled: true:yaml modules: optimizations-azure: enabled: true port: 3002 -
Add credentials to
.env:bash AZURE_TENANT_ID=<your-tenant-id> AZURE_CLIENT_ID=<your-client-id> # the "appId" from the SP creation output AZURE_CLIENT_SECRET=<your-client-secret> # the "password" from the SP creation output AZURE_SUBSCRIPTION_ID=<your-subscription-id> -
Regenerate and redeploy:
bash npm run generate-compose docker compose -f docker-compose.generated.yml pull docker compose -f docker-compose.generated.yml up -d -
Verify the module is running:
bash curl http://localhost:3002/healthExpected response:
json { "status": "ok", "service": "strawly-optimizations-azure", "timestamp": "2026-05-17T03:00:00.000Z" }
Scan schedule
By default, the module scans your Azure subscription daily at 3:00 AM UTC. To change the schedule, edit strawly-deployment.yml:
modules:
optimizations-azure:
schedule:
cron: "0 6 * * *" # 6 AM UTC
timezone: "UTC"
Then regenerate and redeploy.
Trigger a manual scan
To run a scan immediately without waiting for the schedule:
curl -X POST http://localhost:3002/opportunities/scan
Or use the Scan now button in the Azure Optimizations section of the Strawly UI (via Settings).
The module also reads its enabled state and schedule from the shared settings table. You can toggle the module on/off and change the schedule from the Strawly Settings UI without redeploying.
API endpoints
The module exposes these endpoints (consumed by the Strawly backend — not intended for direct use):
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness check |
GET |
/opportunities |
List opportunities. Filter by ?status, ?category, ?priority |
GET |
/opportunities/:id |
Get a single opportunity |
POST |
/opportunities/scan |
Trigger an immediate scan |
PATCH |
/opportunities/:id/status |
Update status (identified / in-progress / implemented / dismissed) |
Working with opportunities
Opportunities appear in the Azure Optimizations dashboard in the Strawly UI. For each opportunity you can:
- View the estimated monthly saving and affected resource
- Update the status as your team acts on it (
In Progress,Implemented,Dismissed) - Filter by category, status, and severity
Troubleshooting
No opportunities appearing after the first scan
- Confirm the scan has run: check the module logs for
Azure scan complete. - If the scan ran but found zero opportunities, Azure Advisor may not have generated recommendations yet for your subscription — this is normal for new or small subscriptions.
- Trigger a manual scan and watch the logs:
docker compose -f docker-compose.generated.yml logs -f optimizations-azure
Authentication errors
Symptoms: logs show 401 Unauthorized or 403 Forbidden from Azure APIs.
- Verify
AZURE_TENANT_ID,AZURE_CLIENT_ID, andAZURE_CLIENT_SECRETare correct in.env. - Confirm the Service Principal has both
ReaderandCost Management Readerroles on the subscription. - Check the client secret has not expired in Azure Active Directory.
Module exits immediately on startup
- Check logs:
docker compose -f docker-compose.generated.yml logs optimizations-azure - Missing environment variables are the most common cause. Confirm all four
AZURE_*variables are set in.env.