Digitalocean

DigitalOcean Cost Optimization Tips

A practical guide to reducing DigitalOcean costs covering right-sizing Droplets, reserved pricing, monitoring usage, cleaning unused resources, and optimizing bandwidth.

DigitalOcean Cost Optimization Tips

Cloud costs grow gradually. A Droplet here, a managed database there, a load balancer you forgot about — suddenly the monthly bill is twice what you expected. Unlike traditional hosting where you pay a fixed rate, cloud infrastructure charges for every resource you provision, whether you use it or not.

DigitalOcean's pricing is straightforward compared to AWS or GCP, but there are still opportunities to reduce costs without sacrificing performance. This guide covers practical techniques for right-sizing resources, eliminating waste, and making informed decisions about DigitalOcean spending for Node.js applications.

Understanding DigitalOcean Pricing

Core Services and Costs

Service Starting Price Billed
Droplets $4/month Hourly
Managed PostgreSQL $15/month Monthly
Managed MongoDB $15/month Monthly
Managed Redis $15/month Monthly
Managed Kafka $20/month Monthly
Load Balancer $12/month Monthly
Spaces (Object Storage) $5/month (250GB) Monthly
Block Storage $0.10/GB/month Monthly
Container Registry $5/month (5GB) Monthly
Kubernetes (DOKS) Free control plane Node costs only
App Platform $5/month Monthly
Bandwidth 1-6TB included Per GB overage

How Billing Works

Droplets are billed hourly up to the monthly cap. A $12/month Droplet costs $0.018/hour. If you run it for 10 hours and destroy it, you pay $0.18 — not $12. This matters for temporary workloads.

Managed databases, load balancers, and reserved instances are billed monthly regardless of usage. You pay the full monthly rate even if you create the resource on the last day of the month.

Right-Sizing Droplets

Audit Current Usage

The most common waste is oversized Droplets — paying for resources your application does not use.

# Check CPU and memory usage on your Droplet
ssh deploy@YOUR_IP

# CPU usage over the last few minutes
top -bn1 | head -5

# Memory usage
free -h

# Disk usage
df -h /

For Node.js applications:

// check-resources.js — run on the server
var os = require("os");

console.log("CPU cores:", os.cpus().length);
console.log("Total memory:", Math.round(os.totalmem() / 1024 / 1024) + " MB");
console.log("Free memory:", Math.round(os.freemem() / 1024 / 1024) + " MB");
console.log("Memory used:", Math.round((os.totalmem() - os.freemem()) / 1024 / 1024) + " MB");
console.log("Load average (1m, 5m, 15m):", os.loadavg().map(function(l) { return l.toFixed(2); }));

var usage = process.memoryUsage();
console.log("Node.js heap used:", Math.round(usage.heapUsed / 1024 / 1024) + " MB");
console.log("Node.js heap total:", Math.round(usage.heapTotal / 1024 / 1024) + " MB");
console.log("Node.js RSS:", Math.round(usage.rss / 1024 / 1024) + " MB");

Right-Sizing Guidelines

If You See Current Size Recommendation
CPU < 10%, RAM < 30% $24/month (2 vCPU, 4GB) Downsize to $12/month (1 vCPU, 2GB)
CPU < 5%, RAM < 40% $12/month (1 vCPU, 2GB) Downsize to $6/month (1 vCPU, 1GB)
CPU > 80% sustained Any Scale up or optimize code
RAM > 80% sustained Any Scale up or fix memory leaks

Most Node.js web applications serving moderate traffic (hundreds of requests per second) run comfortably on a $12/month Droplet (1 vCPU, 2GB RAM). The $24 and $48 tiers are for applications with heavy computation, large datasets, or high concurrency.

Resizing Droplets

# Power off the Droplet first
doctl compute droplet-action power-off YOUR_DROPLET_ID

# Resize (CPU and RAM only — no disk resize)
doctl compute droplet-action resize YOUR_DROPLET_ID --size s-1vcpu-1gb

# Power back on
doctl compute droplet-action power-on YOUR_DROPLET_ID

Resizing without disk change is reversible. You can scale down later. Resizing with disk expansion is permanent — you cannot shrink the disk.

Reserved Instance Pricing

For Droplets and managed databases that run 24/7, reserved pricing saves 20-30% compared to on-demand.

How It Works

Commit to 1-year or 3-year terms for specific resource sizes:

Droplet Size On-Demand 1-Year Reserved 3-Year Reserved
Basic 1 vCPU, 2GB $12/month ~$10/month ~$8/month
Basic 2 vCPU, 4GB $24/month ~$19/month ~$17/month
General Purpose 2 vCPU, 8GB $63/month ~$50/month ~$44/month

When to Use Reserved Pricing

Reserve resources when:

  • The Droplet or database runs continuously (production workloads)
  • You are confident in the resource size (already right-sized)
  • The commitment period is shorter than your project timeline

Do not reserve resources when:

  • Workloads are temporary or seasonal
  • You are still experimenting with sizes
  • The application might migrate to a different platform

Eliminating Unused Resources

Audit All Resources

# List all Droplets
doctl compute droplet list --format ID,Name,Region,Size,Status,Created

# List all volumes
doctl compute volume list --format ID,Name,Region,Size,DropletIDs

# List all load balancers
doctl compute load-balancer list --format ID,Name,Region,Size,Status

# List all databases
doctl databases list --format ID,Name,Engine,Size,Region,Status

# List all snapshots (billed per GB)
doctl compute snapshot list --format ID,Name,ResourceType,Size,Created

# List all domains
doctl compute domain list

# List all container registry repositories
doctl registry repository list-v2

Common Waste

Orphaned volumes — Block Storage volumes that are not attached to any Droplet. They still cost $0.10/GB/month.

# Find unattached volumes
doctl compute volume list --format ID,Name,Size,DropletIDs | grep -v "droplet"

Old snapshots — Snapshots accumulate over time at $0.06/GB/month.

# List snapshots older than 90 days
doctl compute snapshot list --format ID,Name,Size,Created

Unused load balancers — A load balancer with no healthy backends still costs $12/month.

Development databases — Managed databases for testing or staging that nobody uses. The minimum is $15/month.

Idle Droplets — Droplets created for one-time tasks that were never destroyed.

Cleanup Script

// scripts/audit-resources.js
var { execSync } = require("child_process");

function run(cmd) {
  return execSync(cmd, { encoding: "utf8" }).trim();
}

console.log("=== Resource Audit ===\n");

// Droplets
var droplets = run("doctl compute droplet list --format Name,Size,Status,Created --no-header");
console.log("DROPLETS:");
console.log(droplets || "  (none)");
console.log();

// Volumes
var volumes = run("doctl compute volume list --format Name,Size,DropletIDs --no-header");
console.log("VOLUMES:");
if (volumes) {
  volumes.split("\n").forEach(function(line) {
    var parts = line.trim().split(/\s+/);
    var attached = parts[2] && parts[2] !== "[]";
    console.log("  " + parts[0] + " (" + parts[1] + "GB)" + (attached ? "" : " [UNATTACHED - wasting money]"));
  });
} else {
  console.log("  (none)");
}
console.log();

// Snapshots
var snapshots = run("doctl compute snapshot list --format Name,Size,Created --no-header");
console.log("SNAPSHOTS:");
console.log(snapshots || "  (none)");
console.log();

// Load Balancers
var lbs = run("doctl compute load-balancer list --format Name,Size,Status --no-header");
console.log("LOAD BALANCERS:");
console.log(lbs || "  (none)");
console.log();

// Databases
var dbs = run("doctl databases list --format Name,Engine,Size,Status --no-header");
console.log("DATABASES:");
console.log(dbs || "  (none)");

Bandwidth Optimization

Understanding Bandwidth Allowances

Each Droplet includes bandwidth:

Droplet Included Bandwidth
$4/month 500 GB
$6/month 1 TB
$12/month 2 TB
$24/month 4 TB
$48/month 5 TB

Overage is billed at $0.01/GB. Bandwidth is pooled across all Droplets in your account. If one Droplet uses 3 TB and another uses 500 GB, and your total allowance is 3 TB, you pay for 500 GB overage.

Reducing Bandwidth Usage

Use a CDN for static assets:

// Serve static assets from DigitalOcean Spaces CDN
var SPACES_CDN = "https://my-bucket.nyc3.cdn.digitaloceanspaces.com";

app.locals.cdnUrl = SPACES_CDN;
//- In Pug templates
link(rel="stylesheet" href=cdnUrl + "/css/styles.css")
script(src=cdnUrl + "/js/app.js")
img(src=cdnUrl + "/images/hero.jpg")

CDN bandwidth from Spaces is separate from Droplet bandwidth and is included in the $5/month Spaces subscription (up to 1 TB).

Enable compression:

var compression = require("compression");

app.use(compression({
  level: 6,
  threshold: 1024,
  filter: function(req, res) {
    if (req.headers["x-no-compression"]) return false;
    return compression.filter(req, res);
  }
}));

Compression typically reduces response sizes by 60-80% for text content (HTML, CSS, JavaScript, JSON).

Cache responses:

// Cache static responses
app.get("/api/config", function(req, res) {
  res.set("Cache-Control", "public, max-age=3600"); // 1 hour
  res.json(config);
});

// Cache pages
app.get("/about", function(req, res) {
  res.set("Cache-Control", "public, max-age=86400"); // 24 hours
  res.render("about");
});

Cached responses served from the browser do not consume bandwidth.

Optimize images:

# Compress images before uploading
npm install sharp
var sharp = require("sharp");

function optimizeImage(inputPath, outputPath) {
  return sharp(inputPath)
    .resize(1200) // Max width
    .jpeg({ quality: 80, progressive: true })
    .toFile(outputPath);
}

Monitoring Bandwidth

# Check bandwidth usage via the API
doctl account get --format Email,DropletLimit,Status

Monitor in the DigitalOcean dashboard under Settings > Billing > Usage.

Database Cost Optimization

Right-Size Your Database

Managed databases start at $15/month. Check if you actually need managed:

Situation Recommendation Monthly Cost
Low-traffic side project SQLite on Droplet $0 (included)
Small app, single server PostgreSQL on Droplet $0 (included)
Production, need backups/failover Managed Basic $15/month
High traffic, read replicas needed Managed General Purpose $60/month

For applications with low database load, running PostgreSQL directly on your Droplet eliminates the $15/month managed database cost. The trade-off is managing backups, updates, and failover yourself.

Connection Pooling Saves Resources

Without connection pooling, each request opens a new database connection. This can force you to a larger database tier:

// Without pooling — wastes connections
app.get("/api/users", function(req, res) {
  var client = new Client({ connectionString: process.env.DATABASE_URL });
  client.connect()
    .then(function() { return client.query("SELECT * FROM users"); })
    .then(function(result) { res.json(result.rows); })
    .finally(function() { client.end(); });
});

// With pooling — reuses connections efficiently
var pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10
});

app.get("/api/users", function(req, res) {
  pool.query("SELECT * FROM users")
    .then(function(result) { res.json(result.rows); });
});

A basic managed database supports ~25 connections. With efficient pooling, this handles thousands of requests per second.

Use Read Replicas Strategically

Read replicas cost additional money. Only add them when:

  • The primary database CPU consistently exceeds 70%
  • Read queries are slowing down write operations
  • You have confirmed the bottleneck is database load, not application code

App Platform Cost Optimization

Choose the Right Plan

Plan Cost Features
Starter Free (3 static sites) Static sites only
Basic $5/month Single instance, no scaling
Professional $12/month Horizontal scaling, alerts

For many applications, the $5/month Basic plan is sufficient. Professional plans are needed only when you require multiple instances or auto-scaling.

Use Workers for Background Tasks

Instead of running a separate Droplet for background processing, use an App Platform worker component:

services:
  - name: web
    instance_size_slug: basic-xxs  # $5/month
    http_port: 8080

workers:
  - name: processor
    instance_size_slug: basic-xxs  # $5/month
    run_command: node worker.js

Two basic-xxs components ($10/month total) cost less than a dedicated Droplet ($12/month) and include zero-downtime deployments.

Kubernetes Cost Optimization

Control Plane is Free

The DOKS control plane is free. You only pay for worker nodes. This makes Kubernetes cost-competitive with running multiple Droplets manually.

Right-Size Node Pools

# Check actual resource usage
kubectl top nodes
kubectl top pods

# If nodes are underutilized, resize the pool
doctl kubernetes cluster node-pool update my-cluster default-pool \
  --size s-1vcpu-2gb \
  --count 2

Enable Auto-Scaling

doctl kubernetes cluster node-pool update my-cluster default-pool \
  --auto-scale \
  --min-nodes 2 \
  --max-nodes 5

Auto-scaling adds nodes during peak traffic and removes them during quiet periods. Set conservative maximums to prevent runaway costs.

Set Resource Requests Accurately

resources:
  requests:
    cpu: "100m"      # Actually measured, not guessed
    memory: "128Mi"  # Based on real usage
  limits:
    cpu: "500m"
    memory: "512Mi"

Over-requesting resources wastes node capacity. Under-requesting causes performance issues. Monitor actual usage with kubectl top pods and adjust.

Monthly Cost Review Checklist

Run through this checklist monthly:

  1. Review the billing dashboard. Identify the top 3 cost items. Are they expected?
  2. Audit resources. Run the audit script. Delete anything unused.
  3. Check Droplet utilization. Are any Droplets consistently under 20% CPU and 30% memory? Downsize them.
  4. Review snapshots. Delete snapshots older than 90 days unless needed for compliance.
  5. Check database sizing. Is the managed database consistently under 30% CPU? Consider downsizing.
  6. Review bandwidth. Are you approaching bandwidth limits? Consider CDN for static assets.
  7. Check for orphaned resources. Volumes, floating IPs, and load balancers without associated Droplets.

Common Issues and Troubleshooting

Bill is higher than expected

Resource left running after testing:

Fix: Run the resource audit script. Check for Droplets created for one-time tasks, test databases, or old snapshots. Set calendar reminders to review resources after project milestones.

Cannot downsize a Droplet

Disk resize prevents downsizing:

Fix: If you resized with disk expansion, you cannot shrink back. Create a new smaller Droplet, migrate the application, and destroy the old one. Use snapshots to simplify the migration.

Bandwidth overage charges

Static assets consuming too much bandwidth:

Fix: Move static files to Spaces with CDN enabled. Enable compression. Add cache headers. Review access logs for unusual traffic patterns that might indicate a bot or misconfigured client.

Best Practices

  • Right-size from the start. Begin with the smallest resource that works. Scale up when monitoring shows you need it, not before.
  • Destroy resources you are not using. Idle Droplets, unattached volumes, and old snapshots cost money every hour. Set a cleanup schedule.
  • Use reserved pricing for stable workloads. If a Droplet runs 24/7 for production, the 1-year reserved rate saves 20%.
  • Monitor before optimizing. Do not guess where money is going. Check the billing dashboard and resource utilization before making changes.
  • Use the right service for the job. App Platform at $5/month is cheaper than a $12 Droplet for simple applications. A Droplet with PostgreSQL is cheaper than a Droplet plus a managed database for low-traffic sites.
  • Pool bandwidth across Droplets. Bandwidth allowances are shared. A high-bandwidth Droplet can offset a low-bandwidth one.
  • Compress and cache everything. Gzip compression and cache headers reduce bandwidth costs and improve performance simultaneously.
  • Tag resources. Use tags like project:myapp and env:staging to track costs per project and identify resources to clean up when a project ends.

References

Powered by Contentful