Heroku Dynos and Buildpacks: How the Platform Runs Your App
Two objects explain almost everything surprising about Heroku. A buildpack turns source into a runnable slug, and a dyno is the container that runs one process. This guide walks the build stages, the dyno formation, and the constraints the platform expects you to design around.

The bargain Heroku offers
Heroku's proposition has stayed remarkably stable since it popularised the platform-as-a-service model. You hand over source code that follows a few conventions. The platform builds it, runs it, restarts it when it dies, routes traffic to it and gives you a log stream. You never see a server.
Two objects carry that bargain. A buildpack turns your source into something runnable. A dyno is the isolated container that runs one of your processes. Almost every Heroku behaviour that surprises a newcomer follows from how those two things work.
The conventions are the ones later written down as the twelve-factor methodology, which originated at Heroku: configuration in the environment, dependencies declared explicitly, processes that are stateless and disposable, and logs treated as a stream. Understanding the platform is largely a matter of understanding why it insists on those things. For the wider category this sits in, see IaaS, PaaS and SaaS compared.
What actually happens when you push
A deployment is usually triggered by git push heroku main, or by a connected repository, or by a container image push. The classic flow runs through four stages, and knowing them makes build failures far easier to read.
Detect. Each available buildpack is asked whether it recognises the source. A package.json signals Node.js, a requirements.txt or pyproject.toml signals Python, a Gemfile signals Ruby. The first buildpack that says yes takes the job, unless you have set the list explicitly.
Compile. The buildpack installs dependencies, compiles what needs compiling and runs any build step your project defines. This is where a frontend bundle is produced and where native extensions are built.
Assemble the slug. The result is packaged into a slug: a compressed archive of your application and everything it needs to run. The slug is immutable, and it is the unit that gets copied to dynos. There is an upper bound on slug size, which is why enormous asset directories and stray build caches cause deployment failures rather than slow starts.
Release. The slug is combined with your current configuration variables and add-on attachments to produce a release. If your Procfile declares a release process, it runs now, which is the conventional place for database migrations. A failed release phase stops the deployment before any new dyno serves traffic.
Only then does the dyno manager start new dynos from the release and shift traffic to them. Each release is numbered, and heroku releases:rollback returns to a previous one, which is as close to a free undo button as deployment gets.
Buildpacks in more detail
A classic buildpack is a set of scripts with a defined interface: bin/detect decides whether it applies, bin/compile does the work, and bin/release reports default process types. That simplicity is why the ecosystem grew so large, and why writing your own for an unusual runtime is achievable.
Applications frequently need more than one. A Python service that also builds a JavaScript frontend needs both buildpacks, applied in order, with the Node.js one running first so that its output is present when the Python one runs. You control the order explicitly:
heroku buildpacks:add --index 1 heroku/nodejs
heroku buildpacks:add heroku/python
Alongside the classic system, Heroku supports Cloud Native Buildpacks, the CNCF specification that produces standard OCI images rather than slugs. That path makes the same build logic usable outside Heroku, on any platform that understands container images, which is a meaningful hedge against lock-in. You can also skip buildpacks entirely and push a Docker image to the Heroku container registry when you need full control of the runtime.
A buildpack pins your application to a stack, the Ubuntu-based base image the dyno runs on. Stacks are versioned and retired on a schedule, so an occasional stack upgrade is part of owning a Heroku application. Treat it as routine maintenance rather than a migration.
Dyno types, sizes and the formation
A dyno runs one command, and which command comes from your Procfile. The process type named web is special: it is the only one the router sends HTTP traffic to, and it must bind to the port supplied in the PORT environment variable rather than a port you choose.
Everything else is a named worker. A typical file declares a few:
web: gunicorn app.wsgi --workers 3
worker: celery -A app worker --loglevel info
release: python manage.py migrate --noinput
The combination of process types and how many of each you run is called the formation, adjusted with heroku ps:scale web=3 worker=2. Scaling is horizontal by dyno count and vertical by dyno size, and the two are independent.
| Dyno category | Behaviour to know | Typical use |
|---|---|---|
| Eco | Sleeps after a period without traffic and wakes on the next request | Hobby projects and demos where a cold start is acceptable |
| Basic | Does not sleep, single dyno per process type | Small always-on services |
| Standard | Horizontal scaling, autoscaling available on higher tiers | Production web and worker processes |
| Performance | Larger dedicated allocations in several memory and CPU shapes | Memory-hungry applications and heavy background work |
| Private and Shield | Run inside a Private Space with network isolation | Regulated workloads needing stronger boundaries |
One-off dynos are the fifth kind and are easy to overlook. heroku run bash or heroku run rails console starts a temporary dyno from the current slug, which is how you inspect a production environment without keeping a shell server around.
The constraints you must design around
The filesystem is ephemeral. Each dyno gets its own writable disk that is discarded on restart, and dynos restart routinely. User uploads, generated reports and session files must go to object storage or a database, never to local disk. This is the single most common cause of mysterious data loss on the platform.
Dynos cycle. The platform restarts dynos on a regular basis, and also on deploys and configuration changes. Your process must start quickly, shut down cleanly on a termination signal, and tolerate being killed mid-task by making background work idempotent.
The router enforces a request timeout. A web request that exceeds it is terminated with an error even if your process is still working. Long operations belong in a worker dyno with the web request returning immediately, which is why the worker process type exists at all.
Memory is a quota, not a suggestion. Exceeding the dyno's memory allocation produces swapping and a distinctive warning in the logs before performance collapses. Watching for that signal is more useful than most other metrics on this platform.
Configuration lives in the environment. Config vars are set with heroku config:set and injected at dyno start, and changing one triggers a new release and a restart. Add-ons follow the same pattern, attaching their credentials as config vars automatically.
A working session with the CLI
The Heroku CLI mirrors the object model closely, and a handful of commands cover most days:
heroku create my-app --region eu
heroku config:set DATABASE_POOL=10 LOG_LEVEL=info
heroku addons:create heroku-postgresql:essential-0
git push heroku main
Then observe and adjust what is running:
heroku ps
heroku logs --tail --dyno web
heroku ps:scale web=2 worker=1
heroku releases
Pipelines and review apps extend this into a workflow where each pull request gets a temporary application built from the same app.json, and a promotion moves a tested slug from staging to production without rebuilding it. Heroku documents all of it, including the buildpack interface and the dyno lifecycle, in the Dev Center dyno reference.
Questions about how the platform runs your app
Why did my file disappear after a deploy?
Because the dyno filesystem is discarded when the dyno restarts, and a deploy replaces every dyno. Anything that must persist belongs in object storage, a database or an add-on. There is no configuration option that changes this; it is a deliberate part of the disposable process model.
Do I need a buildpack if I already have a Dockerfile?
No. You can push a container image to the Heroku container registry and run that instead, which gives you complete control over the runtime environment. You trade away the automatic dependency handling and stack maintenance that buildpacks provide, so choose it when you genuinely need the control.
What is the difference between a slug and a release?
A slug is the compiled application. A release is a slug combined with a specific set of config vars and add-on attachments. Changing a config var creates a new release without rebuilding the slug, which is why configuration changes deploy in seconds while code changes take a build.
When does it make sense to move off Heroku?
Usually when the workload needs infrastructure the platform does not expose, or when the volume of always-on dynos makes plain virtual machines more economical. The options and their costs are weighed in migrating off Heroku, alongside the Heroku and AWS comparison and the Heroku and DigitalOcean comparison. For small projects the trade against a plain server is covered in serverless versus a VPS, and the Heroku account listings describe the available options.
Questions about a cloud account?
Ask about products, variants, availability or purchasing on either channel.


