Migrate from Heroku to Temps

To move a Heroku app to Temps: deploy the same Git repository (Temps auto-detects the runtime, or uses your Dockerfile), copy your config vars, restore your Heroku Postgres backup into a Temps managed PostgreSQL service, replace add-ons with built-in Temps services, then cut DNS over. The model is the same as Heroku's — push code, the platform builds and runs it — but Temps runs only the web process and doesn't run a release phase, so migrations and workers need the changes described below.

Heroku has no free tier, and on 6 February 2026 it moved to a sustaining engineering model focused on stability and security rather than new features. For a plan-by-plan comparison, see Temps vs Heroku and the best Heroku alternatives.


Procfile → Temps

Heroku uses a Procfile to define process types:

web: node server.js
worker: node worker.js
release: node migrate.js

On Temps, only the web: line is used, as the start command. worker: and release: lines are ignored. You don't need a Dockerfile for a standard app — Temps auto-detects the runtime (see Buildpacks). Add one if you use a custom buildpack or need full control of the image:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Temps picks the container port from the project's port setting, then the image's EXPOSE, then 3000 (see Ports). Bind your app to process.env.PORT with a fallback:

const port = process.env.PORT || 3000;
app.listen(port, "0.0.0.0");

Workers

Temps has no worker process type, and a project that doesn't answer HTTP fails its health check. To run a worker next to your web process, deploy the app as a Docker Compose project with a web service and a worker service; services that don't publish a port stay off the proxy. Alternatively, start the worker inside the web container with a process manager.

Release phase and migrations

Temps has no release phase or pre-deploy command, and it does not run database migrations for you. Run them as a one-off step after the deploy — from your CI pipeline or over SSH with docker exec — as shown in Laravel migrations. Don't chain them into the start command: with rolling deploys, restarts or several replicas, they can run more than once at the same time. If your app must migrate on boot, gate it on TEMPS_REPLICA being 1 so only one replica runs it (see reserved variables).


Buildpacks

Heroku buildpacks auto-detect and configure your runtime. Temps does the same from the files in your repository: Next.js, Vite, Python, Go, Rust, Java and more are detected automatically; a plain Node.js server (Express, NestJS) uses the Node preset you pick, while Rails apps use the nixpacks-ruby preset and Laravel the nixpacks-php preset (see supported platforms).

If your app uses a custom buildpack, add a Dockerfile instead. Temps treats any repo with a Dockerfile as a Docker project.


Environment Variables

Export from Heroku CLI:

heroku config -s --app your-app-name > .env

Then add them in Temps → Project → Settings → Environment Variables, or import the file with bunx @temps-sdk/cli environments vars import .env -p my-app -e production.

Remove Heroku-injected variables before importing — they won't apply on Temps:

Heroku variableAction
DATABASE_URLMap to the POSTGRES_URL Temps injects when you link a managed database (see below)
REDIS_URLRemove — linking a Temps managed Redis injects its own REDIS_URL
HEROKU_APP_NAMERemove or replace with your own
HEROKU_RELEASE_VERSIONRemove
WEB_CONCURRENCYKeep or set it yourself if your server reads it; Temps doesn't set it
PORTRemove — Temps sets it to the project's port

Heroku Postgres Migration

Export data

# Create a backup on Heroku
heroku pg:backups:capture --app your-app-name

# Download the backup
heroku pg:backups:download --app your-app-name

This creates a latest.dump file in the current directory.

Temps doesn't create a database for you on deploy: create a PostgreSQL service, then link it to the project. Linking creates a database named <project>_<environment> and injects POSTGRES_URL, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD into that environment.

bunx @temps-sdk/cli services create -t postgres -n my-app-db -y
bunx @temps-sdk/cli services list                    # note the service ID
bunx @temps-sdk/cli services link --id <service-id> -p my-app
bunx @temps-sdk/cli services env --id <service-id> -p my-app  # POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB

Restore to your new database

The managed database isn't reachable from the internet: on the Temps server it listens on 127.0.0.1, on a host port assigned from 5432 (docker ps on the server shows it). Copy latest.dump to the server and restore it into the linked database, using the user, password and database name from services env:

scp latest.dump user@your-server:/tmp/
ssh user@your-server
pg_restore --verbose --no-acl --no-owner \
  -d "postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@127.0.0.1:<host-port>/<POSTGRES_DB>" \
  /tmp/latest.dump

Or open an SSH tunnel and restore from your machine (see Networking).

Test the restore against a scratch database first. Heroku's pg_dump uses the custom format (-Fc) which pg_restore handles, but verify row counts match before pointing your app at the new database.

Point the app at it

Your code probably reads DATABASE_URL. Either change it to read POSTGRES_URL, or set DATABASE_URL to the same value as POSTGRES_URL in the environment's variables (copy it from bunx @temps-sdk/cli services env --id <service-id> -p my-app). Then run your migrations once, as described in Release phase and migrations.


Add-on Alternatives

Heroku add-ons that are commonly used and their Temps-compatible replacements:

Heroku Add-onAlternative
Heroku PostgresTemps managed PostgreSQL, Neon, or Supabase
Heroku RedisTemps managed Redis
SendGridTemps transactional email (SES, Scaleway or an SMTP relay), or Resend or Postmark
PapertrailTemps runtime logs
New Relic / LibratoTemps monitoring, uptime checks and OpenTelemetry traces
Rollbar / BugsnagTemps error tracking (Sentry-compatible DSN)
SchedulerTemps cron jobs: .temps.yaml entries that call an HTTP path on a schedule, so a scheduled command becomes an endpoint
CloudinaryCloudflare Images or self-hosted Imgproxy

Deploy

# Install Temps if not already done
curl -fsSL https://temps.sh/deploy.sh -o deploy.sh
less deploy.sh
bash deploy.sh
  1. Go to Projects → New Project in the Temps console
  2. Connect your Git repository
  3. Add environment variables
  4. If using a Dockerfile, Temps detects it automatically; otherwise set your build and start commands
  5. Click Deploy

Custom Domains

Heroku routes traffic through yourapp.herokuapp.com. On Temps:

  1. Go to Project → Settings → Domains → Add Domain (or run bunx @temps-sdk/cli custom-domains create --project-id <id> -d app.example.com -y)
  2. Add the DNS records shown (A or CNAME)
  3. Temps provisions TLS automatically via Let's Encrypt

Remove the domain from your Heroku app once you've verified the Temps deployment is healthy.


Verify and Cut Over

  1. Test your app at the Temps preview URL end-to-end (auth, database reads/writes, background jobs)
  2. Cut DNS over to Temps
  3. Keep your Heroku app in maintenance mode (not deleted) for 48 hours as a fallback

Next Steps

Last updated

Was this page helpful?