Migration guide

Move from Heroku to Naijacloud

Your Heroku app running on Naijacloud as a web service, with its config vars, its Postgres data and its domain, and the Heroku app kept as a rollback.

One to two hours of work, after a 48-hour wait for DNSDowntime: none to a few minutes, if you follow the orderLast verified 7 Oct 2026

What things are called here

On Heroku
On Naijacloud
HerokuApp
Web service in a project
HerokuProcfile web: line
Read as the start command
Herokurelease: phase
No release phase. Put migrations in the start command
Herokuworker dyno
No worker type. Run it as a web service that also listens on PORT, or as a cron job
HerokuHeroku Postgres
Postgres database
HerokuHeroku Key-Value Store
Cache (Redis or Valkey)
HerokuHeroku Scheduler
Cron job, any cron schedule in UTC, up to 1 hour per run
HerokuConfig Vars
Variables, scoped per environment
HerokuBuildpacks
Automatic builds for Node, Python, Ruby, PHP, Go and more, or your own Dockerfile
HerokuPipelines
Environments in a project (dev by default)
Herokuheroku run
No shell into a running app
HerokuAdd-ons marketplace
No marketplace. Keep each vendor directly

Before you move: what Heroku has that we don't

  • The add-ons marketplace, with one bill for third-party services.
  • Follower databases and rollback to a point in time for Postgres.
  • A release phase that runs once before traffic moves.
  • Worker dynos as their own process type.
  • One-off dynos and a shell into a running app (heroku run).
  • Autoscaling.
  • Private Spaces and Shield.
  • A SOC 2 report.

If your app depends on one of these, stay on Heroku for now.

1

Lower the DNS TTL, 48 hours before

At your DNS provider, set the TTL on the record that points your domain at Heroku (usually a CNAME, or an ALIAS or ANAME at the root, to a herokudns.com target) to 60 seconds. Wait at least as long as the old TTL.

2

Deploy here and add the domain

In the dashboard, create a Database (Postgres) and copy its Internal URL. Then create a Web service from your app’s GitHub repository. Naijacloud detects the language and fills in the build and start commands. If your repo has a Procfile, its web: line becomes the start command.

Heroku’s release: line has no equivalent, so fold it into the start command. For a Rails app whose Procfile reads web: bundle exec puma -C config/puma.rb and release: bundle exec rails db:migrate, set the Start command to:

Start command
bundle exec rails db:migrate && bundle exec puma -C config/puma.rb

Export the config vars in .env format and load them on the new service:

Terminal
$ heroku config -s --app my-app > heroku.env
$ npm install -g @naijacloud/cli
$ naijacloud login
$ naijacloud env import heroku.env --service my-app

Before importing, delete DATABASE_URL, REDIS_URL and every key an add-on created from heroku.env: credentials for services you’re about to leave make an app start and half-work. Then set DATABASE_URL to the new database’s Internal URL under Variables, and run naijacloud redeploy my-app. Test the app on its naijacloud.app address.

Then open Domains, enter your domain in Custom domain and click Add domain. Add the records it lists that prove ownership and issue the certificate. Don’t change the routing record yet.

3

Wait for the certificate

Where the Domains table lists ownership and HTTPS records, the certificate is issued while traffic is still on Heroku. Wait until the domain shows Verified · HTTPS ready.

If the table lists only one routing record (an A record, as it does for a root domain and in some regions), the certificate is issued in the minutes after the record points here. Plan the switch for a quiet hour.

4

Copy the data

Heroku backups are already in pg_dump's custom format, so they restore directly. Use PostgreSQL 18 client tools. On Naijacloud, add your IP under the database’s External access → Specific IPs, switch Connection to External · TLS and copy the Connection URL.

Terminal
$ heroku pg:backups:capture --app my-app
$ heroku pg:backups:download --app my-app
$ pg_restore --clean --if-exists --no-owner --no-acl \
--dbname "$NAIJACLOUD_DATABASE_URL" latest.dump

How to know the restore is complete. Run analyze; on both databases first — n_live_tup is only an estimate until you do. Then compare the table list, exact counts on your two or three largest tables, the sequence values, and the extension list. Matching row counts with a sequence still sitting at 1 means the next insert will collide.

verify.sql · run on both, compare line for line
analyze;
select relname, n_live_tup from pg_stat_user_tables order by relname;
select count(*) from orders; -- your largest table
select last_value from orders_id_seq; -- sequences advanced
select extname from pg_extension order by 1; -- extensions present
Terminal
$ psql "$(heroku config:get DATABASE_URL --app my-app)" -f verify.sql
$ psql "$NAIJACLOUD_DATABASE_URL" -f verify.sql

Recreate each Heroku Scheduler job as a Cron job with the same command and a cron schedule in UTC.

5

Switch DNS

At your DNS provider, replace the Heroku target with the routing record shown in Domains. With a 60-second TTL, most visitors follow within minutes. Leave the domain on the Heroku app.

6

Keep the old app for 7 days

Scale the Heroku app to zero instead of destroying it: its config vars, releases and add-ons stay, and you can scale back up in seconds. Keep the Postgres database on its current plan.

Terminal
$ heroku ps:scale web=0 --app my-app
CostSee what this would costThe pricing page lists every size and rate in naira, from the same price list the meter charges from.Naijacloud pricing →
RollbackKeep these for 7 days
  • The Heroku app, scaled to zero, not destroyed
  • The Heroku Postgres database, on its current plan
  • The latest.dump file
  • The 60-second TTL on your record

To go back: run heroku ps:scale web=1 and point the record at Heroku again.

Move one service first. Keep the old one running.

Start your move

Runs in af-west · Port Harcourt or eu-west · Europe