Migrate from Heroku to your own servers.
The workflow you already use (git push, env vars, managed Postgres) maps one to one onto Girder. The only real work is moving the database and flipping DNS.
The shape of it
A Heroku app is four things: a git repo, a process listening on a port, env vars, and usually a Postgres database. Girder has a direct answer for each: a built-in git server, containers with health checks, encrypted env vars, and one-command Postgres.
Plan for zero downtime: deploy and verify everything on Girder first, then move the data in a short write window, then cut DNS. Deleting the Heroku app is the last step, not the first.
Prepare the app
Girder builds from a Dockerfile (or Nixpacks if your app has none). If your app already has a Dockerfile, you are done with this step. If it relies on Heroku buildpacks, write a small Dockerfile that installs dependencies, starts your process, and listens on the port in the PORT env var, the same contract Heroku uses.
Export your config vars while you still have the Heroku CLI handy:
$ heroku config --app myapp
Anything secret goes into Girder's encrypted env store before the first deploy, so the first boot sees it.
Deploy to Girder
$ girder apps create myapp
$ girder env set myapp SECRET_KEY_BASE=... # repeat per var
$ cd myapp
$ git remote add girder ssh://git@your-server:2222/myapp.git
$ git push girder main
The build runs on your server and streams back, exactly like a Heroku push. A failed build rejects the push. When it passes, the app is live at https://myapp.apps.example.com with a real certificate. Verify the app fully here: pages, jobs, webhooks, the lot.
Move the database
Provision Postgres on Girder, then capture and download a Heroku backup:
$ girder services add myapp postgres # on Heroku $ heroku pg:backups:capture --app myapp $ heroku pg:backups:download --app myapp # → latest.dump
Put the app in maintenance mode (or schedule a write window), then restore into the Girder database. Its DATABASE_URL is reachable from any machine on your tailnet:
$ pg_restore --no-owner --no-acl \
--dbname "$DATABASE_URL_FROM_GIRDER" latest.dump
Push once more (or restart) so the app picks up the new DATABASE_URL, and verify reads and writes against the migrated data.
Cut over DNS
- A day ahead, lower the TTL on your app's DNS records to 60 seconds so the cutover converges fast.
- Map your domain on Girder:
girder domains add myapp myapp.com. DNS and the Let's Encrypt certificate are automatic. - Verify
https://myapp.comend to end on Girder. - Take Heroku out of maintenance mode only if you rolled back; otherwise scale the Heroku app to zero and keep it around for a week as a safety net before deleting it.
What changes
You keep: git push deploys, env vars, managed Postgres, TLS, zero-downtime rollouts, and rollbacks. You gain: nightly backups with restore drills, point-in-time recovery, a Tailscale-only admin surface, and a bill that does not grow when you ship app number eleven.
You leave behind: Heroku pipelines and review apps, and the add-on marketplace. If your workflow depends on those heavily, keep them for the apps that need them. Migrations do not have to be all or nothing.
Ready when you are: pricing, the full docs, or straight to the waitlist.