> ## Documentation Index
> Fetch the complete documentation index at: https://docs.localops.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Fly.io

> Step-by-step guide to migrate your Fly.io application and Postgres database to AWS using LocalOps

<div style={{ position: "relative", paddingBottom: "66.58%", height: 0 }}>
  <iframe
    src="https://www.loom.com/embed/0bf8ad4510bf401aa429924ad2364a51"
    frameBorder="0"
    allowFullScreen
    style={{
  position: "absolute",
  top: 0,
  left: 0,
  width: "100%",
  height: "100%",
}}
  />
</div>

Migrating from Fly.io to AWS gives you access to the full AWS ecosystem, better regional availability, and predictable
pricing. LocalOps provides a similar developer experience with git-push deployments and automatic scaling.

<Tip>
  **White-glove migration**: Our engineers will migrate your Fly.io app to AWS.
  [Schedule a migration call](https://go.localops.co/fly) and we'll handle
  everything for you.
</Tip>

## What you get after migration

* **Same developer experience**: Push to deploy, just like Fly.io
* **Predictable costs**: No surprise bills—AWS pricing you control
* **Full AWS access**: Use any AWS service (RDS, ElastiCache, S3, SQS, etc.)
* **Production-ready**: Auto-scaling, auto-healing, monitoring, and CI/CD out of the box
* **Built-in observability**: Open-source stack with Prometheus, Loki, and Grafana—no extra cost
* **No vendor lock-in**: Your code runs on standard Kubernetes in your own AWS account

## Migration overview

<Steps>
  <Step title="Set up LocalOps environment" icon="cloud">
    Connect your AWS account and create a new environment for your app.
  </Step>

  <Step title="Deploy your application" icon="rocket">
    Connect your GitHub repo and deploy your app to LocalOps.
  </Step>

  <Step title="Migrate Postgres database" icon="database">
    Export your Fly Postgres data and import it into Amazon RDS.
  </Step>

  <Step title="Update DNS and go live" icon="globe">
    Point your domain to the new environment and verify everything works.
  </Step>
</Steps>

<Info>
  Need help? [Schedule a migration call](https://go.localops.co/fly) and our
  engineers will assist you through the entire process.
</Info>

## Step 1: Set up LocalOps environment

Before migrating, you need a LocalOps environment running on AWS.

<Steps>
  <Step title="Create LocalOps account" icon="user">
    [Sign up for LocalOps](https://console.localops.co/signup) if you haven't
    already.
  </Step>

  <Step title="Connect AWS account" icon="cloud">
    Follow the [AWS connection guide](/accounts/aws) to connect your AWS
    account.
  </Step>

  <Step title="Create environment" icon="container-storage">
    Create a new environment (e.g., production) in your preferred AWS region.
    See [Create new environment](/environment/create).
  </Step>

  <Step title="Create service" icon="server">
    Create a new service and connect your GitHub repository. See [Create new
    service](/environment/services/create).
  </Step>
</Steps>

Once your environment is ready, note down the **VPC ID** and **Private Subnet IDs** from the environment overview page.
You'll need these to create your RDS database.

## Step 2: Prepare your application

### Dockerfile-based apps

If you have a Dockerfile, LocalOps will use it automatically. No changes needed.

### fly.toml configuration

Your `fly.toml` configuration maps to LocalOps concepts:

| fly.toml | LocalOps Equivalent |
| - | - |
| `[http_service]` | [Web service](/environment/services/web) |
| `[processes]` | [Workers](/environment/services/workers) |
| `[[services]]` | [Internal service](/environment/services/internal) |
| `[env]` | Environment variables in service settings |
| `[[vm]]` size | Resource configuration in service settings |

### Health checks

Convert Fly.io health checks to LocalOps format in `ops.json`:

```json theme={null}
{
  "healthchecks": [
    {
      "type": "http",
      "path": "/health",
      "port": 8080,
      "interval": 30,
      "timeout": 5
    }
  ]
}
```

See [ops.json documentation](/environment/services/ops-json) for all health check options.

### Clustered Elixir/Erlang apps

On Fly.io, Elixir apps usually cluster over the private `.internal` network with `dns_cluster` or libcluster. On
LocalOps, add `beam_cluster_id` to `ops.json` and point libcluster at the injected `LOPS_BEAM_CLUSTER_DNS`. If your Fly
app clustered across regions, you can do the same with one environment per region connected by VPC peering. See
[BEAM clustering](/environment/services/beam-clustering) for both setups.

## Step 3: Migrate Fly Postgres to Amazon RDS

### 3.1 Create a backup of your Fly Postgres database

Use `flyctl` to connect to your Postgres cluster and create a backup:

```bash theme={null}
# Connect to your Fly Postgres app
flyctl proxy 5432 -a your-postgres-app-name

# In another terminal, create a backup
pg_dump "postgres://postgres:your-password@localhost:5432/your-database" \
  --format=custom \
  --no-owner \
  --no-acl \
  --file=fly_backup.dump
```

Alternatively, use `flyctl ssh console` to access the database directly:

```bash theme={null}
# SSH into the Postgres machine
flyctl ssh console -a your-postgres-app-name

# Create backup inside the machine
pg_dump -U postgres -d your-database -F c -f /tmp/backup.dump

# Copy backup to local machine (from another terminal)
flyctl sftp get /tmp/backup.dump -a your-postgres-app-name
```

<Warning>
  For large databases, the backup and restore process may take significant time.
  Plan for a maintenance window if you need zero data loss during migration.
</Warning>

### 3.2 Create RDS database in your LocalOps environment VPC

#### Option A: Using ops.json (Recommended)

Add an `ops.json` file to the root of your repository:

```json theme={null}
{
  "dependencies": {
    "rds": {
      "instances": [
        {
          "id": "main-db",
          "prefix": "myapp",
          "engine": "postgres",
          "version": "16.4",
          "storage_gb": 20,
          "instance_type": "db.t4g.small",
          "publicly_accessible": false,
          "exports": {
            "DATABASE_URL": "$dsn",
            "DATABASE_HOST": "$address",
            "DATABASE_NAME": "$dbName",
            "DATABASE_USER": "$username",
            "DATABASE_PASSWORD_ARN": "$passwordArn"
          }
        }
      ]
    }
  }
}
```

Deploy your service to provision the RDS instance automatically. See [RDS documentation](/environment/services/aws/rds)
for all configuration options.

#### Option B: Manual RDS creation

1. **Login to AWS Console** in the same region as your LocalOps environment

2. **Create a DB Subnet Group**:
   * Navigate to RDS > Subnet groups > Create DB subnet group
   * Select the VPC ID from your LocalOps environment
   * Add the private subnet IDs from your LocalOps environment

3. **Create RDS Instance**:
   * Navigate to RDS > Create database
   * Choose PostgreSQL and select the same major version as your Fly database
   * Select the DB subnet group you created
   * Set `Publicly accessible` to **No**
   * Create a security group allowing port 5432 from `10.0.0.0/16`

### 3.3 Restore backup to Amazon RDS

```bash theme={null}
# From an EC2 instance in the same VPC
sudo dnf install postgresql16 -y  # Amazon Linux 2023

# Upload backup
scp fly_backup.dump ec2-user@your-ec2-ip:~/

# Restore
pg_restore --verbose --no-owner --no-acl \
  -h your-rds-endpoint.rds.amazonaws.com \
  -U your-db-username \
  -d your-database-name \
  fly_backup.dump
```

### 3.4 Configure database credentials

If you manually created the RDS instance, add credentials as secrets:

1. Navigate to your service in the LocalOps console
2. Go to Settings > Secrets
3. Add the following secrets:

```
DATABASE_HOST=your-rds-endpoint.rds.amazonaws.com
DATABASE_NAME=your-database-name
DATABASE_USER=your-db-username
DATABASE_PASSWORD=your-db-password
```

<Tip>
  If you used `ops.json` to create RDS, there is nothing to add here: the `$dsn`
  export gives your service `DATABASE_URL`, with the password already filled in.
</Tip>

## Step 4: Migrate Fly Redis (Upstash) to ElastiCache

If you're using Upstash Redis on Fly.io, migrate to Amazon ElastiCache:

```json theme={null}
{
  "dependencies": {
    "elasticache": {
      "clusters": [
        {
          "id": "cache",
          "prefix": "myapp",
          "engine": "redis",
          "node_type": "cache.t4g.micro",
          "exports": {
            "REDIS_HOST": "$address",
            "REDIS_PORT": "$port"
          }
        }
      ]
    }
  }
}
```

Update your connection code:

```javascript theme={null}
// Before (Upstash on Fly.io)
import { Redis } from "@upstash/redis";
const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

// After (ElastiCache)
import Redis from "ioredis";
const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT,
});
```

See [ElastiCache documentation](/environment/services/aws/elasti-cache) for more details.

## Step 5: Migrate environment variables and secrets

Export secrets from Fly.io and add them to LocalOps:

```bash theme={null}
# List all secrets (names only)
flyctl secrets list -a your-app-name
```

For each secret:

1. Get the value from your local environment or source
2. In LocalOps console, navigate to your service > Settings > Secrets
3. Add each variable

See [Secrets documentation](/environment/services/secrets) for more details.

## Step 6: Update your application

If you created RDS with `ops.json`, your application keeps reading `DATABASE_URL`, just as it did on Fly.io - no code
change. The `$dsn` export fills it in with a complete connection URL, password included and already escaped.

If you created the database manually, build the URL from the secrets you added in step 3.4 instead:

```javascript theme={null}
const connectionString = `postgresql://${process.env.DATABASE_USER}:${encodeURIComponent(process.env.DATABASE_PASSWORD)}@${process.env.DATABASE_HOST}/${process.env.DATABASE_NAME}`;
```

## Step 7: Deploy and verify

1. Push your changes to trigger a deployment
2. Check logs in the LocalOps console to verify the application starts correctly
3. Test your application endpoints
4. Update your DNS to point to the new LocalOps environment

See [Custom domain setup](/environment/services/custom-domain) for DNS configuration.

## Built-in observability

Every LocalOps environment comes with a fully integrated open-source observability stack—no extra add-ons required.

### Prometheus + Grafana for metrics

[Prometheus](https://prometheus.io/) automatically collects CPU, memory, disk, and network metrics from every node
running your application. View and analyze metrics through pre-built [Grafana](https://grafana.com/oss/) dashboards,
accessible from the Monitoring tab in your environment.

You can filter and group metrics by:

* Node
* Pod
* Deployment
* Service
* Namespace

### Loki + Grafana for logs

[Loki](https://grafana.com/oss/loki/) automatically collects all logs from STDOUT and STDERR across your services. No
log drain configuration needed—just print to console and your logs are captured.

Access logs through the same Grafana dashboard, with powerful filtering by Kubernetes namespace, deployment, or custom
labels.

### Custom dashboards

Each environment gets its own Grafana instance with pre-built dashboards for infrastructure monitoring. You can create
custom dashboards to visualize application-specific metrics and logs.

<Info>
  Learn more about [logs](/environment/monitoring/logs),
  [metrics](/environment/monitoring/metrics), and
  [alerts](/environment/monitoring/alerts).
</Info>

## Migrating Fly.io features

| Fly.io Feature | LocalOps Equivalent |
| - | - |
| Machines (web) | [Web service](/environment/services/web) |
| Machines (worker) | [Workers](/environment/services/workers) |
| Fly Postgres | [Amazon RDS](/environment/services/aws/rds) |
| Upstash Redis | [Amazon ElastiCache](/environment/services/aws/elasti-cache) |
| Tigris Object Storage | [Amazon S3](/environment/services/aws/s3) |
| Elixir clustering | [BEAM clustering](/environment/services/beam-clustering) |
| Fly Metrics | [Built-in metrics](/environment/monitoring/metrics) |
| Fly Logs | [Built-in logging](/environment/monitoring/logs) |

## Fly CLI equivalents

The [LocalOps CLI](/cli/install-macos) covers the `flyctl` commands you probably ran most often:

| Fly CLI | LocalOps CLI |
| - | - |
| `fly logs` | [`ops logs`](/cli/usage#stream-logs-from-a-service) |
| `fly ssh console` | [`ops connect`](/cli/usage#shell-into-a-service) |
| `fly proxy` | [`ops proxy`](/cli/usage#forward-a-service-port-to-localhost) |
| `fly postgres connect` | [`ops connect --dep`](/cli/usage#open-a-database-shell) |
| `fly redis connect` | [`ops connect --dep`](/cli/usage#open-a-database-shell) |

```sh theme={null}
ops logs --env production --svc web
ops connect --env production --svc web
ops connect --env production --svc web --dep main-db
```

Both take the environment and service names you see in your dashboard, so there is no machine id to look up first. When
a service runs several pods, `ops logs` reads all of them and prefixes each line with the pod it came from.

## Get help with your migration

<Tip>
  **White-glove migration**: Don't want to do this yourself? Our engineers will
  migrate your entire Fly.io setup to AWS—including database migration,
  environment variables, and custom domains. [Schedule a migration call
  now](https://go.localops.co/fly).
</Tip>

Have questions? Email us at [support@localops.co](mailto:support@localops.co).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.