Heroku and Kuberns describe an application in similar terms, so most of this migration is translation rather than redesign. Your repository does not change. Kuberns deploys from source, and the AI agent reads the same signals a buildpack reads: the dependency file, the framework, the start command, and the port your process listens on.
The work that actually takes time is the part Heroku managed for you as attached services: add-ons, the credentials they injected, and your data.
What maps directly
| On Heroku | On Kuberns |
|---|---|
| App | A service inside a project |
| Deployed branch | An environment, one per branch |
| Buildpack detection | The AI agent, which detects framework, runtime, dependency file, web port, and build commands |
Procfile process types |
Procfile commands for web and worker processes |
| Config vars | Environment variables, scoped per environment |
| Heroku Postgres | A managed PostgreSQL datastore |
| Redis add-on | A Redis queue or cache resource |
| Worker dynos | Background workers: Celery or Node Worker |
| Custom domains | Custom domains with automatic SSL |
heroku logs |
The environment's logs |
What you move yourself
Four things do not carry across on their own.
- Your data. Kuberns provisions an empty database. Loading your Heroku dump into it is a step you run, covered in Step 5.
- Add-ons that are not a database, cache, or queue. Transactional email, error tracking, log drains, schedulers, and search stay with their own providers. Keep the account and move its credentials into environment variables.
- Anything written to the dyno filesystem. Heroku's filesystem is ephemeral, so a working Heroku app is almost always writing uploads to external object storage already. If yours does, nothing changes. If it writes to local disk and relies on that surviving, it needs the same external-storage fix it needed on Heroku.
- DNS. You move the records when you are ready to cut over, not before.
Before you start
Collect these from the Heroku app while it is still running.
# Config vars, in KEY=value form
heroku config -s --app <your-app>
# The process types Kuberns will need to reproduce
cat Procfile
# A fresh database dump
heroku pg:backups:capture --app <your-app>
heroku pg:backups:download --app <your-app>Also write down your add-on list, your custom domains and their current TTLs, and any scheduled jobs. Scheduled work is the item teams most often forget until after cutover.
Do not delete anything on Heroku yet. The old app is your rollback plan until Step 8 is verified.
Step 1: Connect the repository
Create the service from your Git provider and pick the branch that is currently deployed on Heroku. Deploy it as a non-production environment first, on the Kuberns-provided URL. Nothing about this step affects your live Heroku app.

The agent then analyzes the repository and returns a configuration for review.
Step 2: Check what the agent detected
The agent reports the framework, root directory, dependency file, base image, web port, build commands, and any Procfile commands it found.

If the agent's result is wrong or incomplete, see agent failures and restart deploy.
Step 3: Map your process types
A Heroku Procfile and a Kuberns Procfile command describe the same thing: what to run.
web: gunicorn myapp.wsgi
worker: celery -A myapp worker -l infoThe web entry becomes the server process. Each additional process type becomes its own resource in the environment: a Celery or Node Worker background worker, or a miscellaneous process for anything outside those categories.
Add one resource per process type you were running. A worker that has no resource simply will not run, and that failure is silent until you notice the queue growing.
Step 4: Recreate config vars and replace add-ons
Add your config vars as environment variables on the environment. Key names must match exactly, including case.
Three groups need attention rather than a straight copy:
- Add-on credentials. Heroku injected values such as
DATABASE_URLandREDIS_URLfrom the attached add-on. Kuberns provisions its own resources, so these must be replaced with the values from your new database and cache, not copied from Heroku. - External provider keys. API keys for email, payments, and monitoring carry over unchanged.
- Anything Heroku-specific. Variables referencing Heroku hostnames, dyno metadata, or app names need new values.

Create the database, cache, and queue resources the application needs from the environment's Resources tab.

Once a database exists, its connection details, database name, username, password, and hostname, are on its datastore overview page. Those are the values your environment variables should carry.
Environment variables apply on the next deploy, and saving them triggers one automatically.
Step 5: Move your data
Kuberns gives you an empty managed database and the credentials to reach it. Loading the dump is your step, using the standard client for that database.
# PostgreSQL
pg_restore --no-owner --no-acl \
-h <kuberns-hostname> -U <username> -d <database> \
latest.dumpUse mysql or mongorestore in place of pg_restore for the other supported database types.
Two things are worth knowing before you run this:
--no-owner --no-aclmatters. A Heroku dump carries Heroku's role names, which do not exist on your new database.- Kuberns does not restore a backup into a running datastore for you, in either direction. Its own backups are dumps you download and load the same way. Test that restore path once now, while nothing depends on it.
Run this import at least twice: once now against the test environment, and once again as the final sync during cutover. The first run is where you find the schema and extension problems.
Step 6: Validate before cutting over
Work through the test environment on its Kuberns URL with production-shaped data.
- The application boots and serves requests.
- Background workers pick up jobs, and scheduled work actually fires.
- Migrations have run.
- Outbound integrations, webhooks, and callbacks reach the new environment.
- Logs and metrics look like the old app's, not quieter.

Anything that fails here is far cheaper to fix now than after DNS has moved.
Step 7: Lower your DNS TTL
Do this a day ahead, not during the cutover. Drop the TTL on the records you are about to change to something short, such as 300 seconds, and wait for the old TTL to expire. Rollback speed on cutover day is set entirely by the TTL you chose the day before.
Step 8: Move the domain
Add the hostname under the environment's Custom Domains, then point DNS at the target Kuberns shows you.
Subdomain CNAME api -> <Kuberns hostname shown in the dashboard>
Root domain A @ -> <server IP shown in the dashboard>
The domain moves through DNS verification, SSL provisioning, and activation. See DNS, SSL, and activation if it stalls in any of those stages.
Run the final data sync immediately before the switch, during a window in which the old app is not accepting writes. Otherwise writes that land on Heroku after your dump are lost.
Rollback
Keep the Heroku app deployed, and its add-ons attached, until you are confident. With a short TTL, rolling back is pointing DNS at Heroku again.
The rollback stops being clean once the new environment has taken writes, because that data now exists only on Kuberns. That is the point at which the migration is real, and it is worth knowing exactly when you cross it.
Common problems
Application boots on Heroku but not on Kuberns. Usually the web port. Confirm the port the agent detected matches the one your process binds to.
Workers are idle. Every non-web process type needs its own resource. Check the process types in your old Procfile against the resources on the environment.
Database connection refused after import. Check that the environment variable holds the Kuberns datastore hostname and credentials, not the Heroku add-on URL.
Uploads disappear after a deploy. The application is writing to local disk. This behaves the same way it did on Heroku and needs external object storage.
Scheduled jobs never run. Heroku Scheduler is an add-on and does not migrate. Its jobs need to be recreated.