I’ve been a huge fan of Heroku since the early days. They were true pioneers of platform as a service,
git push heroku master
was magic when it first appeared, and they made building scalable web apps and services genuinely easy at a time when the alternative was wrestling with EC2 instances and shell scripts.
A lot of us built our first production apps on Heroku, and the developer experience they created shaped how an entire generation thinks about deployment.
On February 6, 2026, Heroku announced that it is entering a sustaining engineering model. No new features, no new enterprise contracts. If you’re starting to think about what comes next, Magic Containers offers a straightforward migration path.
If you’ve been building twelve-factor apps on Heroku, environment-based config, stateless processes, backing services as attached resources, you’ll find that most of those principles translate directly to containers. The deployment model is different, but the thinking is the same.
How Heroku concepts map to Magic Containers
If you're familiar with Heroku, here's how the terminology translates:
| Heroku | Magic Containers |
|---|---|
| App | Application |
| Dyno (web/worker) | Container |
| Buildpack | Docker image |
| Add-on (e.g. Heroku Postgres) | Additional container in the same app |
Procfile
|
Container image entrypoint |
| Config Vars | Environment variables |
heroku.yml
/ Dockerfile deploy
|
Docker image from Docker Hub or GitHub |
| Dyno scaling | Autoscaling (min/max instances per region) |
| Pipeline (staging/production) | Separate applications per environment |
| Region (US/EU) | 40+ regions worldwide |
Key differences
- No buildpacks, just Docker images: Heroku uses buildpacks to detect your language and build your app automatically. Magic Containers runs standard Docker images, giving you full control over your runtime, dependencies, and build process. You can deploy any public or private image from Docker Hub or GitHub Container Registry in any language or framework.
-
Multi-container composition with persistent storage: Heroku apps typically run as a single dyno, with databases provided as separate add-ons connected over the network. Magic Containers allows multiple containers within the same application that communicate over
localhost. This lets you run your app alongside its database without an external hosted database service. Persistent volumes provide durable storage so database files, uploads, and application state survive redeployments and restarts. - Low-level networking: Heroku primarily provides HTTP routing in the US or the EU. Magic Containers supports TCP and UDP via global Anycast in addition to HTTP, enabling workloads such as DNS servers, game servers, VPN endpoints, or custom protocols.
-
No
git pushdeploys: Instead of pushing code directly, you build a Docker image locally or in CI, push it to a registry, and select it in the Magic Containers dashboard. This fits naturally into GitHub Actions or any CI/CD pipeline. - Flexible autoscaling and provisioning: Heroku restricts autoscaling mainly to web dynos and higher-tier plans. Magic Containers autoscales by default and allows customization of scaling behavior and replica counts.
Migration steps
1. Containerize your app
If you already have a Dockerfile, you're ready. If not, create one for your app. Most frameworks have well-documented Docker setups.
Here's a minimal example for a Node.js app:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
On Heroku, your Procfile might define multiple process types like web and worker. With Docker, each process type becomes its own image (or the same image with a different command). For example, a worker that processes background jobs:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["node", "worker.js"]
It’s also possible to use a single Dockerfile and override the command per container (common with Go), if that’s your thing. On Magic Containers, you'd add both as separate containers in the same application: the web container with a CDN endpoint, and the worker container with no endpoint. They share localhost, so your worker can connect to the same database and Redis instance as your web process.
2. Push your image to a registry
Build and push your image to Docker Hub or GitHub Container Registry:
docker build -t yourusername/myapp:latest .
docker push yourusername/myapp:latest
Then connect your registry in the Magic Containers dashboard under Image Registries.
3. Create your application
In the Magic Containers dashboard, click Add App and choose your deployment strategy. For stateful apps with a database, choose Single Region. For stateless apps, Magic deployment will distribute your app globally.
4. Add your containers
Add your app container, selecting the image you just pushed. Set your environment variables. These are the same config vars you had in Heroku, such as
DATABASE_URL,
SECRET_KEY,
and
PORT.
If you were using Heroku Postgres, add a PostgreSQL container in the same application. Since containers in the same app share
localhost, update your database connection to point to
localhost instead of the Heroku Postgres hostname.
5. Expose your app
Add a CDN endpoint pointing to your app's container port. This gives you a public URL with automatic HTTPS, no need to configure SSL certificates. You can also use Anycast for non-HTTP protocols like TCP or WebSocket traffic.
6. Export and import your data
Export your Heroku Postgres database:
Then restore it into your new PostgreSQL container. If your new Postgres is accessible via an Anycast endpoint, you can connect directly with
pg_restore
or
psql.
Example deployments
We have step-by-step guides for deploying popular languages, frameworks, and databases on Magic Containers. These include guides for building APIs with:
- Go
- Node.js (Express and Hono)
- Python (FastAPI)
- PHP (Slim)
If you’re looking to get started with a popular web framework, you can host those too:
And databases, standalone or as sidecars to your container apps:
Each guide shows how to configure multi-container apps with databases, persistent volumes, and CDN endpoints.
You might not need a container
Not every Heroku app needs to become a container. bunny.net offers two other products that can replace parts of your stack with less overhead.
Edge Scripting is a serverless runtime for JavaScript and TypeScript that runs across bunny.net’s network. If your Heroku app is a lightweight API, a webhook handler, or middleware layer, Edge Scripting can replace it without a container at all. It also works as DNS middleware, letting you intercept and modify requests at the edge before they reach your origin. There’s no infrastructure to manage, or Dynos to scale.
Bunny Database is a globally distributed, SQLite-compatible database. If you were using Heroku Postgres through a third-party add-on, or your app doesn’t need the full power of PostgreSQL, Bunny Database is a simpler alternative that runs close to your code across bunny.net’s network.
Getting started
Magic Containers is designed to be the kind of platform Heroku was at its best: simple to deploy to, with none of the complexity you don’t need. Full flexibility of Docker and a global edge network.
You bring a container image, set your environment variables, attach storage where you need it, and you’re running. No buildpack debugging, no add-on marketplace, no dyno sleep.
We’d love to see what you’re building. If you’re mid-migration, just getting started, or want to swap notes with others making the same move, come join us on Discord.

