Guide

Set up a spend-by-service dashboard

Build a CloudWatch dashboard that shows what each AWS service has cost so far this month, give CloudPeek read-only access to it, and put it on your Home Screen. Use the AWS console, or deploy it all at once with CloudFormation or Terraform.

Updated 13 October 2026

What you'll build

You'll create a CloudWatch dashboard named CloudPeek-Spend with two widgets. The first is a bar chart of the five services you've spent most on so far this month, found automatically, with your account's total. The second is that month-to-date total with a sparkline of the past week. In CloudPeek they look like this:

Services that don't fit are added together as Other.

It takes about 15 minutes in the AWS console, or one deploy with CloudFormation or Terraform. Pick your tool on the tabs in each step.

Before you start

  • An AWS account, and a sign-in that can change billing preferences and create CloudWatch dashboards and IAM users and policies, such as an administrator. If your accounts are in AWS Organizations, use the management account to see the whole organisation's spend.
  • CloudPeek on your iPhone or iPad.

What it costs. Billing metrics are free. The CloudWatch free tier includes three dashboards; beyond that, each dashboard is $3 a month. CloudPeek's refreshes call GetMetricData, which AWS bills at $0.01 per 1,000 metrics. With both widgets pinned and refreshing every 30 minutes, that's 8 metrics per refresh, about 12¢ a month. See CloudWatch pricing.

1. Turn on billing metrics

AWS only sends your estimated charges to CloudWatch after you opt in. You do this once per account, in the console: CloudFormation, Terraform and the AWS CLI can't change this setting.

  1. Sign in to the AWS console and open Billing and Cost Management › Billing preferences.
  2. Under Alert preferences, choose Edit.
  3. Select Receive CloudWatch billing alerts and save the change.

Estimated charges are published to the US East (N. Virginia) Region, us-east-1, several times a day. They count from the start of the month, so they go back to zero each month. The first data can take a few hours to appear. If you're not signed in as the root user, the account must also allow IAM access to billing information (under Account › IAM user and role access to Billing information).

Until the first data arrives, the dashboard you'll create next says No data available. To check whether billing metrics have started, open CloudWatch in us-east-1 and choose Metrics › All metrics: a Billing namespace appears once they have. Metrics Explorer doesn't list billing metrics, so look under All metrics.

2. Create the dashboard

  1. Open the CloudWatch console in us-east-1 and choose Create dashboard.
  2. Name it CloudPeek-Spend and choose Create dashboard. If the Add widget window opens, close it.
  3. Choose Actions › View/edit source, replace the source with the dashboard JSON below, and choose Update.
  4. Choose Save.

Download spend-by-service.json (it's shown below), then run:

Shell
aws cloudwatch put-dashboard \
  --region us-east-1 \
  --dashboard-name CloudPeek-Spend \
  --dashboard-body file://spend-by-service.json

One template creates the dashboard, and also the read-only policy and IAM user from step 3. Here's the dashboard resource:

cloudpeek-setup.yaml (dashboard)Download
  Dashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardName: !Ref DashboardName
      DashboardBody: !Sub |
          {
            "start": "-P7D",
            "widgets": [
              {
                "type": "metric",
                "x": 0,
                "y": 0,
                "width": 12,
                "height": 8,
                "properties": {
                  "title": "Top ${TopServices} services by spend",
                  "view": "bar",
                  "region": "us-east-1",
                  "start": "-P1D",
                  "stat": "Maximum",
                  "period": 21600,
                  "metrics": [
                    [ "AWS/Billing", "EstimatedCharges", "Currency", "${Currency}", { "label": "Total" } ],
                    [ { "expression": "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"${Currency}\"', 'Maximum', 21600), MAX, DESC, ${TopServices})", "label": "${!PROP('Dim.ServiceName')}", "id": "top" } ]
                  ]
                }
              },
              {
                "type": "metric",
                "x": 12,
                "y": 0,
                "width": 6,
                "height": 8,
                "properties": {
                  "title": "Month-to-date spend",
                  "view": "singleValue",
                  "region": "us-east-1",
                  "stat": "Maximum",
                  "period": 21600,
                  "sparkline": true,
                  "metrics": [
                    [ "AWS/Billing", "EstimatedCharges", "Currency", "${Currency}", { "label": "Total" } ]
                  ]
                }
              }
            ]
          }

The body is the dashboard JSON below, with ${TopServices} and ${Currency} filled in from the stack's parameters. ${!PROP(…)} stops CloudFormation from treating the dynamic label as one of its own variables.

Shell
aws cloudformation deploy \
  --region us-east-1 \
  --stack-name CloudPeek \
  --template-file cloudpeek-setup.yaml \
  --capabilities CAPABILITY_NAMED_IAM

Or, in the CloudFormation console in us-east-1, choose Upload a template file, name the stack CloudPeek, check the parameters, and select I acknowledge that AWS CloudFormation might create IAM resources with custom names before you submit. When the stack reaches CREATE_COMPLETE, its Outputs tab links to the dashboard.

ParameterDefaultWhat it's for
DashboardNameCloudPeek-SpendThe dashboard's name.
TopServices5How many of your biggest services to chart, from 1 to 20.
CurrencyUSDThe currency your bill is in.
PolicyNameCloudPeekReadOnlyThe read-only policy's name.
CreateUsertrueSet to false if you'll sign in with IAM Identity Center.
UserNamecloudpeekThe IAM user's name.

To change them, add --parameter-overrides, such as --parameter-overrides TopServices=10 CreateUser=false.

One configuration creates the dashboard, and also the read-only policy and IAM user from step 3. Here's the dashboard resource:

cloudpeek-setup.tf (dashboard)Download
resource "aws_cloudwatch_dashboard" "spend" {
  dashboard_name = var.dashboard_name
  dashboard_body = jsonencode({
    start = "-P7D"
    widgets = [
      {
        type   = "metric"
        x      = 0
        y      = 0
        width  = 12
        height = 8
        properties = {
          title  = "Top ${var.top_services} services by spend"
          view   = "bar"
          region = "us-east-1"
          start  = "-P1D"
          stat   = "Maximum"
          period = 21600
          metrics = [
            ["AWS/Billing", "EstimatedCharges", "Currency", var.currency, { label = "Total" }],
            [{
              expression = "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"${var.currency}\"', 'Maximum', 21600), MAX, DESC, ${var.top_services})"
              label      = "$${PROP('Dim.ServiceName')}"
              id         = "top"
            }],
          ]
        }
      },
      {
        type   = "metric"
        x      = 12
        y      = 0
        width  = 6
        height = 8
        properties = {
          title     = "Month-to-date spend"
          view      = "singleValue"
          region    = "us-east-1"
          stat      = "Maximum"
          period    = 21600
          sparkline = true
          metrics = [
            ["AWS/Billing", "EstimatedCharges", "Currency", var.currency, { label = "Total" }],
          ]
        }
      },
    ]
  })
}

It builds exactly the dashboard JSON below with jsonencode. $${PROP(…)} stops Terraform from treating the dynamic label as an interpolation. Save the file in an empty directory and run:

Shell
terraform init
terraform apply
VariableDefaultWhat it's for
dashboard_nameCloudPeek-SpendThe dashboard's name.
top_services5How many of your biggest services to chart, from 1 to 20.
currencyUSDThe currency your bill is in.
policy_nameCloudPeekReadOnlyThe read-only policy's name.
create_usertrueSet to false if you'll sign in with IAM Identity Center.
user_namecloudpeekThe IAM user's name.

To change them, pass -var, such as terraform apply -var top_services=10 -var create_user=false. To add the dashboard to an existing configuration, copy the resources and leave out the terraform and provider blocks.

The dashboard, explained

Whichever tool you use, this is the dashboard you end up with:

spend-by-service.jsonDownload
{
  "start": "-P7D",
  "widgets": [
    {
      "type": "metric",
      "x": 0,
      "y": 0,
      "width": 12,
      "height": 8,
      "properties": {
        "title": "Top 5 services by spend",
        "view": "bar",
        "region": "us-east-1",
        "start": "-P1D",
        "stat": "Maximum",
        "period": 21600,
        "metrics": [
          [ "AWS/Billing", "EstimatedCharges", "Currency", "USD", { "label": "Total" } ],
          [ { "expression": "SORT(SEARCH('{AWS/Billing,Currency,ServiceName} MetricName=\"EstimatedCharges\" Currency=\"USD\"', 'Maximum', 21600), MAX, DESC, 5)", "label": "${PROP('Dim.ServiceName')}", "id": "top" } ]
        ]
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 0,
      "width": 6,
      "height": 8,
      "properties": {
        "title": "Month-to-date spend",
        "view": "singleValue",
        "region": "us-east-1",
        "stat": "Maximum",
        "period": 21600,
        "sparkline": true,
        "metrics": [
          [ "AWS/Billing", "EstimatedCharges", "Currency", "USD", { "label": "Total" } ]
        ]
      }
    }
  ]
}
  • The expression row finds your top services for you. SEARCH finds every service's estimated charges, and SORT(…, MAX, DESC, 5) keeps the five biggest. Services you start using appear on their own. To show more or fewer, change the 5 at the end of the expression (or the TopServices parameter, or the top_services variable).
  • The label ${PROP('Dim.ServiceName')} names each bar after its service, such as AmazonEC2 or AWSLambda. CloudPeek shows the short name (EC2, Lambda), lists them biggest first, and adds up any that don't fit into Other.
  • The row with only a Currency dimension is your whole account's charges so far this month. CloudPeek shows it as the widget's Total rather than as a bar, so services outside the top five still count. In the CloudWatch console it appears as one more bar.
  • Charges count from the start of the month, so the bar chart looks at just the last day ("start": "-P1D"). That way the ranking follows this month's charges rather than last month's. The month-to-date total uses the dashboard's week for its sparkline.
  • If your bill isn't in US dollars, change USD in both rows and in the expression to your currency code.
  • For a donut of each service's share, change "view": "bar" to "view": "pie". CloudPeek puts the Total in the middle and shows everything outside the top five as an Other slice.

To see every service's charges, open CloudWatch in us-east-1 and choose All metrics › Billing › By Service.

3. Give CloudPeek read-only access

Follow Give CloudPeek read-only access in the getting started guide. It creates the CloudPeekReadOnly policy and either an IAM user with an access key or an IAM Identity Center permission set.

Follow Give CloudPeek read-only access in the getting started guide, which has the AWS CLI commands for an IAM user or an IAM Identity Center permission set.

The stack from step 2 already created the CloudPeekReadOnly policy and, unless you set CreateUser to false, the cloudpeek user. Create the user's access key. The secret is shown only once:

Shell
aws iam create-access-key --user-name cloudpeek

For IAM Identity Center, attach CloudPeekReadOnly to a permission set as a customer managed policy (see Option B).

The configuration from step 2 already created the CloudPeekReadOnly policy and, unless you set create_user to false, the cloudpeek user. Create the user's access key outside Terraform, so the secret stays out of your state. It's shown only once:

Shell
aws iam create-access-key --user-name cloudpeek

For IAM Identity Center, attach CloudPeekReadOnly to a permission set as a customer managed policy (see Option B).

4. Sign in and pin your widgets

  1. Open CloudPeek and add your account on the Accounts tab with the access key or IAM Identity Center (see signing in). Choose us-east-1 as the default region.
  2. On Home, open the CloudPeek-Spend dashboard and tap Pin on Top 5 services by spend and Month-to-date spend.
  3. Touch and hold an empty area of your Home Screen until the apps jiggle. Tap Edit, then Add Widget. Find CloudPeek, pick a size and tap Add Widget. Medium suits the bar chart; small suits the total.
  4. New widgets show your first pinned widget. To choose another, touch and hold the widget and tap Edit Widget.

The total also works well on the Lock Screen, where widgets show the latest value.

Removing it

In the CloudWatch console in us-east-1, open Dashboards, select CloudPeek-Spend and choose Delete. To remove CloudPeek's access too, delete the cloudpeek user and the CloudPeekReadOnly policy in the IAM console.

Shell
aws cloudwatch delete-dashboards --region us-east-1 --dashboard-names CloudPeek-Spend

CloudFormation can't delete a user that still has an access key, or a policy that something outside the stack still uses. Delete the user's access key and detach the policy from any permission sets first, then delete the stack:

Shell
aws iam list-access-keys --user-name cloudpeek
aws iam delete-access-key --user-name cloudpeek --access-key-id <access-key-id>
aws cloudformation delete-stack --region us-east-1 --stack-name CloudPeek

IAM won't delete a user that still has an access key, or a policy that's still attached elsewhere. Delete the user's access key and detach the policy from any permission sets first, then destroy:

Shell
aws iam list-access-keys --user-name cloudpeek
aws iam delete-access-key --user-name cloudpeek --access-key-id <access-key-id>
terraform destroy

Troubleshooting

The widgets are empty, or the dashboard says “No data available”

Billing metrics aren't reaching CloudWatch yet. Check that they're turned on (step 1) and give them a few hours. Then look for the Billing namespace under All metrics in us-east-1 (Metrics Explorer doesn't list billing metrics). In AWS Organizations, set this up in the management account, which sees the whole organisation's charges.

A service is missing

It isn't one of your five biggest this month. To chart more, raise the number at the end of the SORT expression (see how it works). Its charges are already included in the Total.

The Total is bigger than the bars added up

That's expected. The Total is your whole bill so far this month, including services outside your top five.

Something else

See Troubleshooting, or email support@cloudpeek.app. Never send us your secret access key.