Heroku is a Platform-as-a-Service (PaaS) that simplifies deployment by handling infrastructure, scaling, and operations. While more expensive than raw infrastructure, Heroku dramatically reduces DevOps overhead for small teams. This guide covers deploying production applications to Heroku from a senior developer's perspective.
Why Heroku
Heroku excels in specific scenarios:
- Rapid Deployment: Git push to deploy
- Managed Infrastructure: No server administration
- Add-ons Ecosystem: One-click databases, caching, monitoring
- Auto-Scaling: Handle traffic spikes automatically
- CI/CD Integration: Built-in pipelines
Best for: MVPs, small teams, applications prioritizing development speed over cost optimization.
Getting Started
Install CLI
# macOS
brew tap heroku/brew && brew install heroku
# Ubuntu/Debian
curl https://cli-assets.heroku.com/install.sh | sh
# npm (any platform)
npm install -g heroku
Authenticate
heroku login
# Opens a browser for authentication
# Or non-interactive
heroku login -i
Create Application
# Create app with a random name
heroku create
# Create with a specific name
heroku create my-awesome-app
# Create in a specific region
heroku create my-app --region eu
Node.js Deployment
Project Structure
my-app/
├── package.json
├── package-lock.json
├── Procfile
├── app.js
└── .gitignore
package.json
{
"name": "my-heroku-app",
"version": "1.0.0",
"engines": {
"node": "18.x"
},
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
Procfile
Defines process types:
web: node app.js
worker: node worker.js
app.js
const express = require('express');
const app = express();
// Heroku provides the PORT environment variable
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello from Heroku!' });
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Deploy
# Initialize Git if needed
git init
git add .
git commit -m "Initial commit"
# Add Heroku remote
heroku git:remote -a my-awesome-app
# Deploy
git push heroku main
# Open in browser
heroku open
Python/Django Deployment
Project Structure
my-django-app/
├── requirements.txt
├── runtime.txt
├── Procfile
├── manage.py
├── myproject/
│ ├── settings.py
│ └── wsgi.py
└── staticfiles/
requirements.txt
Django==4.2
gunicorn==21.2.0
whitenoise==6.6.0
dj-database-url==2.1.0
psycopg2-binary==2.9.9
runtime.txt
python-3.11.4
Procfile
web: gunicorn myproject.wsgi --log-file -
release: python manage.py migrate
settings.py Updates
import os
import dj_database_url
# Security
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = ['*'] # Configure properly for production
# Database
DATABASES = {
'default': dj_database_url.config(
default='sqlite:///db.sqlite3',
conn_max_age=600
)
}
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# WhiteNoise for static files
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ... other middleware
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
PHP/Laravel Deployment
Procfile
web: vendor/bin/heroku-php-apache2 public/
Deploy Commands
# Set buildpack
heroku buildpacks:set heroku/php
# Set environment variables
heroku config:set APP_KEY=$(php artisan key:generate --show)
heroku config:set APP_ENV=production
heroku config:set APP_DEBUG=false
# Deploy
git push heroku main
# Run migrations
heroku run php artisan migrate --force
Environment Variables
Set Config Vars
# Set a single variable
heroku config:set DATABASE_URL=postgres://...
heroku config:set SECRET_KEY=your-secret-key
# Set multiple
heroku config:set NODE_ENV=production DEBUG=false
# View all config vars
heroku config
# Remove variable
heroku config:unset DEBUG
Use in Application
// Node.js
const dbUrl = process.env.DATABASE_URL;
const secret = process.env.SECRET_KEY;
# Python
import os
db_url = os.environ.get('DATABASE_URL')
secret = os.environ.get('SECRET_KEY')
Add-ons
Database (Heroku Postgres)
# Add free tier
heroku addons:create heroku-postgresql:mini
# Add production tier
heroku addons:create heroku-postgresql:standard-0
# Get connection info
heroku pg:info
heroku config:get DATABASE_URL
# Access psql
heroku pg:psql
Redis
# Add Redis
heroku addons:create heroku-redis:mini
# Get connection URL
heroku config:get REDIS_URL
Other Useful Add-ons
# Logging
heroku addons:create papertrail:choklad
# Monitoring
heroku addons:create newrelic:wayne
# Email
heroku addons:create sendgrid:starter
# Scheduler (cron jobs)
heroku addons:create scheduler:standard
Scaling
Manual Scaling
# Scale web dynos
heroku ps:scale web=2
# Scale workers
heroku ps:scale worker=1
# View current scale
heroku ps
# Turn off
heroku ps:scale web=0 worker=0
Dyno Types
# Upgrade dyno type
heroku ps:type web=standard-1x
heroku ps:type worker=standard-2x
Logs and Monitoring
View Logs
# Stream logs
heroku logs --tail
# Filter by process
heroku logs --tail --ps web
# Last N lines
heroku logs -n 500
# Filter by source
heroku logs --source app
heroku logs --source heroku
One-off Commands
# Run command
heroku run node scripts/seed.js
heroku run python manage.py shell
# Run bash
heroku run bash
Pipelines and Review Apps
Create Pipeline
# Create pipeline
heroku pipelines:create my-pipeline -a my-app-production
# Add staging app
heroku pipelines:add my-pipeline -a my-app-staging -s staging
# Promote staging to production
heroku pipelines:promote -a my-app-staging
app.json for Review Apps
{
"name": "My App",
"scripts": {
"postdeploy": "npm run db:seed"
},
"env": {
"NODE_ENV": "development",
"SECRET_KEY": {
"generator": "secret"
}
},
"addons": [
"heroku-postgresql:mini",
"heroku-redis:mini"
],
"buildpacks": [
{ "url": "heroku/nodejs" }
]
}
CI/CD Integration
GitHub Actions
name: Deploy to Heroku
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: my-app
heroku_email: [email protected]
Heroku CI
app.json:
{
"environments": {
"test": {
"scripts": {
"test": "npm test"
},
"addons": [
"heroku-postgresql:mini"
]
}
}
}
Custom Domains and SSL
Add Custom Domain
# Add domain
heroku domains:add www.example.com
heroku domains:add example.com
# View DNS targets
heroku domains
# Configure DNS
# Point www to <app>.herokudns.com (CNAME)
# Point apex to <app>.herokudns.com (ALIAS/ANAME)
SSL
# Automatic SSL (paid dynos only)
heroku certs:auto:enable
# Manual SSL
heroku certs:add server.crt server.key
Troubleshooting
Common Issues
# Check app status
heroku ps
# Restart all dynos
heroku restart
# Check for errors
heroku logs --tail
# Check releases
heroku releases
# Roll back to the previous release
heroku rollback v10
Build Issues
# View build logs
heroku builds:output
# Clear build cache
heroku builds:cache:purge -a my-app
Key Takeaways
- Procfile is essential: Defines how to run your app
- Environment variables: Never hardcode secrets
- Use add-ons: Managed services save time
- Scale appropriately: Start small, scale when needed
- Monitor logs: Essential for debugging production issues
- Use pipelines: Staging → Production workflow
Heroku trades cost for simplicity—perfect for teams that need to ship fast without dedicated DevOps resources.