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

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:

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:

2. Cost Comparison: Heroku vs DigitalOcean

Let's compare real-world costs for a typical production app:

ComponentHerokuDO App PlatformDO Droplets
Web Server (2GB RAM)$50/mo (2x Standard)$12/mo$12/mo
Worker (1GB RAM)$25/mo$6/moIncluded
PostgreSQL (10GB)$50/mo$15/moIncluded
Redis (100MB)$15/mo$7/moIncluded
SSL CertificateFreeFreeFree (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

Path 2: DigitalOcean Droplets (Maximum Control)

Best for: Teams with DevOps experience, custom infrastructure needs, maximum cost savings

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

  1. Sign up at DigitalOcean with $200 free credits
  2. Verify your email and add payment method (required even with free credits)
  3. Navigate to "App Platform" in the main menu

Step 2: Create New App from GitHub

  1. Click "Create App"
  2. Connect your GitHub account (same process as Heroku)
  3. Select the repository containing your app
  4. Choose the branch to deploy (usually main or production)
  5. 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 start

Step 4: Set Environment Variables

Copy environment variables from Heroku:

  1. Get Heroku env vars: heroku config -a your-app-name
  2. In DigitalOcean App Platform, go to "Environment Variables"
  3. 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 PlanRAM/CPUPriceHeroku Equivalent
Basic512MB / 1 vCPU$5/moEco Dyno ($5-7/mo)
Professional1GB / 1 vCPU$12/moStandard 1X ($25/mo)
Professional+2GB / 2 vCPU$24/moStandard 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:

  1. Click "Add Resource" → "Database"
  2. Choose PostgreSQL, MySQL, MongoDB, or Redis
  3. Select size (Basic $15/mo is sufficient for most apps)
  4. Database credentials are auto-added to environment variables

Step 7: Deploy and Test

  1. Click "Create Resources" to deploy your app
  2. DigitalOcean will build and deploy (usually takes 3-10 minutes)
  3. You'll get a temporary URL: your-app-xxxxx.ondigitalocean.app
  4. Test thoroughly before switching DNS

Step 8: Custom Domain Setup

  1. In App Platform, go to "Settings" → "Domains"
  2. Click "Add Domain"
  3. Enter your domain (e.g., yourdomain.com)
  4. Add CNAME record to your DNS provider:
    • Type: CNAME
    • Name: @ (or www)
    • Value: your-app-xxxxx.ondigitalocean.app
  5. DigitalOcean auto-provisions SSL certificate (takes 5-15 minutes)

Step 9: Switch Traffic from Heroku

Once you've tested your DigitalOcean app:

  1. Update DNS to point to DigitalOcean (see step above)
  2. Wait for DNS propagation (usually 5-60 minutes)
  3. Monitor your app for errors
  4. 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

  1. Sign up at DigitalOcean with $200 free credits
  2. Click "Create" → "Droplets"
  3. Choose Ubuntu 22.04 LTS (recommended for most apps)
  4. 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
  5. Choose datacenter region (closest to your users)
  6. Add SSH key for secure access (or use password)
  7. 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 gunicorn

Step 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 port

For 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:8000

Step 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 running

Step 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 nginx

6. 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

  1. In DigitalOcean, go to "Databases" → "Create Database Cluster"
  2. Choose PostgreSQL and version matching Heroku (check with heroku pg:info)
  3. Select size (Basic $15/mo is good for most apps)
  4. Choose same datacenter as your app
  5. Click "Create Database Cluster"
  6. 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 directory

Step 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 above

Option 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_production

Database Migration Checklist

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 automatically

8. CI/CD and Deployment Automation

For App Platform:

DigitalOcean App Platform has built-in CI/CD:

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 key

9. Monitoring and Logging

DigitalOcean Built-in Monitoring

Both App Platform and Droplets include free basic monitoring:

Application Logs

App Platform:

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.log

Advanced Monitoring (Optional)

Consider these tools for production apps:

10. Common Issues and Troubleshooting

Issue: App Won't Start After Migration

Symptoms: 502 Bad Gateway or app crashes immediately

Solutions:

Issue: Database Connection Fails

Symptoms: "Connection refused" or "Authentication failed"

Solutions:

Issue: Static Files Not Loading

Symptoms: Missing CSS/JS/images, broken styles

Solutions:

Issue: SSL Certificate Not Working

Symptoms: "Not Secure" warning, HTTPS doesn't work

Solutions:

Issue: Higher Memory Usage Than Heroku

Symptoms: App uses more RAM on DigitalOcean than Heroku reported

Explanation:

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:

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:

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.