Dashboard

What goes in an Amazon S3 storage and cost dashboard in Metabase?

An Amazon S3 storage and cost dashboard tracks how much each bucket holds and in which storage class, how many requests it serves and how many of them fail, whether lifecycle rules are actually moving objects, and what all of that costs per bucket. It reads three sources — CloudWatch, S3 Storage Lens, and the Cost and Usage Report — and Metabase charts them side by side out of the warehouse you already land them in.

For: platform, data, and FinOps teams who own the buckets and the bill. Grain: one row per bucket per day, plus CUR line items. Source: CloudWatch BucketSizeBytes and request metrics, an S3 Storage Lens daily export, and a Cost and Usage Report with resource IDs enabled.

What does an S3 storage and cost dashboard look like?

Here’s the layout this guide builds. Totals and month-to-date spend sit at the top; the middle section is storage — how much, in which class, growing how fast, and in how many objects; the bottom section is activity and money, where requests, errors, lifecycle transitions, and cost per bucket explain the bill.

Amazon S3 storage and cost dashboard in Metabase showing bucket growth by storage class, requests, errors, lifecycle transitions, and cost per bucket.
An example Amazon S3 storage and cost dashboard in Metabase, built from CloudWatch, Storage Lens, and the Cost and Usage Report. Figures are illustrative.

Which cards belong on an S3 storage and cost dashboard?

Eight cards. Four describe the storage, four follow the money — and the pair that matters most is growth beside cost per bucket, because that is where a slow leak becomes a line item.

  • Stored bytes by storage class over time (stacked bar)
  • Week-over-week growth by bucket (row)
  • Objects and average object size per day (combo)
  • Requests by type — GET, PUT, LIST, DELETE (stacked bar)
  • 4xx and 5xx error rate per day (line)
  • Lifecycle transitions and expirations by target class (bar)
  • Cost by charge type — storage, requests, transfer, lifecycle (stacked bar)
  • Buckets by size, class mix, growth, and month-to-date cost (table)

What data does the dashboard need?

  • CloudWatch BucketSizeBytes and NumberOfObjects — daily, free, and dimensioned by StorageType (StandardStorage, StandardIAStorage, IntelligentTieringFAStorage, GlacierInstantRetrievalStorage, DeepArchiveStorage, and the rest).
  • CloudWatch request metrics per bucket — GetRequests, PutRequests, ListRequests, DeleteRequests, 4xxErrors, 5xxErrors, BytesDownloaded. Opt-in and billed, so enable per bucket.
  • An S3 Storage Lens daily metrics export for the org-wide class mix, and — on the advanced tier — prefix-level detail, activity metrics, and 15 months of history.
  • A Cost and Usage Report with resource IDs enabled, so line_item_resource_id carries the bucket ARN and line_item_usage_type separates TimedStorage, Requests-Tier1/Tier2, DataTransfer-Out, Transition, and EarlyDelete charges.
  • Optional: S3 Inventory for object-level age and class, which is how you size a lifecycle rule before writing it, and server access logs or CloudTrail data events if you need to know who is doing the requesting.

How do you build it?

  1. Land the three sources in one place: a scheduled CloudWatch GetMetricData pull into a table, the Storage Lens export into a bucket, and the CUR into Athena or your warehouse — then connect that warehouse to Metabase. The AWS billing guide covers the CUR half.
  2. Model bucket identity once. CloudWatch names buckets, the CUR gives ARNs, Storage Lens reports account and region — normalize them to one bucket dimension or no two cards will agree.
  3. Build the storage-class and cost-by-charge-type cards first. Together they explain most bills, and they are the pair that tells you whether the problem is bytes, requests, or a lifecycle rule.
  4. Add the growth card as a week-over-week delta rather than a total, and give it a threshold — this is the card that catches versioning with no expiry rule, months before it shows up as a number anyone notices.
  5. Add filters for account, bucket, and date range, and alert FinOps when a bucket’s weekly growth or month-to-date cost crosses its threshold.

Example card SQL

Month-to-date S3 cost per bucket, split by charge type (CUR) PostgreSQL
SELECT
DATE_TRUNC('day', line_item_usage_start_date)          AS day,
SPLIT_PART(line_item_resource_id, ':::', 2)            AS bucket,
SUM(CASE WHEN line_item_usage_type LIKE '%TimedStorage%'
         THEN line_item_unblended_cost END)            AS storage_usd,
SUM(CASE WHEN line_item_usage_type LIKE '%Requests-%'
         THEN line_item_unblended_cost END)            AS requests_usd,
SUM(CASE WHEN line_item_usage_type LIKE '%DataTransfer%'
         THEN line_item_unblended_cost END)            AS transfer_usd,
SUM(CASE WHEN line_item_usage_type LIKE '%Transition%'
          OR line_item_usage_type LIKE '%EarlyDelete%'
         THEN line_item_unblended_cost END)            AS lifecycle_usd,
ROUND(SUM(line_item_unblended_cost), 2)                AS total_usd
FROM cur.aws_billing
WHERE line_item_product_code = 'AmazonS3'
AND line_item_usage_start_date >= DATE_TRUNC('month', CURRENT_DATE)
GROUP BY 1, 2
ORDER BY total_usd DESC;

Metrics

Integrations

Dashboards

FAQ

What is an S3 storage and cost dashboard?
It is the answer to "what is in our buckets, how fast is it growing, and what is it costing us" on one page. Storage on its own is not interesting — S3 is cheap per gigabyte and the bill still surprises people, because the cost is spread across storage class, request tiers, data transfer, and lifecycle transitions. Putting bucket growth beside request volume beside cost per bucket is what makes the surprise legible. Three sources cover it: CloudWatch for daily size and request metrics, S3 Storage Lens for the class and prefix breakdown, and the Cost and Usage Report for the money.
Which source should each card read?
Use the cheapest source that answers the question. CloudWatch storage metrics (BucketSizeBytes, NumberOfObjects) are free, daily, and broken out by storage type — perfect for the growth cards. CloudWatch request metrics (GetRequests, PutRequests, 4xxErrors, 5xxErrors, FirstByteLatency) are per-minute and accurate but are opt-in and billed per bucket, so enable them where they earn their keep. Storage Lens gives the org-wide class mix, and its advanced tier adds prefix-level detail and 15 months of history through a daily metrics export. The CUR is the only place with actual dollars. Everything else is an estimate.
Why is my storage cost higher than size × price?
Because storage is only one line item. A month's S3 bill also contains request charges (PUT, COPY, POST, and LIST at one rate; GET and SELECT at a much cheaper one), data transfer out, per-object monitoring fees on Intelligent-Tiering, and the lifecycle charges people forget: every transition to another class is billed per object, and objects deleted before the class minimum — 30 days for Standard-IA, 90 for Glacier Flexible Retrieval, 180 for Deep Archive — are billed for the remainder anyway as an early-delete charge. Split the cost card by line_item_usage_type and this is visible immediately: a bucket of small, short-lived objects can spend more on requests and early deletes than on bytes.
How do I catch a bucket growing out of control?
Chart size per bucket per day and rank buckets by week-over-week delta rather than by absolute size — the biggest bucket is usually the intended one, and the interesting bucket is the one that doubled last Tuesday. Pair that with object count: if bytes are flat while object count climbs, someone is writing many tiny files, which costs more in PUT requests than in storage and will slow down every downstream scan. The usual culprits are versioning without a lifecycle rule to expire noncurrent versions, incomplete multipart uploads that were never aborted, and log delivery with no expiry — none of them show up as an obvious spike, which is exactly why the trend card is the one worth alerting on.
What should the lifecycle card tell me?
Whether your rules are actually doing anything. A lifecycle policy is written once and then silently either works or does not, and the failure is invisible: objects that never transitioned keep paying the Standard rate forever. Chart transitions and expirations per day by target class alongside the class mix from Storage Lens, and the mismatch stands out — a rule that should be moving 40 TB a month to Glacier while the class mix barely moves is a rule with a prefix filter that no longer matches reality. Intelligent-Tiering is worth considering as the alternative for unpredictable access patterns, since it moves objects for you and charges a small per-object monitoring fee instead of a per-transition one.
How do I get cost per bucket at all?
From the Cost and Usage Report, and only from the CUR — the Cost Explorer console does not break down below the service level for S3 unless you tag. Enable a CUR with resource IDs included, deliver it to S3 in Parquet, and query it with Athena or load it into your warehouse; line_item_resource_id then holds the bucket ARN, so grouping by it gives real dollars per bucket. Two caveats worth putting in the card's description: the CUR lands roughly a day behind, so today's number is always incomplete, and cross-account buckets need either a management-account CUR or one report per account unioned together.
How does this fit with the other AWS dashboards?
It sits between them. A cloud spend overview is the whole bill across services and answers "are we on budget"; an AWS monitoring dashboard is the operational view across EC2, RDS, Lambda and the rest. This one goes one service deep, because S3 is usually both the largest object store and one of the least understood line items — and because the same buckets are the data lake, the answers here feed straight into query cost upstream. Pair it with cost anomalies for the alerting side.