Engineering

Heroku to Aptible Migration Guide

Nick Anderegg
Nick Anderegg
Product & Engineering

Heroku's varied and often platform-specific ways of deploying your application can make it difficult to understand the best way to migrate to another Platform as a Service. We're offering this guide to help you understand the flexible options you have for migrating an application from Heroku to Aptible.

Not only does Aptible make managing application infrastructure easier, but it also unlocks turnkey security & compliance benefits. When you’re ready to launch your application, you can deploy a dedicated stack with configuration monitoring and recommendations for further improvement surfaced directly in the Aptible Dashboard.

The guide will help you migrate your Heroku app and Postgres database to Aptible, and we'll provide specific advice for migrating from each of Heroku's deployment options.

Not migrating from Heroku? Here's how you can get started with Aptible.

Deploying with Git

Migrating your application from Heroku to Aptible by deploying via Git is the simplest method, but it only works for some deployment scenarios. Let's have a look at the changes that need to be made to Heroku's python-getting-started sample application to deploy it on Aptible.

There are a few files in that repository that control how the application is deployed to Heroku:

  • Procfile
  • app.json
  • requirements.txt
  • runtime.txt

When you push your application to Heroku, it uses the information in the runtime.txt file to determine which Buildpack to use (or, if a runtime is not specified, guesses which Buildpack to use based on the repo contents). The Buildpack then builds your app with Docker and deploys it on Heroku.

In contrast, Aptible allows you to have direct control over how your application is built and deployed. Compare the files above with the values defined in this example Dockerfile for deploying Django sites to Aptible:

FROM python:3.10

# System prerequisites
RUN apt-get update \
 && apt-get -y install build-essential libpq-dev \
 && rm -rf /var/lib/apt/lists/*

# If you require additional OS dependencies, you can install them here:
# RUN apt-get update \
#  && apt-get -y install imagemagick nodejs \
#  && rm -rf /var/lib/apt/lists/*

# Install Gunicorn. This won't cause any issues if Gunicorn
# is already present in your requirements.txt, but we specify it here
# because it's a generic runtime dependency.
RUN pip install gunicorn

# This handles the installation of dependencies from the requirements.txt
ADD requirements.txt /app/
WORKDIR /app
RUN pip install -r requirements.txt

ADD . /app

# Collect assets. This approach is not fully production-ready, but
# will help you experiment with Aptible before bothering with static files.
# Review /docs/static-assets
# for production-ready advice.
RUN set -a \
 && . ./.aptible.env \
 && python manage.py collectstatic

EXPOSE 8000

# A separate Procfile is optional when Docker's CMD instruction
# defines how the application should run.
CMD ["gunicorn", "--access-logfile=-", "--error-logfile=-", "--bind=0.0.0.0:8000", "--workers=3", "mysite.wsgi"]

The Dockerfile above takes over the responsibilities of the Procfile, app.json, and runtime.txt files:

  • Docker's CMD instruction defines how to run the container.
  • The base image and most of the metadata specified inapp.json is handled by ADD, ENV, or RUN instructions.
  • The app's runtime is still specified by the base image of the Docker image.

It's important to note that while a Procfile is not needed to deploy to Aptible, they are still supported. Have a look at Defining Services to understand the difference between running an implicit service with Docker's CMD instruction and declaring an explicit service with Procfiles.

Also note that while Heroku uses Profiles to define releases phases, you can accomplish similar results by the addition of an .aptible.yml file to define a before_release configuration.

The only thing from Heroku's set of configuration files that is not handled by this Dockerfile is environment variable generators. However, Aptible does give you full control over the configuration values that are exposed to your runtime as environment variables, which can help you accomplish the same goal.

Once you understand the relationship between Heroku's configuration files and Aptible's Dockerfile deployments, you can follow our Python Quickstart Guide to deploy your application on Aptible, and you can review the database migration section below to understand how to get your data out of Heroku.

GitHub Action

If you are using GitHub Actions to deploy your application on Heroku, you can continue to use this type of deployment to run your application on Aptible. Have a look at the configuration differences between the GitHub Actions offered by Heroku and Aptible, respectively.

Original Heroku Deployment

This is Heroku's sample configuration for their GitHub Action. To use the Action, you must have a Dockerfile in your project containing a CMD instruction, which Heroku uses to build the image and deploy the web server to their platform.

# Heroku example GitHub Action configuration
name: Deploy

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: khileshns/heroku-deploy@v3.12.12 # this is Heroku's Action
        with:
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: <app name>
          heroku_email: <email>
          usedocker: true

Migrating to Aptible's GitHub Action

In contrast, Aptible's GitHub Action deploys a container image that you have already published to a Docker registry. You can find a comparison guide of the steps to do so on Aptible and Heroku in the next section. Once your container image is published, the following configuration can deploy the container using Aptible's GitHub Action!

If you are using a private registry, you can set up Private Registry Authentication once ahead of time using the Aptible CLI.

# Aptible example GitHub Action configuration
name: Deploy to Aptible

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aptible/aptible-deploy-action@master # this is Aptible's Action
        with:
          username: ${{ secrets.APTIBLE_USERNAME }}
          password: ${{ secrets.APTIBLE_PASSWORD }}
          environment: <environment name>
          app: <app name>
          docker_img: <docker image name>
          private_registry_username: ${{ secrets.REGISTRY_USERNAME }}
          private_registry_password: ${{ secrets.REGISTRY_PASSWORD }}

As you can see from the examples above, the usage of each GitHub Action is quite similar. The primary difference between them today is that Aptible requires your Docker image to be built beforehand, something which can also be handled with GitHub Actions!

Deploying Docker Images Directly

If you're used to deploying your application directly with Docker via the Heroku container registry, the process for deploying Docker applications on Aptible will probably feel familiar to you.

This section will walk you through how your existing Heroku deployment workflow translates to Direct Docker Image Deploy using Aptible. We will be using this nginx Heroku repo as the starting point of the application we want to migrate to Aptible.

Original Heroku Deployment

To deploy a Docker image to Heroku, you can run the commands below, assuming that you have properly configured your app.json file containing the name of the app:

heroku container:login        # login to container registry
heroku create                 # create the app
heroku container:push web     # build the image and upload to the heroku registry
heroku container:release web  # deploy the app

Migrating to Aptible's Direct Docker Deployment

To deploy a Docker image to Aptible, you may choose any registry to host your container images. In this example, we will use hub.docker.com as our container registry.

APP_IMAGE="<username>/web"
APP_HANDLE="heroku-docker-nginx-example"

# Build and push the Docker image to Docker Hub
docker login                    # log into hub.docker.com
docker build -t "$APP_IMAGE" .  # build the docker image
docker push "$APP_IMAGE"        # upload the image to docker's registry

# Deploy the image on Aptible
aptible create "$APP_HANDLE"    # create the app
aptible deploy \
  --app "$APP_HANDLE" \
  --docker-image "$APP_IMAGE"   # deploy the app

There are subtle differences between the two platforms. Aptible does not have an app.json equivalent that specifies the app's name, instead specifying this as a command-line argument. We also do not host our own Docker registry directly, but you may host your container images in any registry, such as Docker Hub or the GitHub Container Registry.

Private registry

If you would like to use a private image registry, we have special flags to support that use case.

aptible deploy \
  --app "$APP_HANDLE" \
  --docker-image "$APP_IMAGE" \
  --private-registry-email="my.email@initech.com" \
  --private-registry-username="myuser" \
  --private-registry-password="1234"

See Aptible deploy docs for more information about our options.

Deploying with Terraform

If your app is normally deployed using Heroku's Terraform provider, there are some key differences to keep in mind when migrating an app from Heroku to Aptible.

Aptible's Terraform provider implementation is focused on the functions necessary for managing the lifecycle of your deployment, so you will need to set up users and provision your environment through other methods. Your provisioned environment is then exposed to Terraform via the aptible_environment Data Source.

The Aptible Terraform provider requires Direct Docker Image Deployment, so you must first build and push your app's image to a Docker registry.

Application Definition and Scaling

As the starting point for the application we want to migrate from Heroku, let's look at the example application from HashiCorp's "Deploy, Manage, and Scale an Application on Heroku".

Heroku Terraform Example

resource "heroku_app" "example" {
  name   = var.app_name
  region = "us"
}

resource "heroku_build" "example" {
  app = heroku_app.example.id

  source {
    path = "./app"
  }
}

resource "heroku_formation" "example" {
  app        = heroku_app.example.id
  type       = "web"
  quantity   = var.app_quantity
  size       = "Standard-1x"
  depends_on = [heroku_build.example]
}

output "app_url" {
  value       = heroku_app.example.web_url
  description = "Application URL"
}

Aptible Terraform Example

An application can be deployed in the same way on Aptible with a similar configuration:

data "aptible_environment" "appenv" {
  handle = var.env_name
}

resource "aptible_app" "app" {
  env_id = data.aptible_environment.appenv.env_id
  handle = var.app_name
  config = {
    APTIBLE_DOCKER_IMAGE = var.docker_image
  }
  service {
      process_type = "cmd"
      container_count = var.app_quantity
      container_memory_limit = 512
  }
}

resource "aptible_endpoint" "app_endpoint" {
  env_id = data.aptible_environment.appenv.env_id
  process_type = "cmd"
  resource_id = aptible_app.app.app_id
  resource_type = "app"
  endpoint_type = "https"
  internal = false
  container_port = var.container_port
  default_domain = true
}

output "app_url" {
  value       = aptible_endpoint.app_endpoint.virtual_domain
  description = "Application URL"
}

Understanding the Configuration

Let's walk through a bit of what is happening here.

First, we're defining an environment variable with the name of the Docker image to use (see the Aptible Terraform guide for information about using private registries).

config = {
    APTIBLE_DOCKER_IMAGE = var.docker_image
  }

Next, to scale the application, we define a service block which specifies the scaling and memory limits per container (memory limits are set directly - 512MB would be equivalent to the "Standard-1x").

  service {
      process_type = var.process_type
      container_count = var.app_quantity
      container_memory_limit = 512
  }

Next, we're explicitly defining an endpoint

resource "aptible_endpoint" "app_endpoint" {
  env_id = data.aptible_environment.appenv.env_id
  process_type = var.process_type
  resource_id = aptible_app.app.app_id
  resource_type = "app"
  endpoint_type = "https"
  internal = false
  container_port = var.container_port
  default_domain = true
}

The options selected will output a default domain - comparable to the Heroku domain. Note that the endpoint should point (via container_port) to a port that is contained in your container's EXPOSE directive. The process_type should match either your Procfile defined in your container or be set to "cmd" if using the Dockerfile's CMD instruction directly.

Configuring Terraform for the Database

Next, let's look at the database configuration. The Heroku configuration adds the following:

resource "heroku_addon" "postgres" {
  app  = heroku_app.example.id
  plan = "heroku-postgresql:hobby-dev"
}

This automatically injects a DATABASE_URL environment variable into the application with the properly formatted connection URL. On the Aptible side, we require more explicit configuration:

resource "aptible_database" "psql" {
  env_id = data.aptible_environment.appenv.env_id
  handle = var.app_name
  database_type = "postgresql"
  version = "14"
  container_size = 1024
  disk_size = 10
}

resource "aptible_app" "app" {
  ...
  config = {
    ...
    DATABASE_URL = aptible_database.psql.default_connection_url
  }
  ...
}

Be sure to review the Aptible Terraform guide for more information on using the Aptible Terraform provider.

Migrating a PostgreSQL Database from Heroku

There are multiple ways to create a database on Aptible, and this is most commonly done via the web interface or the command line. When creating your satabase, remember to allocate enough space for the database you want to restore—the backup itself doesn't include the database's indexes (which will be generated upon restoration), so ensure that you select enough space to have a solid margin for new data.

Prerequisites

We need three command-line tools before we can begin:

  1. The Heroku CLI
  2. The Postgres restore utility

On MacOS these can all be installed using Homebrew:

$ brew install --cask aptible
$ brew tap heroku/brew 
$ brew install heroku

# libpq can be used to install postgres tools without a running server.
$ brew install libpq
$ echo 'export PATH="/opt/homebrew/opt/libpq/bin:$PATH"' >> ~/.zshrc

You also need to be logged in to both Heroku and Aptible.

$ heroku login
heroku: Press any key to open up the browser to login or q to exit
 ›   Warning: If browser does not open, visit
 ›   https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com

$ aptible login

Prepare the Database

We can begin by creating a basic database deployment using the db:create CLI command.

aptible db:create --type postgresql --disk-size 20

Using our Aptstract utility

We built aptstract to simplify the process of migrating data to Aptible from other services.

Install Aptstract

Head to the Aptstract Latest Release Page and download the aptstract.pex file. Then open a terminal in the same folder as aptstract.pex and run the following commands.

chmod +x aptstract.pex
mv aptstract.pex apstract

Now you can use the aptstract utility while inside of this folder.

You can also install directly from the aptstract Github repository.

Fetch your Data

For applications with a single database, provide the name of the Heroku App:

$ aptstract fetch-database HEROKU_APP

If your application has multiple databases, they can be retrieved by name:

$ aptstract fetch-database HEROKU_APP HEROKU_DATABASE

Database files will be placed inside the backups folder of your current working directory.

Restore to Aptible

Now we're ready to restore the backup to the Aptible database we created earlier:

aptstract restore-data my-aptible-database ./backups/rph-test-app.dump

Full Aptstract Example

Here's an example with all the pieces put together:

$ aptstract fetch-database my_sample_app my_sample_database
$ aptstract restore-data my-aptible-database ./backups/my_sample_app.my_sample_database.dump

The Migrate Command

You can run the entire process above with a single interactive command.

$ aptstract migrate

The migrate command will ask you a few questions- 1. The name of your Heroku application. 2. If you have more than one database, and if so which database to migrate. 3. The name of your Aptible database.

Since this is an interactive command you should avoid using it inside of other scripts, instead using the individual fetch and restore commands.

Manual Migration

Schedule Backup

If the application only has a single database you only need to specify an application.

heroku pg:backups:capture --app HEROKU_APP

If the application has multiple databases, you will need to supply a database name as well.

heroku pg:backups:capture --app HEROKU_APP HEROKU_DATABASE

Download Backup

Now, you can download the backup. By default, the most recent backup will be downloaded.

heroku pg:backups:download --app HEROKU_APP --output=backup.dump

Open Tunnel

In another shell, create a database tunnel to Aptible.

$ aptible db:tunnel APTIBLE_DATABASE

Keep that shell open as it maintains your connection to the database. You will need the values supplied in the next step.

Run Restore

Next, we run the restore itself. Swap the placeholder values with the values from the db tunnel command.

pg_restore --host=APTIBLE_DATABASE_HOST --port=APTIBLE_DATABASE_PORT --username=APTIBLE_DATABASE_USERNAME  --dbname=APTIBLE_DATABASE backup.dump

You will be prompted for a password, which will be the one provided when you opened the tunnel. Once the password is supplied, the restore should begin.

Close Tunnel

Once the restore has completed you can close your database tunnel and that shell.

Conclusion

Which method should I choose for deployment?

While there are many options, your choice of method should consider your requirements, your resources, and your current deployment process. Deploying with Git is the most common and is often leveraged for simplicity—especially if you’re just getting started! Of course, many of our customers love deploying to Docker with the Aptible GitHub Action.

Ready to get started?

Create your free account loaded with $500 in credits and deploy securely in 5 minutes or less.

Latest From Our Blog