Heroku to Azure App Service Migration
Detailed guidance for migrating Heroku applications to Azure App Service.
Service Mapping
| Heroku Service | Azure Equivalent |
|---|---|
| Heroku App | Azure App Service |
| Dynos (Web) | App Service instances |
| Dynos (Worker) | WebJobs (continuous) or Azure Functions |
| Procfile | Startup command / Docker entrypoint |
| Buildpacks | Docker or App Service runtime stacks |
| Config Vars | App Settings / App Configuration / Key Vault |
| Heroku Postgres | Azure Database for PostgreSQL Flexible Server |
| Heroku Redis | Azure Cache for Redis |
| Heroku Kafka | Azure Event Hubs (Kafka-compatible) |
| Add-ons (general) | Azure managed services |
| Heroku Pipelines | GitHub Actions / Azure DevOps |
| Review Apps | Deployment Slots |
| Heroku CI | GitHub Actions |
| Heroku Scheduler | WebJobs (triggered) or Azure Functions Timer |
| Heroku ACM (SSL) | App Service Managed Certificate |
| Custom Domains | App Service Custom Domains |
| Heroku Logs / Logplex | Application Insights / Azure Monitor |
| Heroku Metrics | Azure Monitor Metrics |
| Heroku Connect | Azure Data Factory or Logic Apps |
| Private Spaces | App Service Environment (ASE) |
Dyno → App Service Plan Mapping
| Heroku Dyno Type | App Service Plan | Notes |
|---|---|---|
| Free / Eco | Free (F1) | Dev/test only — F1 has shared compute with quota limits (CPU minutes), not equivalent to Heroku's 60 min/day cap. See App Service Free SKU limits |
| Basic | Basic (B1) | No auto-scale, no slots |
| Standard-1X (512 MB) | Standard (S1) | Auto-scale, slots, custom domains |
| Standard-2X (1 GB) | Standard (S2) | More memory |
| Performance-M (2.5 GB) | Premium v3 (P1v3) | VNet, more instances |
| Performance-L (14 GB) | Premium v3 (P3v3) | High-memory workloads |
| Private-M / Private-L | App Service Environment (ASE) | Network-isolated |
Procfile → Startup Configuration
Web Process
| Procfile Entry | Azure Equivalent |
|---|---|
web: node server.js | Startup command: node server.js |
web: gunicorn app:app | Startup command: gunicorn --bind=0.0.0.0 --timeout 600 app:app |
web: java -jar target/app.jar | Startup command: java -jar app.jar |
web: bundle exec puma -C config/puma.rb | Custom container with Dockerfile |
Worker Process
Heroku worker dynos have no direct App Service equivalent. Migration paths:
| Heroku Worker Pattern | Azure Equivalent |
|---|---|
worker: node worker.js | WebJob (continuous) |
worker: celery -A tasks worker | Container App or AKS |
clock: node scheduler.js | Azure Functions Timer trigger |
release: rake db:migrate | Deployment slot swap scripts |
Add-ons → Azure Managed Services
| Heroku Add-on | Azure Equivalent | Migration Notes |
|---|---|---|
| Heroku Postgres | PostgreSQL Flexible Server | Use Azure DMS for data migration |
| Heroku Redis | Azure Cache for Redis | Export/import RDB or use replication |
| Heroku Kafka | Azure Event Hubs (Kafka API) | Compatible Kafka protocol |
| Papertrail | Application Insights | Structured logging migration |
| New Relic | Application Insights | APM feature parity |
| SendGrid | Azure Communication Services or SendGrid on Azure | SendGrid available via Azure Marketplace |
| Cloudinary | Azure Blob Storage + CDN | Media storage + delivery |
| Memcachier | Azure Cache for Redis | Redis supports memcache patterns |
| Bonsai (Elasticsearch) | Azure AI Search or Elastic on Azure | Search service migration |
| Heroku Scheduler | Azure Functions Timer trigger | Cron-based scheduling |
| Bucketeer (S3) | Azure Blob Storage | Object storage migration |
Config Vars → App Configuration / Key Vault
| Config Var Type | Azure Target | Implementation |
|---|---|---|
DATABASE_URL | App Setting (managed identity) | Use Entra auth, not connection string |
REDIS_URL | App Setting | Azure Cache connection string or managed identity |
SECRET_KEY | Key Vault reference | @Microsoft.KeyVault(SecretUri=...) |
API_KEY (third-party) | Key Vault reference | Store in Key Vault |
NODE_ENV / RAILS_ENV | App Setting | Add as a regular App Setting (e.g., NODE_ENV=production) — controls runtime behavior. Do NOT confuse with WEBSITES_NODE_DEFAULT_VERSION, which controls the Node.js engine version on Windows App Service. |
PORT | Auto-injected | App Service sets PORT automatically |
| Feature flags | App Configuration | Feature management support built-in |
⚠️ Warning: Heroku's
DATABASE_URLincludes credentials. On Azure, use managed identity authentication instead. Never migrate connection strings with embedded passwords.
Buildpacks → Docker
For apps using standard runtimes, use App Service runtime stacks directly. For custom buildpacks, migrate to Docker:
| Buildpack Scenario | Azure Approach |
|---|---|
| Official Node.js buildpack | App Service Node.js runtime stack |
| Official Python buildpack | App Service Python runtime stack |
| Official Java buildpack | App Service Java runtime stack |
| Multi-buildpack (e.g., Node + Python) | Custom Dockerfile |
| Custom buildpack (apt packages) | Custom Dockerfile |
| Heroku CNB (Cloud Native Buildpack) | Azure Container Apps with CNB |
Heroku Pipelines → GitHub Actions
| Pipeline Feature | Azure Equivalent |
|---|---|
| Pipeline stages (dev → staging → prod) | GitHub Actions environments |
| Auto-deploy on push | GitHub Actions on: push trigger |
| Review Apps | Deployment slots with PR-based deploy |
| Pipeline promotion | Slot swap (staging → production) |
| Heroku CI test runner | GitHub Actions test job |
| Release phase | Pre-swap slot scripts |
Example: GitHub Actions Deployment
name: Deploy to App Service
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for azure/login@v2 OIDC token request
contents: read # Required to checkout the repo
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: azure/webapps-deploy@v3
with:
app-name: '<app-name>'
slot-name: 'staging'⚠️ Without the
permissions:block,azure/login@v2's OIDC token request fails with HTTP 403.
Review Apps → Deployment Slots
| Heroku Review Apps | App Service Deployment Slots |
|---|---|
| Auto-create per PR | Create via GitHub Actions per PR |
| Unique URL per PR | <app>-<slot>.azurewebsites.net |
| Auto-destroy on PR close | Delete slot in PR close workflow |
| Shared pipeline config | Slot-specific App Settings |
app.json postdeploy scripts | Slot swap pre/post actions |