Docs navigation

Quick Start

Prerequisites

  • Node.js 20+ — check with node --version
  • AWS credentials with read-only permissions (see Required IAM permissions below)

Installation

npm install -g @cloudrift/cli
# or run it once-off, without installing:
npx @cloudrift/cli analyze

macOS/Linux via Homebrew:

brew install elleVas/cloudrift/cloudrift

From source (for contributing, or to run unreleased changes):

git clone https://github.com/elleVas/cloudrift.git
cd cloudrift
pnpm install
pnpm nx build cli   # output compiled to apps/cli/dist/

The examples below use the cloudrift command (npm install). Running from source instead? Replace cloudrift with node apps/cli/dist/main.js.

AWS credentials setup

cloudrift uses the standard AWS SDK v3 credential chain. Three options, in order of preference:

Option A — AWS CLI (recommended)

aws configure
# enter: Access Key ID, Secret Access Key, default region (e.g. us-east-1), output format (json)

This creates ~/.aws/credentials with the default profile.

Option B — Edit ~/.aws/credentials manually

[default]
aws_access_key_id     = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Option C — Environment variables

export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-east-1

Verify: aws sts get-caller-identity should return your account ID without errors.

Required IAM permissions

The AWS user/role must have this read-only policy:

{
  "Effect": "Allow",
  "Action": [
    "ec2:DescribeVolumes",
    "ec2:DescribeAddresses",
    "ec2:DescribeInstances",
    "ec2:DescribeSnapshots",
    "ec2:DescribeImages",
    "ec2:DescribeNatGateways",
    "ec2:DescribeNetworkInterfaces",
    "ec2:DescribeLaunchTemplates",
    "ec2:DescribeLaunchTemplateVersions",
    "cloudwatch:GetMetricStatistics",
    "rds:DescribeDBInstances",
    "rds:DescribeDBClusters",
    "rds:DescribeDBSnapshots",
    "elasticloadbalancing:DescribeLoadBalancers",
    "elasticloadbalancing:DescribeTargetGroups",
    "elasticloadbalancing:DescribeTargetHealth",
    "logs:DescribeLogGroups",
    "s3:ListAllMyBuckets",
    "s3:GetBucketLifecycleConfiguration",
    "s3:ListMultipartUploadParts",
    "s3:ListBucketMultipartUploads",
    "ecr:DescribeRepositories",
    "ecr:DescribeImages",
    "codepipeline:ListPipelines",
    "codepipeline:ListPipelineExecutions",
    "secretsmanager:ListSecrets",
    "lambda:ListFunctions",
    "elasticfilesystem:DescribeFileSystems",
    "dynamodb:ListTables",
    "dynamodb:DescribeTable",
    "elasticache:DescribeCacheClusters",
    "sagemaker:ListNotebookInstances",
    "sagemaker:ListEndpoints",
    "sagemaker:DescribeEndpoint",
    "sagemaker:DescribeEndpointConfig",
    "sagemaker:ListEndpointConfigs",
    "sagemaker:ListModels",
    "sagemaker:DescribeModel",
    "sagemaker:ListTags",
    "sqs:ListQueues",
    "sqs:GetQueueAttributes",
    "sqs:ListDeadLetterSourceQueues",
    "sqs:ListQueueTags",
    "tag:GetResources",
    "eks:ListClusters",
    "eks:ListNodegroups",
    "eks:DescribeNodegroup",
    "sts:GetCallerIdentity"
  ],
  "Resource": "*"
}

--live-pricing additionally requires pricing:GetProducts (AWS Pricing API). Not needed for the default static pricing.

First usage

# Scan the default region (us-east-1)
# Account ID is auto-detected via STS
cloudrift analyze

# Scan multiple regions
cloudrift analyze -r us-east-1 eu-west-1 ap-southeast-1

# Specific services only (skips the interactive picker)
cloudrift analyze --scanners ebs-volume elastic-ip

# All scanners without interactive picker
cloudrift analyze --all-services

# Disable the grace period (report resources of any age)
cloudrift analyze --min-age-days 0

The interactive picker

Running analyze in a real terminal (outside CI) shows an interactive picker — a checkbox list of every scanner, all pre-selected. Press Enter to scan everything, or deselect what you don’t need.

The picker never appears when:

  • stdout is not a TTY (piped output)
  • The CI=true environment variable is set
  • You use --silent, --scanners <kinds...> or --all-services

In those cases all scanners run automatically.

Output formats

# Console table (default)
cloudrift analyze

# JSON output (machine-readable, ideal for piping)
cloudrift analyze --format json | jq '.totalWasteMonthlyUsd'

# Filter findings with jq
cloudrift analyze --format json | jq '.findings[] | select(.category=="waste")'

# Markdown for GitHub Actions step summary
cloudrift analyze --format markdown >> "$GITHUB_STEP_SUMMARY"

In machine-readable formats (json, markdown) all human messages are routed to stderr, so stdout carries only the report — ideal for piping.

PDF report

# PDF with auto-generated filename (cloudrift-reports/AWS_report_YYYY_MM_DD.pdf)
cloudrift analyze --pdf

# PDF with custom filename, no terminal output
cloudrift analyze --pdf ./report.pdf --silent

The PDF report contains:

  • Executive summary — monthly and annual waste totals, resource count, per-type breakdown
  • Top recommendations — up to 8 items sorted by impact, with estimated annual saving
  • Detail pages — one table per resource type found
  • Scan warnings — listed if any resource type could not be scanned

Flag order: the --pdf filename is optional, so it’s only picked up if it immediately follows the flag. Use --pdf=./report.pdf --silent to avoid ambiguity.

JSON file report

# Also writes a JSON file to disk (independent of --format)
cloudrift analyze --json

# With custom filename
cloudrift analyze --json ./report.json --silent

Partial failure handling

If scanning a resource type fails (e.g. missing CloudWatch permissions for NAT Gateways), the tool:

  • Still returns all other results
  • Shows a “Scan Warnings” section with the error details
  • Marks the total as (incomplete — see warnings above)
  ⚠ Scan Warnings
  • NAT Gateways: Access denied to CloudWatch metrics

  Total estimated waste: $56.20/month (incomplete — see warnings above)

The exit code is driven only by the cost threshold, never by scan errors.

All options

Flag Description Default
-r, --regions <regions...> AWS regions to scan us-east-1
--format <format> stdout format: table, json, markdown table
--config <path> Path to config file auto-discovered
--live-pricing Current prices from AWS Pricing API off
--scanners <kinds...> Only these services (skips picker)
--all-services All scanners without picker on in CI/non-TTY
--account-id <id> Account ID override (auto-detected via STS) auto
--assume-role-arn <arn> Assume this IAM role for cross-account access
--external-id <id> External ID for --assume-role-arn
--min-age-days <days> Grace period in days 7
--ignore-tag <tag> Exclusion tag cloudrift:ignore
--pdf [filename] Generate PDF report
--json [filename] Generate JSON report to disk
--silent No stdout output off
--notify-slack Slack notification when waste exceeds costAlertThresholdUsd (reads SLACK_WEBHOOK_URL from env) off
--notify-webhook POST a JSON summary to a webhook (reads CLOUDRIFT_WEBHOOK_URL from env) off
--notify-email <address> Email a summary (reads CLOUDRIFT_SMTP_* from env) off
-h, --help Show help

Notifications: also available on dead-resources, resource-security, and history --compare (different trigger conditions — see each command below). Best-effort: a broken webhook or misconfigured SMTP logs a warning and never fails the scan. Every credential is read from the environment, never a flag — set them in your shell profile or as CI secrets, never in a committed file.

Cross-account scanning

cloudrift can scan a different account by assuming an IAM role via STS:

# Scan a different account by assuming a role
cloudrift analyze --assume-role-arn arn:aws:iam::222222222222:role/cloudrift-scanner

# With external ID (if the trust policy requires it)
cloudrift analyze --assume-role-arn arn:aws:iam::222222222222:role/cloudrift-scanner --external-id my-secret

# Scan multiple accounts from a shell loop
for account in 111111111111 222222222222; do
  cloudrift analyze \
    --assume-role-arn "arn:aws:iam::${account}:role/cloudrift-scanner" \
    --format json > "report-${account}.json"
done

All commands (analyze, cost, trend, dead-resources, resource-security) accept --assume-role-arn and --external-id.

Cost and trend commands

Beyond waste detection, cloudrift offers two commands to compare and track AWS spend via Cost Explorer:

# Compare this month's spend with the same days last month
cloudrift cost

# Monthly trend over the last 12 months
cloudrift trend --months 12

# Only EC2 and S3, skip billing confirmation
cloudrift trend --months 12 --services ec2 s3 --yes

# Fail in CI if spend increased by more than 20%
cloudrift cost --fail-on-increase 20 --format json

⚠️ cost and trend call AWS Cost Explorer, which bills $0.01 per request — the only cloudrift commands that can generate an AWS cost. Both prompt for interactive confirmation before the first call (skippable with -y/--yes, --silent, or in CI). Closed billing periods are cached to disk.

Dead-resources command

Finds resources left dead or unused in the account at $0 direct AWS cost (invisible to waste detection): unused EC2 key pairs, orphaned security groups, inactive IAM users/roles, unrotated access keys, and more — 18 checks total.

# All checks, default region
cloudrift dead-resources

# Multiple regions
cloudrift dead-resources -r us-east-1 eu-west-1

# Only IAM checks
cloudrift dead-resources --scanners iam-user-inactive iam-policy-unattached

# Machine-readable output
cloudrift dead-resources --format json | jq '.findings[] | select(.severity=="warning")'

# PDF report
cloudrift dead-resources --pdf ./hygiene.pdf --silent

Findings carry a severity (info / warning / critical) instead of a $/month estimate. Requires additional IAM permissions — see the full policy with cloudrift iam-policy.

Resource-security command

Scans the account’s security posture: disabled MFA, unrotated access keys, internet-open security groups, public S3 buckets, unencrypted EBS/RDS, disabled GuardDuty/Config/Security Hub, disabled VPC Flow Logs, KMS key rotation, wildcard IAM admin policies, expiring ACM certificates, public resource policies on Lambda/SNS/SQS/ECR/Secrets Manager, and more — 29 checks total.

# Every check, default region
cloudrift resource-security

# Only IAM checks
cloudrift resource-security --scanners iam-root-mfa-disabled iam-user-mfa-disabled

# Machine-readable output
cloudrift resource-security --format json | jq '.findings[] | select(.severity=="critical")'

# PDF report
cloudrift resource-security --pdf ./security.pdf --silent

Findings also carry a severity. Requires additional IAM permissions — see the full policy with cloudrift iam-policy.

History command

View local scan history. Every run of analyze, dead-resources, and resource-security saves a snapshot to a local SQLite file (~/.cloudrift/trends/<account-id>.db) — best-effort, never blocking the scan.

# Every recorded snapshot, most recent first
cloudrift history

# Only cloud-cost history, last 10 runs
cloudrift history --domain cloud-cost --limit 10

# Machine-readable output
cloudrift history --format json | jq '.[0].payload'

# Self-contained HTML report (per-domain charts, stat tiles, linear forecast)
cloudrift history --html

# HTML report for a single domain
cloudrift history --domain cloud-cost --html

# Compare two specific snapshots
cloudrift history --compare

The --html report varies by domain: cloud-cost shows a single waste-dollar line with a linear forecast plus a “top resource type by waste” list; dead-resources/resource-security show critical/warning/info as three separate lines. The combined report opens with 3 executive stat tiles aimed at a CTO/CEO audience.

No data is ever uploaded anywhere: the file never leaves your machine.

IAM policy command

Prints the full read-only IAM policy required by cloudrift as ready-to-paste JSON:

cloudrift iam-policy

No AWS calls — useful for pasting directly into the IAM console, Terraform, or CDK.

Example output

  Scanning us-east-1 (account 123456789012) for wasted cloud resources...

  EBS Volumes — Unattached
  ┌────────────────────┬───────────┬────────┬──────┬────────────┬────────────┐
  │ Volume ID          │ Region    │ Size   │ Type │ Created    │ Est. Cost  │
  ├────────────────────┼───────────┼────────┼──────┼────────────┼────────────┤
  │ vol-0abc123def456  │ us-east-1 │ 500 GB │ gp3  │ 2025-01-15 │ $40.00/mo  │
  └────────────────────┴───────────┴────────┴──────┴────────────┴────────────┘

  Total estimated waste: $40.00/month

Per-region pricing

Prices are region-aware. Regions with specific pricing: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-1, ap-northeast-1. All others fall back to us-east-1 defaults.

Every report shows prices as of with the date the price table was last verified.