Why Leave Heroku?
Since Heroku removed its free tier in 2022, costs have increased significantly. A basic Heroku setup (1 dyno + Postgres + Redis) can cost $30-50/month, while a VPS with equivalent resources costs $5-15/month. Self-hosting also gives you full control over your infrastructure.
Heroku to VPS Mapping
| Heroku Feature | Self-Hosted Equivalent |
|---|---|
| Dynos | Docker containers or systemd services |
| Heroku Postgres | PostgreSQL installed directly |
| Heroku Redis | Redis installed directly |
| Heroku CLI deploy | Git + deploy script or CI/CD |
| Config Vars | .env file or Docker env |
| Add-ons | Self-hosted equivalents |
| SSL | Certbot / Let us Encrypt |
Export from Heroku
# Export database
heroku pg:backups:capture -a my-app
heroku pg:backups:download -a my-app
# Creates latest.dump (PostgreSQL custom format)
# Export config vars
heroku config -a my-app --shell > .env
# Get application code (should be in Git already)
git clone https://github.com/you/my-app.git
Set Up VPS
# Install Docker
curl -fsSL https://get.docker.com | bash
# Create docker-compose.yml
version: "3.8"
services:
app:
build: .
ports: ["3000:3000"]
env_file: .env
depends_on: [db, redis]
restart: always
db:
image: postgres:16
volumes: [pg_data:/var/lib/postgresql/data]
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: always
redis:
image: redis:7-alpine
volumes: [redis_data:/data]
restart: always
nginx:
image: nginx:alpine
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- certs:/etc/letsencrypt
depends_on: [app]
restart: always
volumes:
pg_data:
redis_data:
certs:
Restore Database
# Restore Heroku PostgreSQL dump
docker compose up -d db
docker compose exec -T db pg_restore --clean --no-owner \
-U myapp -d myapp < latest.dump
Adapt Procfile to Docker
# Heroku Procfile:
# web: gunicorn app:app
# worker: celery -A tasks worker
# Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "app:app", "-b", "0.0.0.0:3000"]
# Worker as separate service in docker-compose:
worker:
build: .
command: celery -A tasks worker
env_file: .env
depends_on: [db, redis]
restart: always
Deploy Script
#!/bin/bash
# /usr/local/bin/deploy.sh
cd /opt/my-app
git pull origin main
docker compose build
docker compose up -d
docker compose exec app python manage.py migrate
echo "Deployed at $(date)"
SSL and Domain
# Install certbot and get certificate
apt install -y certbot
certbot certonly --standalone -d example.com
# Configure Nginx to use the certificate
Best Practices
- Use Docker Compose for Heroku-like simplicity
- Set up CI/CD with GitHub Actions for automated deploys
- Configure automated database backups
- Set up monitoring (Uptime Kuma, Prometheus)
- Use a process manager or Docker restart policies for reliability