On this page
- What transfers without changes
- What needs an alternative
- Pre-migration checklist
- Step 1: Export secrets (environment variables)
- Step 2: Migrate your database
- Step 3: Move Fly Volume data
- Step 4: Reuse your Dockerfile
- Step 5: Map fly.toml to Temps settings
- Step 6: Install Temps and deploy
- Step 7: Cut over DNS
- Multi-region
- Next steps
Migrate from Fly.io to Temps
To move a Fly.io app to a self-hosted Temps server: deploy the same repository (Temps builds your existing Dockerfile), copy your secrets, restore a pg_dump of your Fly Managed Postgres (or legacy Fly Postgres) into a Temps managed PostgreSQL service you link to the project, move Fly Volume files to S3 storage, map your fly.toml settings, then cut DNS over. Both platforms are container-first, so the Dockerfile carries over unchanged. The differences are the deployment model — Fly runs Machines in many regions, Temps runs on one VPS or a multi-node cluster you control — and that Temps app containers have no persistent volumes.
For a feature-by-feature comparison, see Temps vs Fly.io; for what Fly.io costs today, see Fly.io pricing.
What transfers without changes
| Feature | Fly.io | Temps |
|---|---|---|
| Dockerfile | Fly builds and runs it | Temps builds and runs it — same Dockerfile |
| HTTPS | Automatic | Same — Let's Encrypt, auto-renewed |
| Environment variables (secrets) | fly secrets set | Temps dashboard or bunx @temps-sdk/cli environments vars set |
| Health checks | [checks] in fly.toml | health.path in .temps.yaml, checked before a deploy goes live (see health checks) |
| Private networking | WireGuard mesh | Apps and managed databases share a private Docker network; connect with the variables Temps injects (such as POSTGRES_HOST). Multi-node clusters join worker nodes over a private network you provide (see multi-node) |
What needs an alternative
| Fly feature | Temps equivalent |
|---|---|
| Fly Managed Postgres, or legacy Fly Postgres | Temps managed PostgreSQL or external (Neon, Supabase) |
| Upstash for Redis on Fly | Temps managed Redis |
| Fly Volumes | Temps managed S3 storage for files, or a Docker Compose project with a named volume. App containers have no persistent volumes |
TCP/UDP services ([[services]] in fly.toml) | No direct equivalent. The Temps proxy routes HTTP and HTTPS (including WebSockets) to one port per app |
| Multi-region deployment | Multi-node Temps (advanced) |
flyctl CLI | bunx @temps-sdk/cli (the temps CLI) |
fly.toml | Temps project settings and .temps.yaml (see Step 5) |
| Machine autostop (scale to zero) | Fixed replicas per environment; on-demand mode stops idle non-production environments |
Pre-migration checklist
- SSH access to a VPS running Ubuntu 22.04+ (choosing a server)
- Domain name with DNS access
- Fly secrets exported (see Step 1)
- Fly Volumes data backed up (see Step 3)
- Postgres backup taken (see Step 2)
Step 1: Export secrets (environment variables)
# List secret names — Fly shows names and digests, never values
fly secrets list --app YOUR_APP_NAME
# Secrets are environment variables inside the running Machine,
# so you can read the values there
fly ssh console -a YOUR_APP_NAME -C "printenv"
Or take the values from where you keep them (a .env file, 1Password). Save them as a .env file and import it with bunx @temps-sdk/cli environments vars import .env -p my-app -e production.
Note which secrets reference Fly-specific infrastructure:
| Fly secret | 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 |
FLY_APP_NAME | Remove — Fly-specific |
FLY_REGION | Remove — Fly-specific |
FLY_PUBLIC_IP | Remove — replace with your VPS IP if needed |
PRIMARY_REGION | Remove — not applicable |
Step 2: Migrate your database
Export from Fly Managed Postgres
Fly Managed Postgres (MPG) clusters live on Fly's private network. Open a local proxy with fly mpg proxy, which listens on localhost:16380 by default, and use the direct connection string from the cluster's Connection tab in the Fly dashboard with the host replaced by localhost:16380 (Fly docs):
# Open a proxy to your Managed Postgres cluster
fly mpg proxy YOUR_CLUSTER_ID &
# Export the database
pg_dump "postgres://fly-user:PASSWORD@localhost:16380/fly-db" \
--no-acl \
--no-owner \
-Fc \
-f fly_backup.dump
# Close the proxy
kill %1
Legacy Fly Postgres (unmanaged) clusters
Fly has archived its unmanaged Fly Postgres and no longer supports it, but existing clusters are Fly apps you can still proxy to:
fly proxy 5433:5432 -a YOUR_POSTGRES_APP &
pg_dump "postgresql://postgres:PASSWORD@localhost:5433/YOUR_DB" \
--no-acl --no-owner -Fc -f fly_backup.dump
kill %1
fly secrets list won't show the password. Use the credentials printed when the cluster was created, or read the connection string from an app attached to it:
fly ssh console -a YOUR_APP_NAME -C "printenv DATABASE_URL"
Set Up the New Database
Option A — Temps managed PostgreSQL (recommended for simplicity). 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:
Create a database at Neon, Supabase, or any hosted provider and use its connection string as NEW_DATABASE_URL below.
Restore
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:
scp fly_backup.dump user@YOUR_VPS_IP:/tmp/
ssh user@YOUR_VPS_IP
NEW_DATABASE_URL="postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@127.0.0.1:<host-port>/<POSTGRES_DB>"
pg_restore \
--clean \
--if-exists \
--no-owner \
-d "$NEW_DATABASE_URL" \
/tmp/fly_backup.dump
Verify the restore by checking row counts on your most important tables before cutting over. 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: Move Fly Volume data
Fly Volumes are persistent disks attached to your Machines. 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 the extracted files to the bucket with any S3 client. - Docker Compose project with a named volume. If the app needs a real filesystem (SQLite, LiteFS-style setups, a search index), deploy it as a Docker Compose project and declare a named volume at the path your app uses.
Either way, copy your volume data off Fly first, while the app is still running:
# SSH into your Fly Machine
fly ssh console -a YOUR_APP_NAME
# Compress the volume data
tar -czf /tmp/volume_backup.tar.gz /path/to/your/volume
# Exit the console
exit
# Copy the archive to your local machine
fly ssh sftp get /tmp/volume_backup.tar.gz ./fly_volume_backup.tar.gz -a YOUR_APP_NAME
Then extract the archive and upload its files to the S3 bucket, or copy them into the Compose project's named volume.
Step 4: Reuse your Dockerfile
Your existing Dockerfile works on Temps without changes. Temps detects it automatically when you connect your repository.
If your fly.toml sets a custom Dockerfile path or build context, set the same values under Project → Settings → Build & deploy. There is no separate build-arguments screen: Temps passes the environment's variables to the build as --build-arg values (see build arguments), so add each build arg as an environment variable. Don't put secrets in build args; they end up in the image history.
# fly.toml — what you had
[build]
build-target = "production"
[build.args]
NODE_ENV = "production"
In Temps, add NODE_ENV=production as an environment variable. Temps has no build-target setting and builds the Dockerfile's last stage, so make the production stage the last one.
Step 5: Map fly.toml to Temps settings
| fly.toml setting | Temps equivalent |
|---|---|
internal_port | Default Port under Project → Settings → Build & deploy. Set it to the same value. Temps routes traffic to it and sets PORT to it; if unset, traffic goes to the image's EXPOSE port, then 3000 (see Ports) |
[[services.http_checks]] / [checks] | health.path in .temps.yaml, checked before a deploy goes live |
[processes] | One process per project, the image's CMD. Run extra process groups as services in a Docker Compose project |
min_machines_running | A fixed replica count per environment |
auto_stop_machines | On-demand mode for non-production environments; production stays running |
Step 6: Install Temps and deploy
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
- Temps detects your Dockerfile automatically
- 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 before cutting over DNS.
Step 7: Cut over DNS
Once your app works on the Temps preview URL:
- Go to Project → Settings → Domains → Add Domain
- Enter your domain
- Add the DNS records Temps shows you
In your DNS provider:
| Type | Name | Value |
|---|---|---|
| A | app (or @ for root) | YOUR_SERVER_IP |
Lower your DNS TTL to 60 seconds before the cutover so you can revert quickly.
Keep your Fly app running for 48 hours after cutting over DNS as a fallback.
Multi-region
Fly.io's main advantage over a single-server setup is global distribution. On Temps, apps run on a single VPS by default. Options if you need multi-region:
- CDN for static assets — put Cloudflare in front of your Temps instance for free
- Multi-node Temps — see the Multi-node guide for running Temps across multiple servers
- Keep non-latency-sensitive apps on Temps, latency-critical on Fly — Temps and Fly are not mutually exclusive
For most apps, a single well-located VPS with a CDN is sufficient.