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:
- Review the billing dashboard. Identify the top 3 cost items. Are they expected?
- Audit resources. Run the audit script. Delete anything unused.
- Check Droplet utilization. Are any Droplets consistently under 20% CPU and 30% memory? Downsize them.
- Review snapshots. Delete snapshots older than 90 days unless needed for compliance.
- Check database sizing. Is the managed database consistently under 30% CPU? Consider downsizing.
- Review bandwidth. Are you approaching bandwidth limits? Consider CDN for static assets.
- 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:myappandenv:stagingto track costs per project and identify resources to clean up when a project ends.