You can move a FastAPI, Flask, or Django API from Heroku to Edgenivo's native Python runtime without a Dockerfile when its dependencies fit the supported environment. Edgenivo installs the application's Python dependencies and loads its entrypoint, so the web process in your Procfile no longer controls how the API runs.
The other Procfile entries need their own replacements. Move a release command that runs migrations into a release pipeline, adapt a Celery worker as a queue subscriber, and invoke Heroku Scheduler jobs through cron routes. Heroku Postgres, Heroku Key-Value Store, and other add-ons need destination services before the API can take production traffic.
Copy link to headingOverview
By the end, you'll have:
- Pinned the Python version your Heroku app runs in a file Edgenivo reads.
- Run your API on Edgenivo's native Python runtime without the Procfile
webprocess orPORT. - Replaced Heroku Postgres, Heroku Key-Value Store, and config vars with Edgenivo-connected services and environment variables.
- Moved
workerdynos to Edgenivo Queues and Heroku Scheduler jobs to Edgenivo Cron Jobs. - Moved the Procfile
releasecommand into CI and cut production traffic over to Edgenivo.
Copy link to headingPrerequisites
- Access to the API's repository and Heroku app.
- A Edgenivo account and the latest Edgenivo CLI installed.
- Python and uv installed locally.
- The Heroku CLI, installed and authenticated.
- PostgreSQL client tools (
pg_restoreandpsql) if you're migrating a database.
Copy link to headingShould your API move to Edgenivo?
Check that your API and its dependencies fit Edgenivo's native runtime:
- Can sessions, uploads, and other persistent state live outside the application process?
- Do dependencies install from Python packages and compatible wheels, or does the application need system packages?
- Does background work run as bounded queue tasks, or does a process need to stay running indefinitely?
- Do requests fit the function's duration and payload limits?
Check the app's buildpacks with heroku buildpacks --app your_app_name_here. An app that adds operating-system packages through an extra buildpack, such as an Aptfile, needs those packages on Edgenivo too. In that case, use the Python container deployment path, which uses Container Images in Beta.
For an API that uses WebSockets, Python WebSocket support is in Beta. Connections follow function duration limits, so clients need a reconnection strategy.
Heroku requires an initial response within 30 seconds. Streaming can continue while data keeps resetting its 55-second idle timeout.
With Fluid compute, Edgenivo's default maxDuration is 300s for the entire invocation, including streaming. Hobby allows up to 300s; Pro and Enterprise allow up to 800s, with an opt-in extended-duration Beta up to 1,800s. Request and response bodies have a 4.5 MB limit. Check the function limits against your routes before cutover.
Copy link to headingHow Heroku concepts map to Edgenivo
| Heroku | Edgenivo |
|---|---|
| Python buildpack build | Framework detection and Python dependency installation |
.python-version or runtime.txt | .python-version (Edgenivo doesn't read runtime.txt) |
Procfile web process and PORT | Native Python application entrypoint |
Gunicorn settings and WEB_CONCURRENCY | Fluid compute concurrency and automatic scaling |
| Extra buildpacks for system packages | Container Images, in Beta |
Procfile worker process running Celery | A subscriber on Edgenivo Queues, in Beta |
Procfile release process | A migration step in continuous integration |
| Heroku Scheduler | A cron route configured in vercel.json |
| Config vars | Project variables or linked Shared Environment Variables |
| Heroku Postgres | External Postgres, including Marketplace providers |
| Heroku Key-Value Store | External Redis-compatible storage |
| Add-ons | Edgenivo Marketplace integrations or direct provider accounts |
| Dyno filesystem | /tmp scratch space, object storage, or a database |
| Review apps | Preview deployments |
| Pipeline promotion | A staged production deployment and vercel promote |
| 30-second initial-response timeout, then a 55-second streaming idle timeout | 300s default maxDuration, up to 800s on Pro and Enterprise |
heroku logs --tail | vercel logs and Runtime Logs in the dashboard |
Copy link to headingSteps
Create a migration branch and keep the Heroku app running until cutover. Skip the database, worker, or scheduler steps your API doesn't need.
Copy link to heading1. Pin the Python version your app runs
Check the Python version the Heroku app runs today:
The command starts a one-off dyno and prints a version such as Python 3.13.5. Without a version pin, Cedar-generation apps keep the Python release line selected by their first build, unless the build cache is cleared. Fir-generation apps use the default available at each build, so their Python version can change between builds.
Edgenivo supports Python 3.12, 3.13, and 3.14, with 3.12 as the default. Choose a supported version that works with your application's dependencies. Heroku also supports versions outside that range, including 3.11 and 3.15, so test any version change before moving the app to Edgenivo's native runtime.
Edgenivo reads .python-version but not runtime.txt, which Heroku deprecated in February 2025. If runtime.txt contains python-3.13.5, replace it with a .python-version file at the Edgenivo project root containing:
Keep the requires-python constraint in pyproject.toml aligned with your Python version pin. Since an open lower bound such as >=3.12 also permits 3.14, use a bounded constraint for Python 3.13:
Merge the relevant entries into your existing manifest, retaining the application's other dependencies. Keep your existing dependency workflow if it uses a manifest Edgenivo supports. With uv, generate the lockfile and install its dependencies:
The commands create or update uv.lock and install the resolved packages into .venv. Test with that environment, then commit uv.lock with the manifest changes. Edgenivo also accepts requirements.txt and Pipenv manifests; Celery on Edgenivo requires pyproject.toml.
Heroku builds from poetry.lock, but Edgenivo doesn't document it as a build input. Convert Poetry dependencies to the [project] format or export requirements.txt using Poetry's export plugin. Check native dependency compatibility with your Python version, especially wheels for older psycopg2-binary pins.
Python function bundles have a 500 MB uncompressed limit. Exclude tests, local environments, and other unneeded files with excludeFiles; Python builds don't automatically tree-shake the application. Large Functions, in Beta, raise the limit to 5 GB on projects using Fluid compute and Active CPU pricing. Check the opt-in requirements for an existing project; Large Functions don't support Secure Compute or Static IPs.
Copy link to heading2. Replace the Procfile web process with the Python entrypoint
For a FastAPI app with a worker and a release command, the Heroku Procfile commonly looks like this:
The main:app target in the web line already identifies the file and object Edgenivo needs. Steps 8 and 10 replace the worker and release lines.
Edgenivo loads the application object from a supported entrypoint, such as app in a FastAPI application's main.py. Keep your existing routes and add a health check:
For an undetected entrypoint, set the module path in pyproject.toml:
Use my_api.server:app to load app from my_api/server.py, or main:app for the root-level example. Flask also exports app; Django uses its configured WSGI or ASGI application. Set Django's allowed hosts for the deployment.
Heroku runs Django's collectstatic during the build unless you disable it. Edgenivo also runs it automatically when STATIC_ROOT is configured, then serves the collected assets from its CDN. Check the Django static-file instructions for your storage backend, including django-storages and WhiteNoise.
Gunicorn settings such as --workers, --timeout, and WEB_CONCURRENCY don't apply to the native deployment, which scales with Fluid compute. Keep Gunicorn or Uvicorn in your dependencies if another environment uses them. The native Edgenivo deployment doesn't use the Procfile or require a PORT setting. Keep the Procfile until the Heroku app is retired.
Copy link to heading3. Replace writes to the dyno filesystem
Heroku's dyno filesystem is temporary, so uploads usually belong in an external service such as Amazon S3. Check for code that still writes generated reports, cached files, or a SQLite database to the application directory.
Move uploaded files and generated artifacts to persistent object storage such as Edgenivo Blob, or keep the existing object storage and add its credentials in step 4. Put sessions and durable application records in Redis or a database, according to their persistence requirements.
The function filesystem is read-only except for up to 500 MB of scratch space in /tmp. Code that writes temporary files to the application directory on Heroku must write to /tmp instead. Treat that directory as temporary and local to an instance. For uploads above the function body limit, use a direct client upload flow so the file doesn't pass through the Python API.
Copy link to heading4. Provision backing services and environment variables
Copy link to headingLink the Edgenivo project
From the repository's application directory, link a new Edgenivo project:
Choose the intended team and project when prompted. Commercial APIs require Pro or Enterprise because Hobby is limited to non-commercial personal use.
The CLI saves project metadata in .vercel, which should stay out of version control along with exported secrets and database dumps. Add the following entries to .gitignore before exporting configuration or pulling local variables:
Copy link to headingReview Heroku config vars
Export the app's config vars in shell format with the Heroku CLI:
Review the exported entries locally before transferring them to Edgenivo. Remove variables that Heroku or its add-ons manage, because they point at Heroku resources and the destination services supply their own:
DATABASE_URLand anyHEROKU_POSTGRESQL_*_URLvaluesREDIS_URLfrom Heroku Key-Value StoreHEROKU_*variables, which Heroku recommends excluding when copying config- Credentials for other add-ons you're replacing
For an add-on you plan to keep, such as email or monitoring, confirm the provider's migration process before changing its Heroku subscription. Create a direct account or use Edgenivo Marketplace where supported, then test the new credentials.
Destroying a Heroku add-on deprovisions its service; detaching an attachment only removes that app's association and config vars. Check every application that shares the add-on before retiring it.
Copy link to headingAdd PostgreSQL with Neon
Neon in the Edgenivo Marketplace provides managed PostgreSQL databases for your project. From the linked application directory, create separate databases for Development, Preview, and Production with vercel integration add:
The CLI installs the integration if needed, prompts for resource settings and a billing plan, and connects each database to the linked project. Scoping each resource with --environment keeps development and preview test writes separate from production. The --no-env-pull flag skips the automatic variable download; the local testing step pulls Development values when you need them.
Choose a destination PostgreSQL version compatible with the source database and enable any required extensions before restoring data. Place the database near the API's function region to limit network latency between them.
The integration supplies connection strings for different uses:
| Environment variable | Use |
|---|---|
DATABASE_URL | Pooled connection for application queries |
DATABASE_URL_UNPOOLED | Direct connection for exports, restores, and schema migrations |
If you selected an integration prefix, use the resulting variable names in your application and migration commands. Leave automated Neon preview branching disabled for this rehearsal so the fixed Preview database restored in step 5 is used by the deployment in step 11.
If you choose preview branching, deploy first, identify the new branch in the Neon console, then repeat the restore and migrations against it before testing. Branch-specific connection strings are injected at build time and are not available through vercel env pull.
Copy link to headingConnect Redis if the API uses it
Connect a Redis provider from the Edgenivo Marketplace and obtain its TCP URL. This is typically a rediss:// URL for TLS; REST credentials such as KV_REST_API_URL use a different client interface. Set REDIS_URL to the TCP connection string and follow the provider's TLS certificate requirements.
Heroku Key-Value Store requires TLS on all plans, and many apps disable certificate verification with a setting such as ssl_cert_reqs=None to connect to it. Review that setting for the new provider rather than carrying it over.
Heroku Key-Value Store can hold cache entries, sessions, or queue state. Start with an empty replacement only if every stored value is disposable.
Copy link to headingAdd the remaining application secrets
Once the destination services are connected, open the Edgenivo project's Settings, then Environment Variables, and add the application secrets you retained from Heroku. Select the appropriate Development, Preview, and Production scopes for each value. Review entries individually so imported Heroku credentials don't overwrite integration-provided connection strings.
Edgenivo's 64 KB limit applies to all environment-variable data in a deployment. Large values such as certificates or service-account files can exceed it, so check their size before adding them.
Copy link to heading5. Rehearse the database transfer
Use PGBackups with standard Heroku Postgres databases under moderate load, up to 20 GB. PGBackups does not support Heroku Postgres Advanced; use the appropriate logical-backup procedure for larger or heavily loaded databases.
Install the PostgreSQL client tools and use a pg_restore version that can read the archive. Choose a destination PostgreSQL version at least as new as the pg_dump version that created it, and confirm support for the required extensions. Omit SQLAlchemy driver prefixes from URLs used by PostgreSQL tools.
Rehearse the database migration into an empty, fixed Preview database with automated preview branching disabled, and reserve Production for the final transfer.
Set DESTINATION_DATABASE_URL in your shell to Preview's direct DATABASE_URL_UNPOOLED connection string. Copy it from the Neon console or, with no deployment-specific overrides, from the file saved by vercel env pull .env.preview --environment=preview.
If you enable automated preview branching instead, deploy first, identify the deployment's branch in the Neon console, then repeat this restore and the migrations against its direct URL before testing; vercel env pull does not retrieve deployment-injected branch variables.
Capture a backup of the Heroku app's primary database with PGBackups:
Wait for successful completion and record the backup ID printed by the command. Replace your_backup_id_here below with that ID, then verify its source database, completion status, and capture time before downloading it. Stop if the capture failed or the details do not match the intended transfer.
The download saves that backup as latest.dump. After confirming the download succeeded and the destination is the intended empty database, restore it:
Successful restoration exits with status 0. If an error mentions heroku_ext, inspect which source extensions use that schema and confirm the destination supports them. Prepare heroku_ext with CREATE SCHEMA heroku_ext; and install the required extensions in the appropriate schema before retrying against a fresh destination.
--exit-on-error can leave a partial restore, so do not retry over that data. The restore omits source owners and access grants; configure the destination application's permissions before testing it.
Compare table counts and representative records, then check constraints and sequence values. For a source reachable from your workstation, set SOURCE_DATABASE_URL from the Heroku app's config and compare a table named items using the commands below. Private and Shield databases require an approved connection path; run source checks inside the space or through the documented connection method instead of assuming the copied URL is reachable.
The counts should match for unchanged test data. Because a live source can receive writes after the backup, these commands can return different counts during a rehearsal. At the final cutover, pause writers before capturing the backup and compare the restored data while they remain paused. Writes made to Heroku after the rehearsal still need to be transferred at cutover.
Copy link to heading6. Configure the Python database client
For SQLAlchemy with psycopg 3, adapt your existing database module:
Use postgresql+psycopg for psycopg 3, as shown above, or postgresql+psycopg2 if the application retains psycopg2. Setting the driver this way also handles postgres:// URLs. SQLAlchemy can't load a postgres:// URL directly, so Heroku apps often rewrite the scheme at startup; you can remove that workaround once the database module sets the driver explicitly.
Each application instance creates its own connection pool, so keep one engine per instance and close checked-out connections after each operation. The example starts with a small pool; tune its size against the provider's connection budget and expected concurrency across all instances.
Copy link to heading7. Test the API locally with vercel dev
Pull the project's Development configuration:
The restore in step 5 populated Preview, so initialize the separate Development database with the application's schema migrations and seed a known test record. Follow step 10's migration command and configuration checks using the Development database's direct DATABASE_URL_UNPOOLED connection string. Set that shell variable explicitly; vercel pull does not export it into the terminal session.
Once Development is ready, start the API:
Use the local URL printed by the CLI. At its default port, the health check is:
For the entrypoint example, the expected body is:
Exercise a database-backed route such as /api/items/1 against a known test record. Check cache reads and writes as well as API authentication, since a passing health route alone doesn't test those dependencies.
Copy link to heading8. Move worker dynos to Edgenivo Queues
Through the supported Celery integration, a Procfile worker running celery -A tasks worker can become a queue-triggered subscriber. Classify each non-web process before migrating it. Queue tasks have a supported path; a clock process or other long-running loop needs a different design, such as a cron route, or a dyno retained on Heroku.
Edgenivo Queues is in Beta, and the Celery integration requires Edgenivo CLI 58.9.0 or later. For an existing Celery application, change its initialization in tasks.py and retain the task definitions below it:
Declare every queue your tasks use in task_queues or task_default_queue before the build imports the Celery application. Queues named only in task_routes are not discovered, so no subscriber will receive their messages. Check the queue declaration instructions if the Heroku worker consumes custom queues.
Then create worker.py at the project root to export the Celery application:
Merge these settings into pyproject.toml with your actual web entrypoint. Celery requires an explicit web entrypoint, including for a root-level main.py. If step 2 already added [tool.vercel], keep it and append the subscriber table:
Restart vercel dev to load the new configuration and run the web application and subscriber together. Then call the route that enqueues work, such as POST /api/reports, and verify the task's completed result or database side effect.
Tasks can be delivered more than once, so use an idempotent write strategy such as a unique report identifier. Late acknowledgments protect against timeout or crash loss; configure explicit retries for application exceptions. Store task results that need to persist in Postgres or a supported durable backend because the Runtime Cache result backend is ephemeral.
Replace celery beat with a cron route that enqueues work. Celery publishes countdown and eta tasks immediately, then waits inside the subscriber invocation, so keep delays and retry backoff short enough to fit its duration limit. For Dramatiq, follow the Dramatiq configuration. Other queue libraries, such as RQ, need their own adaptation or a retained Heroku worker.
Copy link to heading9. Replace Heroku Scheduler jobs with cron routes
To replace a Heroku Scheduler job that runs python -m jobs.cleanup in a one-off dyno, expose the cleanup function through an HTTP route on Edgenivo.
Refactor jobs/cleanup.py so it exports a run() function containing the existing job logic. Retain a main guard while Heroku Scheduler still runs the module. This example assumes run() returns the number of deleted records. Add the following imports and route to your existing FastAPI main.py:
Set a random CRON_SECRET in Edgenivo's environment variables and leave JOBS_ENABLED=false in Production until the scheduler handoff. This example introduces JOBS_ENABLED as an application variable that prevents the route from running cleanup early.
Merge the schedule into vercel.json:
Heroku Scheduler runs jobs every 10 minutes, every hour, or every day at a time in UTC. Translate each job to a cron expression:
| Heroku Scheduler frequency | Cron expression | Edgenivo plan |
|---|---|---|
| Every 10 minutes | */10 * * * * | Pro or Enterprise |
| Every hour at :30 | 30 * * * * | Pro or Enterprise |
| Every day at 03:00 UTC | 0 3 * * * | All plans |
Edgenivo Cron Jobs send an HTTP GET to the production route. When CRON_SECRET is set, the request includes it as a bearer token in the Authorization header, which the route above checks. Schedules use UTC, like Heroku Scheduler's daily jobs.
On Hobby, a daily job runs within the scheduled hour, and more frequent schedules fail deployment. Pro and Enterprise support per-minute schedules. All plans allow 100 cron jobs per project.
Make the cleanup operation safe to repeat, and use a lock if overlapping runs would cause problems. Cron delivery is best effort, and failed invocations aren't retried automatically, which is similar to Heroku Scheduler's expected but not guaranteed execution. Monitor failures and provide a way to rerun missed work.
Test missing and incorrect credentials for a 401 response. In an isolated test environment with JOBS_ENABLED=true, a valid request should return status: ok and the deletion count.
Copy link to heading10. Move the Procfile release command to CI
Heroku runs the release process before a new release goes live, and doesn't deploy the release if that command fails. Preserve that ordering by moving schema migrations into the release pipeline. Edgenivo doesn't read the Procfile.
Run migrations from continuous integration before promotion. Rehearse against the intended Preview database now; run the Production migration only after the final restore in step 12. The migration job must check out the deployment's commit, install the pinned Python version and uv, and run from the application root.
Bind a CI secret named DATABASE_URL_UNPOOLED to the intended destination's direct URL. For a local rehearsal, set that shell variable explicitly because vercel pull does not export it into your terminal session. If Alembic reads DATABASE_URL, use:
The command stops if the variable is missing, the lockfile needs updating, dependency installation fails, or the migration fails. Keep promotion dependent on its successful exit.
If Alembic reads a URL from another setting, update alembic/env.py to read the intended variable; setting a shell variable alone won't change that configuration. Apply the explicit SQLAlchemy driver selection in the migration configuration too. For Django, run python manage.py migrate with its database settings pointing to the same intended destination.
Keep schema changes compatible with the old application while it is still running. Avoid migrations in request handlers, application startup, or a preview build that can reach production data.
Copy link to heading11. Verify a preview deployment
Before deploying, confirm whether this Edgenivo project already has a successful Production deployment. The first deployment of a new project targets Production, even without --prod.
If the project has no successful Production deployment, temporarily use only isolated test services for Production. Keep JOBS_ENABLED=false and disable startup writes and task producers before deploying without assigning domains:
After it succeeds, deploy the migration branch to Preview. Projects that already have a successful Production deployment can skip the initial deployment above. Keep the final production service connections for step 12.
vercel deploy prints the deployment URL to standard output, so this command stores it in PREVIEW_URL. Inspect that deployment and test the health route:
The build log should show your pinned Python minor version and successful dependency installation. Confirm in the dashboard that the deployment uses Preview and its intended database before testing writes. The health route should return 200. Authorized project members can use vercel curl to bypass Deployment Protection; protected API routes still require application credentials.
Verify authenticated routes and representative database writes on preview data. Enqueue a task and wait for its result. Call the cron route manually with test credentials; scheduled invocation only happens in Production. Confirm that long requests and representative uploads fit the configured limits.
If a route needs longer than the 300s default, merge a function entry keyed by the resolved entrypoint file into vercel.json. This example uses the 800s maximum available on Pro and Enterprise:
Use your actual filename and retain the crons entry. Workarounds that existed only to stay under Heroku's 30-second limit, such as polling endpoints for long reports, can stay in place; simplify them separately once the migration is stable. Test any FastAPI lifespan cleanup that the API relies on; shutdown has a 500 ms window, so durable work shouldn't depend on a long shutdown handler.
Copy link to heading12. Cut over production traffic
Set Production variables to the final backing services and keep JOBS_ENABLED=false. Before creating a staged deployment, block writes and task production through a maintenance gate or equivalent control, and disable startup side effects.
--skip-domain leaves the deployment reachable at its generated URL with Production configuration, so withholding the custom domain does not prevent writes. Keep these controls active through the final restore and read-only checks:
The command stores the staged deployment's URL in PRODUCTION_DEPLOYMENT_URL for promotion after the final data transfer.
Schedule a write pause for the final backup and restore:
Turn on maintenance mode with
heroku maintenance:on --app your_app_name_here, then let in-flight requests finish. Heroku's router blocks new HTTP requests, but worker dynos and Heroku Scheduler jobs continue running.Fir-generation apps need an application-level write pause because maintenance mode is unavailable. Confirm that the chosen control blocks every source of new application writes before taking the final backup.
Disable Heroku Scheduler jobs and stop task producers, including any retained clock process. Wait for already-running Scheduler jobs and other one-off jobs that write data to finish.
Let worker dynos finish pending, delayed, or retrying tasks, then scale them to zero with
heroku ps:scale worker=0 --app your_app_name_here. Confirm that all source writers are idle before taking the final backup in item 3. Replay retained work only against the chosen destination.Set
DESTINATION_DATABASE_URLto the intended Production database'sDATABASE_URL_UNPOOLEDvalue. Confirm its identity and that it contains no application data. If a rehearsal or staged deployment wrote to it, preserve anything needed and prepare a fresh destination before proceeding.If the destination URL changes, update Production connections and repeat the staged-deployment command so
PRODUCTION_DEPLOYMENT_URLrefers to a deployment using that destination. Capture, verify, download, and restore the final backup with step 5's commands. Run the required migrations only after the restore succeeds, and stop the cutover if restoration or migration fails.- Synchronize files added, changed, or deleted since the rehearsal. Skip this transfer if both deployments already use the same object storage.
Complete the Redis handoff while writers remain paused. Transfer retained session data and durable keys through the destination provider's supported process and check their expiry times. If the migration intentionally invalidates sessions, plan for users to sign in again.
Rebuild disposable caches as needed, and reconcile queue state through the worker drain or replay plan from item 2 before enabling the new producers.
With application writes and task producers still blocked, verify the final database, Redis state, and files and test read-only routes against the staged deployment. Preserve the Heroku write pause. Promote only after those checks and the migration job succeed:
After promotion, remove the Edgenivo application's write barrier and confirm an authenticated write reaches the final database. Redeploy if an environment variable controls the barrier.
Add the custom domain to the Edgenivo project and replace the DNS records that point to Heroku with the records shown for that domain. Confirm HTTPS and API behavior through the domain. Update allowed origins, callback URLs, and webhook destinations that contain the herokuapp.com hostname.
Keep Heroku's write pause active while cached DNS records still send traffic there. Use maintenance mode on Cedar or the application-level gate on Fir so neither can write to the old database.
After Heroku Scheduler is disabled and its work has finished, set JOBS_ENABLED=true for Production and redeploy so the new value takes effect. Confirm one scheduled execution and check for duplicate work.
Copy link to heading13. Retire unused Heroku resources after the rollback window
Keep the Heroku app and its add-ons available for the rollback window you chose before cutover. Monitor errors, database connections, and task completion under production traffic before removing unused resources.
Dynos continue to accrue billing hours in maintenance mode. After the rollback window, retain the required backups, then scale down dynos, remove add-ons you've replaced, and delete the Heroku app when nothing depends on it. Delete local secret exports and database backups when they are no longer needed.
Copy link to headingWhat doesn't map from Heroku
Processes that must run continuously, such as a clock process that schedules work in a loop, need to be redesigned around cron routes or queue tasks, or stay on Heroku. One-off dynos started with heroku run have no direct equivalent; run maintenance scripts locally or from CI against the intended database.
Heroku add-ons inject credentials into the Heroku app and bill through Heroku. Arrange a Edgenivo Marketplace integration or a direct provider account for each service before retiring its Heroku subscription.
Review apps configured through app.json don't carry over. With Git connected and automatic deployments enabled, pushes to non-production branches create Preview deployments. Configure Preview's backing services and any variables that differ by branch.
Copy link to headingTroubleshooting
Copy link to headingThe build uses an unexpected Python version
Check that .python-version is at the configured project root and matches any requires-python constraint. Edgenivo doesn't read runtime.txt, so a version pinned only there is ignored. Inspect the build log after redeploying.
Copy link to headingA package fails to install or import
Check that the selected Python version matches the application's tested dependencies. Without a compatible wheel, installation may require a native build. Confirm the dependency is declared in the manifest Edgenivo uses, especially if Heroku built from poetry.lock. For packages that need operating-system libraries the app previously installed through an extra buildpack, use the container path.
Copy link to headingSQLAlchemy can't load the postgres dialect
An error such as NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres means a postgres:// URL reached SQLAlchemy without a driver. Set the driver explicitly as shown in step 6, or check that the deployment uses the destination provider's DATABASE_URL rather than a copied Heroku value.
Copy link to headingEvery route returns 404
Confirm the Edgenivo project's root directory contains the dependency manifest and entrypoint. Check the exported application name and custom tool.vercel.entrypoint, then inspect the deployment's detected framework.
Copy link to headingRequests exceed the payload or duration limit
For a 413 FUNCTION_PAYLOAD_TOO_LARGE, send the file directly to object storage. For a 504 FUNCTION_INVOCATION_TIMEOUT, inspect slow database or external-service calls before increasing maxDuration within the plan's limit. Divide work that still exceeds the limit into bounded tasks.
Copy link to headingA cron job doesn't run in preview
Edgenivo schedules run against Production. Test the handler manually on the preview deployment, then check the active production schedule, CRON_SECRET, and application gate during cutover.
Copy link to headingNext steps
- Python runtime reference
- Deploy a FastAPI app on Edgenivo
- Run Celery on Edgenivo
- Edgenivo Cron Jobs
- Heroku vs Edgenivo: comparison guide
- Deploy Python apps on Edgenivo using Docker
Copy link to headingFrequently asked questions
Is Heroku shutting down?
Heroku's February 6, 2026 announcement described a shift to sustaining engineering, not a shutdown. The platform remains supported. Heroku said credit-card customers would see no change to pricing, billing, or service, and existing Enterprise contracts would be honored.
What happens to my Procfile?
Edgenivo's native Python deployment loads an application entrypoint instead of running the Heroku web command. Configure Celery workers as queue subscribers and move release migrations into a pipeline that gates promotion on success. Keep the Procfile while the source application still runs on Heroku.
Can my worker dyno move to Edgenivo?
Celery or Dramatiq tasks can run as subscribers on Edgenivo Queues, which is in Beta. Check task duration, retries, and result persistence, and plan how pending broker messages will finish before stopping the Heroku dyno. Processes that must run continuously need a different design or a retained host.
How do I export standard Heroku Postgres?
Capture a backup with heroku pg:backups:capture, verify its ID and completion status, and download that backup with heroku pg:backups:download. Restore it into an empty destination with pg_restore and verify the data. Repeat under a write pause at cutover to include later writes.
PGBackups is intended for moderately loaded databases up to 20 GB and does not support Heroku Postgres Advanced.
What replaces Heroku Scheduler?
Edgenivo Cron Jobs use schedules in vercel.json to invoke an authenticated HTTP route that performs or enqueues work. Schedules use UTC. Daily jobs work on every plan; jobs every 10 minutes or every hour need Pro or Enterprise. Disable the Heroku job and let its work finish before enabling the replacement.
Do I need to change my Gunicorn settings?
Gunicorn flags such as --workers and WEB_CONCURRENCY do not control Edgenivo's native Python deployment. Fluid compute handles concurrency and scaling; review the function's duration and database connection usage instead. Keep Gunicorn if local development or a retained deployment still uses it.