Migrate from Render to Temps
To move a Render service to a self-hosted Temps server: deploy the same Git repository, copy your environment variables, restore a pg_dump of your Render Postgres into a Temps managed PostgreSQL service you link to the project, move persistent-disk files to S3 storage, turn cron jobs into HTTP endpoints scheduled in .temps.yaml, then cut DNS over. The deployment model is the same — connect a Git repo, push to deploy, get a URL — but Temps runs on a VPS you control, and Render's disks and command-based cron jobs have no one-to-one equivalent.
For a feature-by-feature comparison, see Temps vs Render; for what Render costs today, see Render pricing 2026.
What transfers without changes
| Feature | Render | Temps |
|---|---|---|
| Web services | Auto-deploy from Git | Same — auto-deploy from Git |
| Build command | Detected or manual | Same — detected or set in project settings |
| Start command | node server.js, gunicorn, etc. | Detected, or set with a Procfile web: line or your Dockerfile's CMD |
| Environment variables | Dashboard or render.yaml | Temps dashboard or CLI |
| Health checks | /healthz path | health.path in .temps.yaml, checked before a deploy goes live (see health checks) |
| HTTPS | Automatic | Same — Let's Encrypt, auto-renewed |
| Preview environments | Pull request previews | Preview environments per branch, once enabled for the project |
What needs an alternative
| Render feature | Temps equivalent |
|---|---|
| Render Postgres | Temps managed PostgreSQL or external (Neon, Supabase) |
| Render Key Value (Redis/Valkey) | Temps managed Redis |
| Persistent Disks | Temps managed S3 storage for files, or a Docker Compose project with a named volume. App containers have no persistent volumes |
| Render Cron Jobs | Temps cron jobs: .temps.yaml entries that call an HTTP path in your app on a schedule |
| Background Workers | A worker service in a Docker Compose project. Temps has no worker process type |
| Render Static Sites | Static site deployment |
| Render Private Services | Managed databases share a private Docker network with your apps and are reached through the variables Temps injects (such as POSTGRES_HOST). Call another app by its domain, or run internal-only services in one Docker Compose project, where services without a published port stay off the proxy (see internal networking) |
Pre-migration checklist
- SSH access to a VPS running Ubuntu 22.04+ (choosing a server)
- Domain name with DNS access (or test on the
sslip.ioURL Temps gives each app) - Render environment variables exported (see Step 1)
- Database backup exported if you are migrating data
Step 1: Export environment variables
In Render, go to your service → Environment and copy all key-value pairs (and those of any environment group the service uses). The Render CLI has no command that lists environment variables; to script the export, call the Render API:
curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
https://api.render.com/v1/services/YOUR_SERVICE_ID/env-vars
Save them as a .env file and import it into Temps with bunx @temps-sdk/cli environments vars import .env -p my-app -e production, or paste them in Project → Settings → Environment Variables.
Note which variables are Render-specific and need to be changed:
| Render variable | Action |
|---|---|
DATABASE_URL | Map to the POSTGRES_URL Temps injects when you link a managed database (see Step 2) |
REDIS_URL | Remove — linking a Temps managed Redis injects its own REDIS_URL |
PORT | Remove — Temps sets it to the project's port (see Ports) |
RENDER_SERVICE_NAME | Remove — Render-specific |
RENDER_EXTERNAL_URL | Remove or replace with your Temps URL |
RENDER_INTERNAL_HOSTNAME | Remove — use the variables Temps injects for linked services (such as POSTGRES_HOST), or the other app's domain |
Step 2: Migrate your database
Export from Render Postgres
In Render, go to your database → Info → copy the External Connection String.
pg_dump "$RENDER_DATABASE_URL" \
--no-acl \
--no-owner \
-Fc \
-f render_backup.dump
Set Up the New Database
Create a managed Postgres service
- 1
In the Temps dashboard, go to Managed Services, then New Service, then PostgreSQL.
- 2
Give the service a name (e.g. my-app-db) and create it.
- 3
Link the service to your project and environment. Temps creates a database named <project>_<environment> and injects POSTGRES_URL and the other POSTGRES_* variables.
Checkpoint: Run bunx @temps-sdk/cli services env --id <service-id> -p my-app and confirm POSTGRES_URL is listed before restoring data into it.
Option A — Temps managed PostgreSQL (same server). Temps doesn't create a database on deploy: create the 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
Option B — External Postgres (Neon, Supabase, etc.): Create a database with your chosen provider and copy the connection string.
Restore Your Data
A Temps 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 the dump to the server and restore it there, using the values from services env. For an external provider, use its connection string as NEW_DATABASE_URL.
scp render_backup.dump user@your-server:/tmp/
ssh user@your-server
NEW_DATABASE_URL="postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@127.0.0.1:<host-port>/<POSTGRES_DB>"
# Test on a copy first
pg_restore \
--clean \
--if-exists \
--no-owner \
-d "$NEW_DATABASE_URL" \
/tmp/render_backup.dump
# Verify row counts match the Render database
psql "$NEW_DATABASE_URL" -c "SELECT schemaname, tablename, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC;"
Then point the app at it: change your code to read POSTGRES_URL, or set DATABASE_URL to the same value in the environment's variables.
Step 3: Handle persistent disks
Move disk files to S3 storage
- 1
Go to Services, then Create Service, and choose S3 / RustFS.
- 2
Link the new service to your project so S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET and the AWS-style aliases are injected.
- 3
Change the code that wrote to the Render disk path (e.g. /var/data) to read and write through an S3 client instead.
- 4
Upload the files you copied off the Render disk to the bucket.
Checkpoint: Run bunx @temps-sdk/cli services env --id <service-id> -p my-app to confirm the S3 variables are injected, and check that the app can read an uploaded file on its Temps URL before cutting over.
Render persistent disks mount a directory that survives deployments. Temps app containers have no persistent volumes: files written inside the container are lost on the next deploy. Choose one of:
- Object storage (recommended for uploads and user files). Create a Temps managed S3 storage service (
bunx @temps-sdk/cli services create -t s3 -n my-app-files -y) and link it to the project, which injectsS3_ENDPOINT,S3_ACCESS_KEY,S3_SECRET_KEY,S3_BUCKETand AWS-style aliases. Change the app to store files through an S3 client, then upload your existing files to the bucket with any S3 client. - Docker Compose project with a named volume. If the app needs a real filesystem (SQLite, a search index), deploy it as a Docker Compose project and declare a named volume for that path.
Copy the data off the Render disk before you cut over. Once SSH is set up for the service, use scp with the SSH address from the Render dashboard (Render recommends -s for the SFTP protocol), or magic-wormhole from the service's shell:
scp -s -r YOUR_SERVICE_SSH_ADDRESS:/var/data ./render-disk
Step 4: Set up cron jobs
Add a cron job
- 1
Move the job's logic into an HTTP endpoint in your app (e.g. /api/cron/cleanup) that checks the Authorization: Bearer CRON_SECRET header.
- 2
Add a cron entry to .temps.yaml at the project root with path: /api/cron/cleanup and schedule: 0 2 * * *.
- 3
Commit and push, then deploy. Temps reads .temps.yaml after the deployment goes live and schedules the job.
- 4
See the Cron Jobs guide for details.
Checkpoint: Run bunx @temps-sdk/cli environments crons list -p my-app -e production to confirm the cron job is registered.
A Render cron job runs a command in its own container on a schedule:
# render.yaml (what you had)
services:
- type: cron
name: daily-cleanup
schedule: "0 2 * * *"
buildCommand: npm run build
startCommand: node scripts/cleanup.js
Temps cron jobs don't run commands. They are entries in .temps.yaml that make Temps send an HTTP request to a path in your app on a schedule (in UTC), so move the script's logic into an endpoint:
# .temps.yaml (project root)
cron:
- path: "/api/cron/cleanup"
schedule: "0 2 * * *"
name: "Daily cleanup"
// Express example: the logic from scripts/cleanup.js, behind a protected endpoint
app.get("/api/cron/cleanup", async (req, res) => {
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
return res.status(401).end();
}
await cleanup();
res.json({ ok: true });
});
Temps injects a CRON_SECRET variable and sends it as a bearer token; check it so nobody else can trigger the job. Temps doesn't enforce a timeout on cron requests, so set your own inside long jobs. See the Cron Jobs guide for details.
Step 5: Install Temps and deploy
Create a project and deploy
- 1
In the Temps dashboard, go to Projects, then New Project.
- 2
Connect your Git repository.
- 3
Add your environment variables, and link the managed database so Temps injects POSTGRES_URL (or set DATABASE_URL to an external provider's connection string).
- 4
Click Deploy. Your app gets a Temps URL on sslip.io, or on your own wildcard domain if you configured one.
Checkpoint: Open the preview URL and test the app thoroughly before cutting over DNS.
Install Temps on your server:
curl -fsSL https://temps.sh/deploy.sh -o deploy.sh
less deploy.sh
bash deploy.sh
Then:
- Go to Projects → New Project in the Temps dashboard
- Connect your Git repository
- Add your environment variables, and link the managed database from Step 2
- Click Deploy
Your app gets a Temps URL on sslip.io (for example my-app.203-0-113-1.sslip.io), or on your own wildcard domain if you configured one. Test it thoroughly before cutting over DNS. Once the app is live, set up backups and monitoring to protect your data.
Step 6: Cut over DNS
Add your custom domain
- 1
In your project, go to Settings, then Domains, and click Add Domain.
- 2
Enter your domain (e.g. app.yourdomain.com). Temps shows you the DNS records to add.
- 3
At your DNS provider, add an A record for app pointing to YOUR_SERVER_IP, and lower the TTL to 60 seconds before the cutover.
Checkpoint: Run dig +short app.yourdomain.com and confirm it returns your server's IP, then confirm the domain status shows active in Temps.
- 4
Keep your Render services running for 48 hours as a fallback in case you need to revert.
Once your app works on the Temps preview URL:
- In Project → Settings → Domains, click Add Domain
- Enter your domain (e.g.
app.yourdomain.com) - Temps shows you the DNS records to add
In your DNS provider:
| Type | Name | Value |
|---|---|---|
| A | app | YOUR_SERVER_IP |
Lower your TTL to 60 seconds before the cutover so you can revert quickly if needed.
Keep your Render services running for 48 hours as a fallback. DNS changes can take up to 48 hours to propagate everywhere, though most resolvers pick up changes within minutes.
Rollback plan
If you encounter problems after cutting over:
- Update your DNS A record back to Render's IP
- Wait for propagation (fast if you lowered TTL)
- Your Render service is still running and receives traffic again