Deploying to Heroku Platform

Module 28: DevOps & Deployment - Wednesday, Lecture 3

Introduction to Heroku

In our previous lectures, we explored various cloud providers and delved into AWS deployment strategies. Now, we'll focus on Heroku, a Platform as a Service (PaaS) that offers a simpler, more developer-friendly approach to deploying applications.

Heroku abstracts away much of the infrastructure complexity, allowing developers to focus on writing code rather than managing servers. Think of Heroku as a concierge service for your applications—you provide the code, and Heroku handles the infrastructure, scaling, monitoring, and other operational aspects.

flowchart TD A[Developer] --> B[Git Push to Heroku] B --> C[Heroku Platform] C --> D[Build Process] D --> E[Runtime Environment] E --> F[Running Application] G[Managed Add-ons] --> E H[Automatic Scaling] --> E I[Monitoring & Logging] --> E

Why Choose Heroku?

When Heroku Might Not Be the Best Choice

Heroku Architecture

Core Concepts

Understanding Heroku's core concepts is essential for effective deployment:

mindmap root((Heroku Architecture)) Apps Dynos Web Dynos Worker Dynos One-off Dynos Config Vars Buildpacks Slugs Add-ons Databases Caching Monitoring Search Pipelines Review Apps Staging Production Deployment Git-based Container Registry GitHub Integration

Applications

A Heroku application is a collection of dynos, configuration, and add-ons that together run your code.

Dynos

Dynos are lightweight Linux containers that run your application code. Heroku offers several dyno types:

Buildpacks

Buildpacks are scripts that prepare your code for execution. They detect the language and framework of your application and install the necessary dependencies.

Slugs

A slug is a compressed and packaged copy of your application optimized for distribution to the dyno manager. The buildpack compiles your application into a slug during the build process.

Add-ons

Add-ons are third-party services that extend your application's functionality:

Config Vars

Config vars are environment variables that allow you to customize your application's behavior without modifying code.

Release

A release is a snapshot of your application code, config vars, and add-ons. Heroku maintains a history of releases, allowing you to roll back if needed.

Deploying to Heroku

Preparation for Deployment

Before deploying to Heroku, ensure your application meets these requirements:

Language Support

Heroku supports many languages and frameworks through buildpacks:

Essential Files

Depending on your language, you'll need specific files:

Procfile

A Procfile specifies the commands that are executed by the app on startup:

# Example Procfile for Node.js
web: node server.js

# Example Procfile for Python
web: gunicorn app:app

# Example Procfile with multiple process types
web: node server.js
worker: node worker.js

Port Configuration

Your application must listen on the port specified by the PORT environment variable:

// Node.js example
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});
# Python Flask example
from flask import Flask
import os

app = Flask(__name__)

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)

Deployment Methods

Heroku offers several ways to deploy your application:

Git Deployment

The most common method is deploying directly from Git:

# Create a new Heroku app
heroku create my-awesome-app

# Deploy your code
git push heroku main

# Open your application in a browser
heroku open

GitHub Integration

You can connect your GitHub repository to Heroku for automated deployments:

  1. Go to your Heroku Dashboard
  2. Select your app
  3. Go to the "Deploy" tab
  4. Select "GitHub" as the deployment method
  5. Connect to your GitHub repository
  6. Enable automatic deploys (optional)

Container Registry

For Docker-based deployments, you can use Heroku Container Registry:

# Log in to the Heroku Container Registry
heroku container:login

# Build and push your Docker image
docker build -t registry.heroku.com/my-app/web .
docker push registry.heroku.com/my-app/web

# Release the image to your app
heroku container:release web -a my-app

Heroku CLI Deployment

You can deploy directly using the Heroku CLI:

# Deploy from a specific directory
heroku deploy:src

# Deploy a WAR file (Java)
heroku deploy:war --war target/myapp.war

Managing Environment Variables

Config vars in Heroku allow you to customize your application's behavior:

# Set a config var
heroku config:set DATABASE_URL=postgres://username:password@host:port/database

# View all config vars
heroku config

# Remove a config var
heroku config:unset API_KEY

Managing Add-ons

Heroku add-ons extend your application's functionality:

# Add PostgreSQL database
heroku addons:create heroku-postgresql:hobby-dev

# Add Redis for caching
heroku addons:create heroku-redis:hobby-dev

# Add Papertrail for logging
heroku addons:create papertrail:choklad

# List all add-ons
heroku addons

# Remove an add-on
heroku addons:destroy heroku-redis

Heroku Deployment Examples

Node.js Application

Deploying a typical Node.js/Express application:

Required Files

# package.json
{
  "name": "nodejs-example",
  "version": "1.0.0",
  "description": "Node.js Example for Heroku",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "engines": {
    "node": "18.x"
  }
}
# server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from Heroku!');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Deployment Steps

# Initialize Git repository (if not already done)
git init
git add .
git commit -m "Initial commit"

# Create Heroku app
heroku create nodejs-example-app

# Deploy to Heroku
git push heroku main

# Scale to 1 web dyno (default)
heroku ps:scale web=1

# Open the app in browser
heroku open

Python Flask Application

Deploying a Flask application to Heroku:

Required Files

# app.py
from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def home():
    return 'Hello from Flask on Heroku!'

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port)
# requirements.txt
flask==2.2.3
gunicorn==20.1.0
# Procfile
web: gunicorn app:app

Deployment Steps

# Create a virtual environment (local development)
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install flask gunicorn

# Generate requirements.txt
pip freeze > requirements.txt

# Initialize Git and commit
git init
git add .
git commit -m "Initial commit"

# Create Heroku app
heroku create flask-example-app

# Deploy to Heroku
git push heroku main

# Open the app
heroku open

React + Node.js Full-Stack Application

Deploying a full-stack application with React frontend and Node.js backend:

Project Structure

my-fullstack-app/
├── client/             # React frontend
│   ├── public/
│   ├── src/
│   ├── package.json
│   └── ...
├── server/             # Node.js backend
│   ├── server.js
│   └── ...
├── package.json        # Root package.json
└── ...

Root package.json

{
  "name": "fullstack-example",
  "version": "1.0.0",
  "engines": {
    "node": "18.x"
  },
  "scripts": {
    "start": "node server/server.js",
    "build": "cd client && npm install && npm run build",
    "dev": "concurrently \"npm run server\" \"npm run client\"",
    "server": "nodemon server/server.js",
    "client": "cd client && npm start",
    "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "concurrently": "^7.6.0",
    "nodemon": "^2.0.21"
  }
}

Server Code

// server/server.js
const express = require('express');
const path = require('path');

const app = express();
const port = process.env.PORT || 5000;

// API routes
app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello from the backend!' });
});

// Serve static assets in production
if (process.env.NODE_ENV === 'production') {
  // Set static folder
  app.use(express.static(path.join(__dirname, '../client/build')));

  // Any route that's not an API route will be handled by the React app
  app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, '../client/build', 'index.html'));
  });
}

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Deployment Steps

# Initialize Git repository
git init
git add .
git commit -m "Initial commit"

# Create Heroku app
heroku create fullstack-example-app

# Set environment variables
heroku config:set NODE_ENV=production

# Deploy to Heroku
git push heroku main

# Open the app
heroku open

Database-Backed Application

Deploying an application with a PostgreSQL database:

Adding a Database

# Add PostgreSQL add-on
heroku addons:create heroku-postgresql:hobby-dev

# Check the database URL
heroku config:get DATABASE_URL

# Connect to the database
heroku pg:psql

Database Connection Code (Node.js/Sequelize)

// db.js
const { Sequelize } = require('sequelize');

// Extract database configuration from DATABASE_URL
const databaseUrl = process.env.DATABASE_URL || 'postgres://localhost:5432/local_db';

// Configure SSL for Heroku PostgreSQL
const sequelize = new Sequelize(databaseUrl, {
  dialect: 'postgres',
  dialectOptions: {
    ssl: {
      require: true,
      rejectUnauthorized: false // Required for Heroku PostgreSQL
    }
  }
});

// Test the connection
async function testConnection() {
  try {
    await sequelize.authenticate();
    console.log('Database connection has been established successfully.');
  } catch (error) {
    console.error('Unable to connect to the database:', error);
  }
}

testConnection();

module.exports = sequelize;

Database Migrations

# Run migrations on Heroku
heroku run npx sequelize-cli db:migrate

# Seed the database
heroku run npx sequelize-cli db:seed:all

Advanced Heroku Topics

Heroku Pipelines

Heroku Pipelines provide a continuous delivery workflow for your applications:

graph LR A[GitHub Repository] --> B[Review Apps] B --> C[Staging App] C --> D[Production App] E[Automated Tests] --> B E --> C

Setting Up a Pipeline

# Create a pipeline
heroku pipelines:create my-pipeline --stage staging -a my-staging-app

# Add an app to a pipeline
heroku pipelines:add my-pipeline -a my-production-app --stage production

# Promote an app from staging to production
heroku pipelines:promote -a my-staging-app

Review Apps

Review Apps create temporary apps for each GitHub pull request:

  1. Go to your pipeline dashboard
  2. Enable Review Apps
  3. Configure the app.json file in your repository
# app.json example
{
  "name": "My Application",
  "description": "My application description",
  "repository": "https://github.com/username/repo",
  "env": {
    "NODE_ENV": {
      "value": "review"
    }
  },
  "addons": [
    "heroku-postgresql:hobby-dev"
  ],
  "buildpacks": [
    {
      "url": "heroku/nodejs"
    }
  ],
  "scripts": {
    "postdeploy": "npm run db:migrate"
  }
}

Custom Domains & SSL

Using custom domains with your Heroku apps:

# Add a custom domain
heroku domains:add www.example.com

# Verify domain ownership
heroku domains:add example.com

# Automatically manage SSL certificates
heroku certs:auto:enable

# View domain status
heroku domains

Then update your DNS settings with your domain provider:

Scaling Applications

Scaling your application horizontally and vertically:

# Scale horizontally (add more dynos)
heroku ps:scale web=3 worker=2

# Scale vertically (use different dyno types)
heroku ps:resize web=standard-2x

# View current dyno configuration
heroku ps

# View resource usage
heroku ps:utilization

Monitoring & Logging

Viewing logs and monitoring your application:

# View real-time logs
heroku logs --tail

# Filter logs by source
heroku logs --source app --tail

# Add Papertrail add-on for advanced logging
heroku addons:create papertrail:choklad

# Add New Relic for monitoring
heroku addons:create newrelic:wayne

# View application metrics
heroku metrics

Heroku Scheduler

Running scheduled tasks with Heroku Scheduler:

# Add Scheduler add-on
heroku addons:create scheduler:standard

# Open Scheduler dashboard
heroku addons:open scheduler

In the Scheduler dashboard, you can add jobs that run at specified intervals:

Example commands for scheduled jobs:

Heroku Deployment Best Practices

Application Architecture

Performance Optimization

Security Considerations

Deployment Workflow

Monitoring and Maintenance

Real-World Heroku Examples

Example 1: SaaS Web Application

A multi-tier SaaS application deployed on Heroku:

graph TD A[GitHub Repository] --> B[Heroku Pipeline] B --> C[Heroku Review Apps] B --> D[Staging App] B --> E[Production App] E --> F[Web Dynos
React + Node.js] E --> G[Worker Dynos
Background Jobs] E --> H[Scheduler
Periodic Tasks] F & G & H --> I[PostgreSQL Database] F & G & H --> J[Redis Cache] K[Custom Domain
example.com] --> E L[Monitoring
New Relic] --> E M[Logging
Papertrail] --> E

Key Components:

Example 2: Content Management System

A WordPress site deployed on Heroku:

graph TD A[WordPress Core] --> B[Heroku Web Dyno] B --> C[PostgreSQL Database] B --> D[AWS S3
Media Storage] E[Redis Cache] --> B F[Cloudflare CDN] --> B G[SendGrid
Email Service] --> B H[Custom Theme] --> B I[Custom Plugins] --> B

Implementation Details:

Example 3: Microservices Architecture

A system with multiple interconnected services on Heroku:

graph TD A[API Gateway
Node.js] --> B[User Service
Python] A --> C[Product Service
Node.js] A --> D[Order Service
Java] A --> E[Payment Service
Ruby] B & C & D & E --> F[Service Registry] B --> G[User Database] C --> H[Product Database] D --> I[Order Database] E --> J[Payment Database] K[Message Queue
RabbitMQ] --> B & C & D & E

Architecture Notes:

Limitations and Alternatives

Heroku Limitations

While Heroku is great for many applications, it has some limitations:

Alternative PaaS Providers

If Heroku doesn't meet your needs, consider these alternatives:

Provider Key Features Best For
Render Simple deployments, free tier, native support for many languages Modern web applications, startups
DigitalOcean App Platform Simple pricing, integrated with DigitalOcean services Developers familiar with DigitalOcean
Google App Engine Automatic scaling, integration with Google Cloud Applications needing deep Google Cloud integration
Azure App Service Windows and Linux options, enterprise integration Enterprise applications, .NET applications
AWS Elastic Beanstalk AWS integration, more control over infrastructure Applications needing deeper AWS integration
Fly.io Global deployment, persistent volumes, WebSockets Applications needing global presence
Railway Developer-friendly, GitHub integration, persistent storage Developer projects, startups

When to Consider Moving from Heroku

Signs that you might need to look beyond Heroku:

Migration Strategies

If you need to migrate from Heroku, consider these approaches:

Practical Exercise: Deploying to Heroku

Exercise: Deploy a Full-Stack Application to Heroku

In this exercise, you'll deploy a full-stack application (React frontend + Express backend) to Heroku.

Prerequisites

Step 1: Create a Simple Full-Stack Application

# Create project directory
mkdir heroku-fullstack-demo
cd heroku-fullstack-demo

# Initialize Git
git init

# Create package.json
npm init -y

# Install dependencies
npm install express cors

# Create a simple server file
cat > server.js << 'EOF'
const express = require('express');
const path = require('path');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(cors());
app.use(express.json());

// API route
app.get('/api/info', (req, res) => {
  res.json({ 
    message: 'Hello from the backend!',
    env: process.env.NODE_ENV,
    timestamp: new Date()
  });
});

// Serve static assets in production
if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));
  
  app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
  });
}

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
EOF

# Update package.json scripts
npm pkg set scripts.start="node server.js"
npm pkg set scripts.heroku-postbuild="cd client && npm install && npm run build"

# Create client React app
npx create-react-app client

# Update client src/App.js
cat > client/src/App.js << 'EOF'
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Determine API URL based on environment
    const apiUrl = process.env.NODE_ENV === 'production' 
      ? '/api/info' 
      : 'http://localhost:5000/api/info';
      
    fetch(apiUrl)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error fetching data:', error);
        setLoading(false);
      });
  }, []);
  
  return (
    

Full-Stack Heroku Deployment

{loading ? (

Loading data...

) : data ? (

Message from server: {data.message}

Environment: {data.env}

Timestamp: {new Date(data.timestamp).toLocaleString()}

) : (

No data received from server

)}
); } export default App; EOF # Create Procfile echo "web: npm start" > Procfile # Create .gitignore cat > .gitignore << 'EOF' node_modules .env .DS_Store npm-debug.log client/node_modules client/build EOF # Commit changes git add . git commit -m "Initial commit"

Step 2: Deploy to Heroku

# Login to Heroku
heroku login

# Create a new Heroku app
heroku create fullstack-demo-app

# Set environment variables
heroku config:set NODE_ENV=production

# Push to Heroku
git push heroku main

# Open the app
heroku open

# View logs
heroku logs --tail

Step 3: Add a Database

# Add PostgreSQL database
heroku addons:create heroku-postgresql:hobby-dev

# Update server.js to use the database
# (Not included in this example for brevity)

# Commit and push changes
git add .
git commit -m "Add database support"
git push heroku main

Step 4: Configure a Pipeline

# Create a pipeline
heroku pipelines:create fullstack-pipeline --stage production -a fullstack-demo-app

# Create a staging app
heroku create fullstack-demo-staging --remote staging

# Add staging app to pipeline
heroku pipelines:add fullstack-pipeline -a fullstack-demo-staging --stage staging

# Push to staging
git push staging main

# Promote from staging to production
heroku pipelines:promote -a fullstack-demo-staging

Challenge Extensions

Conclusion

Heroku provides a developer-friendly platform for deploying applications with minimal infrastructure management. Its simplicity, integrated services, and streamlined workflow make it an excellent choice for many web applications, particularly for startups, small teams, and projects where development speed is a priority.

Key takeaways from this lecture include:

Whether you're deploying a simple web application, a complex microservices architecture, or anything in between, Heroku offers a platform that lets you focus on your code rather than infrastructure management.

Additional Resources