July 14, 2025

How-to host Drupal 11 on Heroku

Heroku doesn't usually come up in conversations about Drupal hosting. Most teams reach for it when building Rails apps, Node backends, or launching SaaS prototypes. But that doesn't mean it's off the table for Drupal.

Matthew Crist
Matthew Crist
Co-Founder, Chief of Technology

Heroku doesn’t usually come up in conversations about Drupal hosting. Most teams reach for it when building Rails apps, Node backends, or launching SaaS prototypes. But that doesn’t mean it’s off the table for Drupal.

In fact, Heroku’s strengths — git-based deployments, managed add-ons, easy scaling, and a developer experience that stays out of your way — map surprisingly well to Drupal 11’s modern architecture. If you are already comfortable with Heroku or want to avoid the operational overhead of traditional Drupal hosting, this guide walks you through getting it running.

Why Heroku for Drupal

Traditional Drupal hosting options fall into two camps: managed Drupal hosts (Pantheon, Acquia, Platform.sh) and self-managed infrastructure (AWS, DigitalOcean, bare metal). The managed hosts are good but expensive and opinionated. The self-managed route gives you control but requires ongoing server administration.

Heroku sits in a useful middle ground:

  • Git-based deployments. Push to a branch and your site deploys. No SSH, no rsync, no deployment scripts to maintain.
  • Managed databases. Heroku Postgres is production-grade, handles backups automatically, and scales with a slider.
  • Add-on ecosystem. Redis, email, monitoring, logging, and scheduling are all available as managed add-ons.
  • Horizontal scaling. Need more capacity? Scale your dynos. Need less? Scale them back. You pay for what you use.
  • CI/CD built in. Heroku Pipelines give you review apps, staging, and production environments connected to your Git workflow.

The tradeoff is that Heroku requires you to think about your application differently than a traditional LAMP stack. The filesystem is ephemeral. You need external services for things that a traditional server handles locally. But if you understand those constraints upfront, the operational simplicity is worth it.

What you need

Before you start, make sure you have the following:

  • A Heroku account (the Eco or Basic plan works for development; Production dynos for anything real)
  • The Heroku CLI installed
  • PHP 8.3+ and Composer installed locally
  • A fresh Drupal 11 project (or an existing one you want to migrate)
  • An S3-compatible object storage account (AWS S3, Cloudflare R2, or MinIO) for file uploads

Step 1: Create your Drupal project

If you are starting fresh, create a new Drupal 11 project with Composer:

composer create-project drupal/recommended-project my-drupal-site
cd my-drupal-site

Initialize a Git repository if one does not exist:

git init
git add .
git commit -m "Initial Drupal 11 project"

Step 2: Configure the Heroku app

Create a new Heroku app and add the required add-ons:

heroku create my-drupal-site
heroku addons:create heroku-postgresql:essential-0
heroku addons:create heroku-redis:mini

Heroku needs to know how to build and serve your PHP application. Create a Procfile in your project root:

web: vendor/bin/heroku-php-apache2 web/

This tells Heroku to use Apache with the web/ directory as the document root, which is where Drupal 11’s index.php lives in the recommended project structure.

Step 3: Configure PHP and extensions

Create or update your composer.json to specify the PHP version and required extensions:

{
  "require": {
    "php": "^8.3",
    "ext-pdo_pgsql": "*",
    "ext-redis": "*"
  }
}

Run composer update to regenerate the lock file after making changes.

Step 4: Database configuration

Heroku provides the database connection string as the DATABASE_URL environment variable. Drupal needs this parsed into its settings format. Add the following to web/sites/default/settings.php:

if (getenv('DATABASE_URL')) {
  $db = parse_url(getenv('DATABASE_URL'));
  $databases['default']['default'] = [
    'database' => ltrim($db['path'], '/'),
    'username' => $db['user'],
    'password' => $db['pass'],
    'host' => $db['host'],
    'port' => $db['port'],
    'driver' => 'pgsql',
    'prefix' => '',
  ];
}

Yes, this means running Drupal on PostgreSQL instead of MySQL. Drupal 11 has full PostgreSQL support, and Heroku Postgres is significantly easier to manage than bolting on a MySQL add-on. If you have MySQL-specific queries in custom modules, you will need to test them, but core and most contributed modules work fine on Postgres.

Step 5: Redis for caching

Drupal’s default database caching works but is not ideal for production. Heroku Redis gives you a proper cache backend. Install the Redis module:

composer require drupal/redis

Add the Redis configuration to your settings.php:

if (getenv('REDIS_URL')) {
  $redis = parse_url(getenv('REDIS_URL'));
  $settings['redis.connection']['host'] = $redis['host'];
  $settings['redis.connection']['port'] = $redis['port'];
  if (!empty($redis['pass'])) {
    $settings['redis.connection']['password'] = $redis['pass'];
  }
  $settings['cache']['default'] = 'cache.backend.redis';
  $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';
}

Step 6: Handle the ephemeral filesystem

This is the biggest gotcha with Drupal on Heroku. Heroku’s filesystem is ephemeral — any files written to disk are lost when the dyno restarts or redeploys. This means Drupal’s default behavior of storing uploaded files in sites/default/files/ will not work.

You need to use an external object storage service. The S3 File System module is the standard solution:

composer require drupal/s3fs

Configure it via environment variables in settings.php:

if (getenv('S3_BUCKET')) {
  $settings['s3fs.access_key'] = getenv('S3_ACCESS_KEY');
  $settings['s3fs.secret_key'] = getenv('S3_SECRET_KEY');
  $settings['s3fs.bucket'] = getenv('S3_BUCKET');
  $settings['s3fs.region'] = getenv('S3_REGION');

  // Use Cloudflare R2 or another S3-compatible provider
  if (getenv('S3_ENDPOINT')) {
    $config['s3fs.settings']['use_customhost'] = TRUE;
    $config['s3fs.settings']['hostname'] = getenv('S3_ENDPOINT');
  }

  $settings['s3fs.use_s3_for_public'] = TRUE;
  $settings['s3fs.use_s3_for_private'] = TRUE;
}

Set the environment variables on Heroku:

heroku config:set S3_BUCKET=my-drupal-files
heroku config:set S3_ACCESS_KEY=your-access-key
heroku config:set S3_SECRET_KEY=your-secret-key
heroku config:set S3_REGION=us-east-1

Cloudflare R2 is a cost-effective alternative to AWS S3 with no egress fees. If you are using R2, add the endpoint:

heroku config:set S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com

Step 7: Cron and scheduled tasks

Drupal relies on cron to run scheduled tasks — clearing caches, processing queues, checking for updates. On a traditional server, you would set up a system cron job. On Heroku, use the Heroku Scheduler add-on:

heroku addons:create scheduler:standard

Then add a scheduled job via the Heroku dashboard or CLI that runs every 10 minutes:

vendor/bin/drush cron

Make sure Drush is in your project dependencies:

composer require drush/drush

Step 8: Deploy

Set a hash salt and any other required environment variables:

heroku config:set DRUPAL_HASH_SALT=$(openssl rand -hex 32)

Update your settings.php to use it:

$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT');

Commit everything and deploy:

git add .
git commit -m "Configure Drupal 11 for Heroku"
git push heroku main

Run the Drupal installation via Drush:

heroku run vendor/bin/drush site:install --db-url=\$DATABASE_URL -y

Your Drupal 11 site should now be running on Heroku.

Gotchas and things to watch

A few things to keep in mind as you run Drupal on Heroku in production:

  • Config management is essential. Use Drupal’s configuration export/import (drush config:export and drush config:import) to manage configuration in code. Do not rely on the database for configuration state — your deployment pipeline should be the source of truth.
  • Dyno sleeping. Eco dynos sleep after 30 minutes of inactivity. The first request after sleeping takes several seconds. Use Basic or Standard dynos for anything public-facing.
  • Build time. Composer installs can be slow on Heroku. Use the Composer buildpack’s caching to speed up subsequent deploys.
  • SSL. Heroku provides free SSL on all apps via Automated Certificate Management. Drupal’s trusted_host_patterns setting should be configured to match your Heroku app domain.
  • Log management. Drupal’s watchdog logs go to the database by default. Consider using the Syslog module to send logs to Heroku’s log stream, which can be piped to add-ons like Papertrail for searchable log management.

A note on AI integration

One advantage of running on Heroku is easy access to external APIs, including AI services. Heroku’s ecosystem includes AI-related add-ons, and connecting your Drupal instance to OpenAI, Anthropic, or other AI APIs is straightforward — set the API keys as environment variables and use Drupal’s HTTP client or a contributed module to make the calls.

If you are using Drupal as a data platform (structured content exposed via JSON:API), Heroku’s infrastructure makes it simple to build a lightweight middleware service on another dyno that consumes your Drupal API, processes it through an AI model, and serves the results. The whole stack lives in one Heroku pipeline.

Is it worth it?

Heroku is not the cheapest option for Drupal hosting, and it is not the most common. But for teams that value deployment simplicity, managed infrastructure, and a developer experience that does not involve SSH sessions and server configuration, it is a legitimate option.

The key is understanding the constraints — especially the ephemeral filesystem — and designing around them from the start. If you do that, you get a Drupal hosting setup that deploys with git push, scales with a slider, and lets you focus on the application instead of the infrastructure.