How to Migrate from Heroku to DigitalOcean in 2025
Published: November 2025 • 25 min read
Complete guide to migrating from Heroku to DigitalOcean, including cost comparisons, step-by-step migration, database transfer, and automation.
TL;DR
- Cost Savings: 70-90% cheaper than Heroku ($7-25/mo vs $25-250/mo)
- Migration Time: 2-6 hours for typical apps
- Complexity: Medium (requires some DevOps knowledge)
- Best Alternative: DigitalOcean App Platform (similar to Heroku) or Droplets (more control)
- Free Credits: Get $200 free credits with this DigitalOcean link
Table of Contents
- Why Migrate from Heroku?
- Cost Comparison: Heroku vs DigitalOcean
- Two Migration Paths
- Path 1: DigitalOcean App Platform (Easiest)
- Path 2: DigitalOcean Droplets (More Control)
- Database Migration Guide
- SSL/TLS Configuration
- CI/CD and Deployment Automation
- Monitoring and Logging
- Common Issues and Troubleshooting
- Migration Checklist
1. Why Migrate from Heroku?
Heroku has been a popular Platform-as-a-Service (PaaS) for over a decade, but several factors are driving developers to look for alternatives in 2025:
Pricing Changes
In November 2022, Heroku eliminated their free tier and significantly increased pricing across all plans. What used to cost $0-25/month now starts at $25/month for basic dynos, with typical production apps costing $100-500/month.
Cost at Scale
As your application grows, Heroku's costs scale dramatically:
- Standard Dynos: $25-50/month each
- Performance Dynos: $250-500/month each
- Database: $50-500/month for production-grade Postgres
- Redis: $15-500/month
- Add-ons: Additional costs for monitoring, logging, etc.
A typical production app with 2 web dynos, 1 worker dyno, Postgres, and Redis can easily cost $200-400/month on Heroku.
DigitalOcean Advantages
DigitalOcean offers several compelling advantages:
- 70-90% Cost Savings: Similar workloads cost $15-50/month instead of $200-400/month
- More Control: Full root access, custom configurations, better performance tuning
- Transparent Pricing: No surprise bills, predictable costs
- Better Performance: Dedicated resources instead of shared dynos
- Two Options: App Platform (easy, Heroku-like) or Droplets (flexible, powerful)
- $200 Free Credits: Try before committing with this signup link
2. Cost Comparison: Heroku vs DigitalOcean
Let's compare real-world costs for a typical production app:
| Component | Heroku | DO App Platform | DO Droplets |
|---|---|---|---|
| Web Server (2GB RAM) | $50/mo (2x Standard) | $12/mo | $12/mo |
| Worker (1GB RAM) | $25/mo | $6/mo | Included |
| PostgreSQL (10GB) | $50/mo | $15/mo | Included |
| Redis (100MB) | $15/mo | $7/mo | Included |
| SSL Certificate | Free | Free | Free (Let's Encrypt) |
| Total Monthly Cost | $140/mo | $40/mo | $12/mo |
| Annual Savings | — | $1,200/year saved | $1,536/year saved |
Key Takeaway: You can save $1,200-1,500+ per year by switching from Heroku to DigitalOcean, while maintaining similar or better performance.
3. Two Migration Paths
DigitalOcean offers two main hosting options, each suited for different needs:
Path 1: DigitalOcean App Platform (Recommended for Most)
Best for: Teams wanting minimal DevOps work, similar experience to Heroku
- ✅ Similar to Heroku (git push deploys, automatic scaling, managed databases)
- ✅ Faster migration (2-4 hours typical)
- ✅ Automatic SSL, health checks, zero-downtime deploys
- ✅ Built-in CI/CD from GitHub/GitLab
- ❌ Less control over infrastructure
- ❌ Slightly more expensive than Droplets ($12-40/mo vs $6-12/mo)
Path 2: DigitalOcean Droplets (Maximum Control)
Best for: Teams with DevOps experience, custom infrastructure needs, maximum cost savings
- ✅ Full control (root access, custom configs, any software stack)
- ✅ Cheapest option ($6-12/mo for most apps)
- ✅ Better performance (dedicated resources, no noisy neighbors)
- ✅ Run multiple apps on one server
- ❌ More setup work (4-6 hours typical)
- ❌ Requires DevOps knowledge (Nginx, systemd, etc.)
Our Recommendation: Start with App Platform if you're new to self-hosting. It's 70% cheaper than Heroku and requires minimal changes. Migrate to Droplets later if you need more control or want to save even more.
4. Path 1: DigitalOcean App Platform (Easiest)
This path is the closest to Heroku's experience. You connect your GitHub/GitLab repo, and DigitalOcean handles deployments automatically.
Step 1: Sign Up for DigitalOcean
- Sign up at DigitalOcean with $200 free credits
- Verify your email and add payment method (required even with free credits)
- Navigate to "App Platform" in the main menu
Step 2: Create New App from GitHub
- Click "Create App"
- Connect your GitHub account (same process as Heroku)
- Select the repository containing your app
- Choose the branch to deploy (usually
mainorproduction) - DigitalOcean will auto-detect your app type (Node.js, Python, Ruby, etc.)
Step 3: Configure Build Settings
DigitalOcean uses similar concepts to Heroku:
# If your app uses a Procfile (like Heroku), it works as-is
web: gunicorn myapp.wsgi:application
worker: celery -A myapp worker -l info
# Or configure build commands in App Platform UI:
Build Command: npm install && npm run build
Run Command: npm startStep 4: Set Environment Variables
Copy environment variables from Heroku:
- Get Heroku env vars:
heroku config -a your-app-name - In DigitalOcean App Platform, go to "Environment Variables"
- Add each variable (same format as Heroku)
⚠️ Important: Update database URLs if you're migrating databases (covered in next section).
Step 5: Choose Resource Size
Select the right plan for your needs:
| App Platform Plan | RAM/CPU | Price | Heroku Equivalent |
|---|---|---|---|
| Basic | 512MB / 1 vCPU | $5/mo | Eco Dyno ($5-7/mo) |
| Professional | 1GB / 1 vCPU | $12/mo | Standard 1X ($25/mo) |
| Professional+ | 2GB / 2 vCPU | $24/mo | Standard 2X ($50/mo) |
Recommendation: Start with Professional ($12/mo) for most production apps. You can scale up/down anytime.
Step 6: Add Database
DigitalOcean offers managed databases similar to Heroku Postgres:
- Click "Add Resource" → "Database"
- Choose PostgreSQL, MySQL, MongoDB, or Redis
- Select size (Basic $15/mo is sufficient for most apps)
- Database credentials are auto-added to environment variables
Step 7: Deploy and Test
- Click "Create Resources" to deploy your app
- DigitalOcean will build and deploy (usually takes 3-10 minutes)
- You'll get a temporary URL:
your-app-xxxxx.ondigitalocean.app - Test thoroughly before switching DNS
Step 8: Custom Domain Setup
- In App Platform, go to "Settings" → "Domains"
- Click "Add Domain"
- Enter your domain (e.g.,
yourdomain.com) - Add CNAME record to your DNS provider:
- Type: CNAME
- Name: @ (or www)
- Value:
your-app-xxxxx.ondigitalocean.app
- DigitalOcean auto-provisions SSL certificate (takes 5-15 minutes)
Step 9: Switch Traffic from Heroku
Once you've tested your DigitalOcean app:
- Update DNS to point to DigitalOcean (see step above)
- Wait for DNS propagation (usually 5-60 minutes)
- Monitor your app for errors
- Once stable (24-48 hours), you can scale down or delete Heroku app
✅ Success! Your app is now running on DigitalOcean App Platform. You should see significantly lower monthly costs while maintaining similar performance.
5. Path 2: DigitalOcean Droplets (More Control)
This path gives you full control over your infrastructure and is the cheapest option, but requires more DevOps knowledge.
Step 1: Create a Droplet
- Sign up at DigitalOcean with $200 free credits
- Click "Create" → "Droplets"
- Choose Ubuntu 22.04 LTS (recommended for most apps)
- Select plan:
- Basic: $6/mo (1GB RAM, 1 vCPU) - good for small apps
- Basic: $12/mo (2GB RAM, 2 vCPU) - recommended for production
- Basic: $24/mo (4GB RAM, 2 vCPU) - for larger apps
- Choose datacenter region (closest to your users)
- Add SSH key for secure access (or use password)
- Click "Create Droplet"
Step 2: Initial Server Setup
SSH into your new server and set up basics:
# SSH into server (use IP from DigitalOcean dashboard)
ssh root@your_server_ip
# Update system packages
apt update && apt upgrade -y
# Create non-root user (security best practice)
adduser deploy
usermod -aG sudo deploy
# Set up firewall
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
# Install essential packages
apt install -y nginx postgresql postgresql-contrib redis-server git curl
# Install Node.js (if using Node.js app)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# OR install Python (if using Django/Flask)
apt install -y python3-pip python3-venv
pip3 install pipenv gunicornStep 3: Deploy Your Application
For Node.js Apps:
# Switch to deploy user
su - deploy
# Clone your repository
git clone https://github.com/yourusername/your-app.git
cd your-app
# Install dependencies
npm install
# Build if needed (for Next.js, React, etc.)
npm run build
# Create .env file with environment variables
nano .env
# Add: DATABASE_URL, SECRET_KEY, etc.
# Test run
npm start # Should work on port 3000 or your configured portFor Python/Django Apps:
# Switch to deploy user
su - deploy
# Clone repository
git clone https://github.com/yourusername/your-app.git
cd your-app
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Create .env file
nano .env
# Add: DATABASE_URL, SECRET_KEY, DEBUG=False, ALLOWED_HOSTS=yourdomain.com
# Collect static files
python manage.py collectstatic --noinput
# Run migrations
python manage.py migrate
# Test with Gunicorn
gunicorn myapp.wsgi:application --bind 0.0.0.0:8000Step 4: Configure Systemd Service
Set up your app to run automatically and restart on crashes:
# Create systemd service file
sudo nano /etc/systemd/system/myapp.service
# For Node.js:
[Unit]
Description=My Node.js App
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/your-app
Environment="NODE_ENV=production"
EnvironmentFile=/home/deploy/your-app/.env
ExecStart=/usr/bin/npm start
Restart=always
[Install]
WantedBy=multi-user.target
# For Python/Django:
[Unit]
Description=My Django App
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/your-app
Environment="DJANGO_SETTINGS_MODULE=myapp.settings"
EnvironmentFile=/home/deploy/your-app/.env
ExecStart=/home/deploy/your-app/venv/bin/gunicorn myapp.wsgi:application --bind unix:/tmp/myapp.sock
Restart=always
[Install]
WantedBy=multi-user.target
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp # Check it's runningStep 5: Configure Nginx as Reverse Proxy
Nginx will handle HTTPS, static files, and proxy requests to your app:
# Create Nginx config
sudo nano /etc/nginx/sites-available/myapp
# Add configuration:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# For Node.js apps (HTTP proxy)
location / {
proxy_pass http://localhost:3000; # Change port if needed
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# For Python/Django apps (Unix socket)
location / {
proxy_pass http://unix:/tmp/myapp.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Serve static files directly (Django)
location /static/ {
alias /home/deploy/your-app/staticfiles/;
}
# Serve media files (Django)
location /media/ {
alias /home/deploy/your-app/media/;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t # Test configuration
sudo systemctl restart nginx6. Database Migration Guide
This is the most critical part of migration. Follow these steps carefully to avoid data loss.
Option 1: Heroku Postgres → DigitalOcean Managed Database
Step 1: Create DigitalOcean Database
- In DigitalOcean, go to "Databases" → "Create Database Cluster"
- Choose PostgreSQL and version matching Heroku (check with
heroku pg:info) - Select size (Basic $15/mo is good for most apps)
- Choose same datacenter as your app
- Click "Create Database Cluster"
- Wait 5-10 minutes for provisioning
Step 2: Backup Heroku Database
# Create backup on Heroku
heroku pg:backups:capture -a your-app-name
# Download backup
heroku pg:backups:download -a your-app-name
# This creates latest.dump file in current directoryStep 3: Restore to DigitalOcean
# Get connection details from DigitalOcean dashboard
# Format: postgresql://username:password@host:port/database
# Restore backup to DigitalOcean
pg_restore --verbose --clean --no-acl --no-owner \
-h your-do-db-host \
-U doadmin \
-d defaultdb \
latest.dump
# Verify data
psql -h your-do-db-host -U doadmin -d defaultdb -c "SELECT COUNT(*) FROM your_main_table;"Option 2: Direct Database Copy (Faster, Zero Downtime)
Use pg_dump with streaming to minimize downtime:
# Get Heroku database URL
heroku config:get DATABASE_URL -a your-app-name
# Get DigitalOcean database URL from dashboard
# Stream copy (one command, minimal downtime)
pg_dump <heroku_database_url> | psql <digitalocean_database_url>
# This works for databases up to ~10GB
# For larger databases, use the backup method aboveOption 3: PostgreSQL Running on Droplet
If using Droplets instead of managed database:
# On your Droplet, set up PostgreSQL
sudo -u postgres createdb myapp_production
sudo -u postgres createuser myapp_user -P # Enter password when prompted
# Grant permissions
sudo -u postgres psql
GRANT ALL PRIVILEGES ON DATABASE myapp_production TO myapp_user;
\q
# Download Heroku backup and restore
heroku pg:backups:download -a your-app-name
pg_restore --verbose --clean --no-acl --no-owner \
-h localhost \
-U myapp_user \
-d myapp_production \
latest.dump
# Update your app's DATABASE_URL
DATABASE_URL=postgresql://myapp_user:password@localhost:5432/myapp_productionDatabase Migration Checklist
- ✅ Backup created and downloaded from Heroku
- ✅ DigitalOcean database created with correct version
- ✅ Data restored to DigitalOcean database
- ✅ Row counts match between Heroku and DigitalOcean
- ✅ App tested with new database connection
- ✅ DATABASE_URL environment variable updated
7. SSL/TLS Configuration
For App Platform:
SSL is automatic! DigitalOcean provisions Let's Encrypt certificates automatically when you add a custom domain. No configuration needed.
For Droplets (Using Certbot):
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
# Get SSL certificate (Certbot auto-configures Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Follow prompts:
# - Enter email for renewal notifications
# - Agree to Terms of Service
# - Choose to redirect HTTP to HTTPS (recommended)
# Test auto-renewal (certificates expire every 90 days)
sudo certbot renew --dry-run
# Certbot sets up auto-renewal via cron/systemd timer automatically8. CI/CD and Deployment Automation
For App Platform:
DigitalOcean App Platform has built-in CI/CD:
- Every push to your connected branch triggers automatic deployment
- Zero-downtime deploys (similar to Heroku)
- Automatic rollback on failed health checks
- View build logs in dashboard
For Droplets (Using GitHub Actions):
Set up automated deployments with GitHub Actions:
# Create .github/workflows/deploy.yml in your repo
name: Deploy to DigitalOcean
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to DigitalOcean
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DO_HOST }}
username: deploy
key: ${{ secrets.DO_SSH_KEY }}
script: |
cd ~/your-app
git pull origin main
npm install # or pip install -r requirements.txt
npm run build # if needed
sudo systemctl restart myapp
sudo systemctl status myapp
# Add secrets in GitHub repo settings:
# Settings → Secrets → Actions
# - DO_HOST: your droplet IP
# - DO_SSH_KEY: your private SSH key9. Monitoring and Logging
DigitalOcean Built-in Monitoring
Both App Platform and Droplets include free basic monitoring:
- CPU usage
- Memory usage
- Disk I/O
- Network bandwidth
- Access via DigitalOcean dashboard → Monitoring
Application Logs
App Platform:
- View logs in dashboard under "Runtime Logs"
- Supports log streaming (similar to
heroku logs --tail) - Logs retained for 7 days on Basic/Pro plans
Droplets:
# View systemd service logs
sudo journalctl -u myapp -f # Follow logs in real-time
sudo journalctl -u myapp --since "1 hour ago"
sudo journalctl -u myapp -n 100 # Last 100 lines
# View Nginx access logs
sudo tail -f /var/log/nginx/access.log
# View Nginx error logs
sudo tail -f /var/log/nginx/error.logAdvanced Monitoring (Optional)
Consider these tools for production apps:
- Uptime Monitoring: UptimeRobot (free), Pingdom
- Error Tracking: Sentry (free tier available)
- APM: New Relic, Datadog (can be expensive)
- Log Aggregation: Papertrail (free tier), Logtail
10. Common Issues and Troubleshooting
Issue: App Won't Start After Migration
Symptoms: 502 Bad Gateway or app crashes immediately
Solutions:
- Check environment variables are set correctly
- Verify database connection string is correct
- Check logs:
sudo journalctl -u myapp -n 50 - Ensure port bindings are correct (App Platform uses PORT env var)
- Verify Node.js/Python version matches Heroku
Issue: Database Connection Fails
Symptoms: "Connection refused" or "Authentication failed"
Solutions:
- Check DATABASE_URL format:
postgresql://user:pass@host:port/dbname - Ensure database allows connections from your app's IP (check DigitalOcean database Trusted Sources)
- For Droplets: Check PostgreSQL is running:
sudo systemctl status postgresql - Test connection manually:
psql $DATABASE_URL
Issue: Static Files Not Loading
Symptoms: Missing CSS/JS/images, broken styles
Solutions:
- For Django: Run
python manage.py collectstatic - Check Nginx configuration serves
/static/correctly - Verify file permissions:
chmod -R 755 /path/to/staticfiles/ - For App Platform: Ensure build command includes static file generation
Issue: SSL Certificate Not Working
Symptoms: "Not Secure" warning, HTTPS doesn't work
Solutions:
- For App Platform: Wait 15-30 minutes after adding domain (auto-provisioning takes time)
- For Droplets: Check DNS records are correct (CNAME or A record)
- Run
sudo certbot certificatesto check status - Verify Nginx is listening on port 443:
sudo netstat -tlnp | grep :443
Issue: Higher Memory Usage Than Heroku
Symptoms: App uses more RAM on DigitalOcean than Heroku reported
Explanation:
- Heroku limits are enforced differently (quotas vs hard limits)
- Your app may have been RAM-constrained on Heroku and now uses its "real" needs
- This is often fine - DigitalOcean Droplets have dedicated RAM vs Heroku's shared resources
- If problematic, optimize your app or upgrade to larger plan
11. Migration Checklist
Pre-Migration
- ☐ Sign up for DigitalOcean with $200 free credits
- ☐ Document all Heroku environment variables
- ☐ List all Heroku add-ons and find replacements
- ☐ Backup Heroku database
- ☐ Choose migration path (App Platform vs Droplets)
- ☐ Test app locally with production-like environment
During Migration
- ☐ Create DigitalOcean resources (App/Droplet, Database)
- ☐ Deploy application code
- ☐ Migrate database data
- ☐ Configure environment variables
- ☐ Set up SSL certificate
- ☐ Configure custom domain
- ☐ Test thoroughly on temporary URL
Post-Migration
- ☐ Update DNS to point to DigitalOcean
- ☐ Monitor for errors (24-48 hours)
- ☐ Set up monitoring and alerting
- ☐ Configure automated backups
- ☐ Set up CI/CD pipeline
- ☐ Document new deployment process for team
- ☐ Keep Heroku app running for 1 week (safety net)
- ☐ After stable week, scale down or delete Heroku app
- ☐ Cancel Heroku add-ons to stop billing
Conclusion
Migrating from Heroku to DigitalOcean can save you 70-90% on hosting costs ($1,200-1,500/year for typical apps) while maintaining or improving performance.
Key Takeaways:
- ✅ App Platform is easiest (2-4 hours) and closest to Heroku experience
- ✅ Droplets offer maximum control and cost savings (4-6 hours setup)
- ✅ Database migration is the most critical step - backup first!
- ✅ SSL is free and automatic on both paths
- ✅ Start with $200 free credits to test: Sign up here
Most teams can complete the migration in a weekend and see immediate cost savings on their next billing cycle.
Need Help?
If you're stuck or want expert assistance:
- DigitalOcean Community: community.digitalocean.com
- DigitalOcean Tutorials: Comprehensive deployment guides
- r/webdev Reddit: Active community for deployment questions
Ready to Save Money?
Start your migration today with $200 in free credits:
Get $200 DigitalOcean Credits →
Free credits valid for 60 days. No credit card required to sign up.