# Move from Heroku to Naijacloud

Source: https://naijacloud.com/guides/migrate-from-heroku

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.

_90 min · Intermediate · Last verified 2026-10-07 · Stack: postgres, dns_

## What things are called here

| On Heroku | On Naijacloud |
| --- | --- |
| App | Web service in a project |
| Procfile web: line | Read as the start command |
| release: phase | (none) No release phase. Put migrations in the start command |
| worker dyno | (none) No worker type. Run it as a web service that also listens on PORT, or as a cron job |
| Heroku Postgres | Postgres database |
| Heroku Key-Value Store | Cache (Redis or Valkey) |
| Heroku Scheduler | Cron job, any cron schedule in UTC, up to 1 hour per run |
| Config Vars | Variables, scoped per environment |
| Buildpacks | Automatic builds for Node, Python, Ruby, PHP, Go and more, or your own Dockerfile |
| Pipelines | Environments in a project (dev by default) |
| heroku run | (none) No shell into a running app |
| Add-ons marketplace | (none) 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.

## The cutover, in order

1. **Lower DNS TTL**: 48 hours before. Set the record to 60 seconds.
2. **Deploy here and add the domain**: Traffic still goes to Heroku.
3. **Certificate issued**: Wait for it before you touch DNS, where you can.
4. **Copy the data**: Capture, restore, compare the counts.
5. **Switch DNS**: Point the record at Naijacloud.
6. **Keep the old app 7 days**: Going back is one DNS change.

## 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.

## 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:

```text title="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:

```sh
$ 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.

## 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**.

> **Warning: Never move DNS before the certificate is issued here**
>
> If the record points at us while we hold no certificate for the domain, every request fails TLS — a hard browser error, not a slow page — until issuance finishes.

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.

## 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**.

```sh
$ 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.

```sql title="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
```

```sh
$ psql "$(heroku config:get DATABASE_URL --app my-app)" -f verify.sql
$ psql "$NAIJACLOUD_DATABASE_URL" -f verify.sql
```

> **Warning: Check the restore before you switch DNS**
>
> Compare the output from both databases. If it differs, or pg_restore printed errors, fix it now. If the Heroku app is still taking writes, run `heroku maintenance:on --app my-app` for the few minutes a final capture and restore take, then switch DNS straight after.

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

## 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.

## 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.

```sh
$ heroku ps:scale web=0 --app my-app
```

> **Warning: Don’t destroy the old app yet**
>
> Destroying the Heroku app or its database removes your way back. After 7 days, check nothing still calls the old address, then remove them and raise the TTL again.

## Rollback: keep 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.

All guides: https://naijacloud.com/guides · Index for agents: https://naijacloud.com/llms.txt
