Move from Heroku to Europe.
run.zero works like Heroku, with buildpacks, a Procfile and DATABASE_URL, on servers in Germany. The only new file is a run-zero.yaml, but your data does not move by itself.
Needs the Developer plan, the free plan only covers websites. Pricing · Check the limits first
ProcfilestaysDATABASE_URLstays- Servers in Nuremberg
> Deploy the heroku-migration folder to run.zero.
runzero_create_appdoneexample-heroku
runzero_push_filesdoneone commit
runzero_deploydoneservices and Postgres created
runzero_wait_for_builddonelive
live at https://web-example-heroku-b24cc7f1.run-zero.de
- Live demoRunning on run.zero(opens in a new tab)
- Example codeMIT, on GitHub(opens in a new tab)
- BuildpacksNode, Python, Go, PHP, Java
- Hosted in GermanyHetzner, Nuremberg
Your app in four steps.
Tested with the Flask app that runs live.
- 01
Check the Procfile
The
webline must listen on$PORT, just like on Heroku. It stays as it is. - 02
Translate the addons
Heroku Postgres becomes the Postgres addon. The platform sets
DATABASE_URL, so no code changes.run-zero.yaml version: "1"services: web: port: 8080addons: db: type: postgres attachTo: [web] - 03
Copy your config vars
Set the values from
heroku configas secrets in the dashboard or withrunzero_set_secrets. The service restarts afterwards. - 04
Create an account and deploy
Create an account, connect your agent with this command, then sign in from Claude Code with
/mcpand Authenticate.- Then tell your agent: deploy this repo to run.zero.
Without an agent
Create the app in the dashboard and connect your GitHub repo. Every push rebuilds.
Connect Claude Code claude mcp add --transport http \ -s user run-zero \ https://api.run-zero.de/mcp \ --client-id runzero-cli \ --callback-port 53761
After 1 to 3 minutes the app is live at https://web-<app>.run-zero.de. Check a route of your app, in the example /api/health. If the build is red, runzero_get_build_logs tells you why.
Moving data: these steps bring your app, not your data. There is no dump import, single SQL statements go through the SQL console or runzero_db_query. For a large database, this is not a finished migration today.
A typical Heroku app.
Flask, gunicorn and Postgres, only the config is new.
The same web line as on Heroku.
web: gunicorn app:app --bind 0.0.0.0:$PORTCounts visits in Postgres through DATABASE_URL.
# A typical Heroku app: Flask, gunicorn from the Procfile, Postgres from DATABASE_URL.import osimport psycopgfrom flask import Flask, jsonifyapp = Flask(__name__)DATABASE_URL = os.environ.get("DATABASE_URL")def count_visit() -> int: # run.zero injects DATABASE_URL with sslmode=require, psycopg accepts it as is. with psycopg.connect(DATABASE_URL) as conn: conn.execute("CREATE TABLE IF NOT EXISTS visits (id serial PRIMARY KEY, at timestamptz DEFAULT now())") conn.execute("INSERT INTO visits DEFAULT VALUES") return conn.execute("SELECT count(*) FROM visits").fetchone()[0]@app.get("/")def index(): visits = count_visit() if DATABASE_URL else "no database attached" return f"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>From Heroku to run.zero</title></head><body style="font-family:system-ui,sans-serif;max-width:640px;margin:64px auto;padding:0 24px"><h1>From Heroku to run.zero</h1><p>Same Procfile, same DATABASE_URL. Visits stored in Postgres: <strong id="visits">{visits}</strong></p><p><a href="/api/health">/api/health</a></p></body></html>"""@app.get("/api/health")def health(): return jsonify(status="ok", database=bool(DATABASE_URL), platform="run.zero")flask, gunicorn and psycopg.
flask==3.1.0gunicorn==23.0.0psycopg[binary]==3.2.3Heroku concepts on run.zero.
What stays and what has another name.
| Heroku | run.zero |
|---|---|
| Buildpacks | Paketo buildpacks for Node, Python, Go, PHP, Java |
| Procfile | Procfile, unchanged |
| Dyno | Service with 0 to 10 replicas, depending on the plan |
| Heroku Postgres | Postgres addon with DATABASE_URL |
| Config vars | Secrets on the service or the app |
| git push heroku main | Push to the connected repo, or runzero_push_files |
| app.json, pipelines | run-zero.yaml, preview addresses for changes |
What does not work today.
So the move holds no surprises.
Needs the Developer plan
Free has no server apps and no Postgres, a deploy stops with an error.
See pricingAddons: Postgres and Keycloak
Heroku Redis, Kafka or marketplace addons do not exist here.
No dump import
There is no import for a dump. Small datasets go in as SQL through the SQL console or
runzero_db_query.No app.json
Describe services, ports and addons in
run-zero.yaml.
What you stop doing yourself.
Builds, HTTPS, the database and backups run on the platform.
Data stays in Germany
Servers and backups at Hetzner in Nuremberg. With a data processing agreement.
HTTPS by default
Every service gets an address on
run-zero.de, custom domains a Let’s Encrypt certificate.Postgres as an addon
One entry in
run-zero.yaml,DATABASE_URLarrives as an environment variable.No lock-in
Your code stays in your repo, the database is a dump you can download.
Questions.
About the move.
Do I have to change my code?
Usually not. Procfile, $PORT and DATABASE_URL work as on Heroku. Only check the addons you use.
Does DATABASE_URL work with SSL?
Yes. The URL carries sslmode=require against a self-signed certificate. psycopg accepts it as is, node-postgres needs ssl: { rejectUnauthorized: false } and a URL without sslmode.
Is there a replacement for Heroku Scheduler?
We have not tested one, the example only shows the web process.
Can I leave again?
Yes. The code lives in your repo, and the dashboard offers the database as a pg_dump download.
Where do the servers run?
At Hetzner in Nuremberg, backups included. With a data processing agreement.