---
id: fly-io
slug: /guides/deploying/fly-io
title: Using Atlas with Fly.io
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

[Fly.io](https://fly.io/) is a platform for running full stack apps and databases close to the users. Under the hood, Fly
converts Docker images (or other OCI-compliant image formats) to [Firecracker microVMs](https://firecracker-microvm.github.io/). Fly allows
the deployment of the app in different regions and route the requests based on the [app load and user closeness](https://fly.io/docs/reference/load-balancing/).

Apps on Fly can be [deployed in one of three ways](https://fly.io/docs/reference/builders/): using a Dockerfile, a
Docker image or a [buildpack](https://buildpacks.io/).

In this guide, we will demonstrate how Atlas can be used to perform database schema migrations for Fly.io deployments process using a Dockerfile.
We will assume that you have already have a Fly project and are able to deploy it, if you are new to Fly, [check the getting started guide](https://fly.io/docs/speedrun/).

## Release command

When you configure your Fly project, you can define a _release command_. This command is executed during the release phase before the new version of your application is deployed.  If it fails, i.e exits with a status code other than zero, Fly marks the deployment as failed as well. The release command is the
[recommended way](https://fly.io/docs/reference/configuration/#the-deploy-section) to run database migrations on Fly.

:::note
The release command only allows executing commands that are present in the application image, meaning that we
have to embed the Atlas binary with our application. [We usually suggest a separate step for handling the
migration](https://atlasgo.io/guides/deploying/intro#running-migrations-as-part-of-deployment-pipelines),
but since Fly currently does not support providing a separate image for the release phase, we recommend this solution.
:::

## Defining the Dockerfile

Using Docker multi-stage builds, we can compose lightweight images from multiple steps that use heavier base images. In this guide, we will use a Go app as an example. Because Go is a compiled language, so we can use a separate step for building the target binary and another for producing the runtime container. This way the runtime environment can be smaller, omitting the build environment.

Suppose our project structure is similar to the one below:

```text
.
├── fly.toml
├── go.mod
├── go.sum
├── main.go
└── migrations
    ├── 20221220000101_create_users.sql
    └── atlas.sum
```

Our objective is to build an image that contains the Atlas binary, the database migrations and our application code. For
our Go app the Dockerfile can be defined as:

```dockerfile title="Dockerfile"
FROM arigaio/atlas:latest-alpine as atlas

# build stage
FROM golang:1.19.2-bullseye as build
WORKDIR /build
ADD go.mod /build/go.mod
ADD go.sum /build/go.sum
ADD main.go /build/main.go
RUN CGO_ENABLED=0 go build -o app main.go

# runtime stage
FROM alpine
COPY --from=atlas /atlas /atlas
COPY migrations /migrations

COPY --from=build /build/app /app
CMD ["/app"]
```

If you are using another compiled programming language, most of the time you will only have to change the build stage.
If your application requires a runtime you may have to change the final stage as well.

:::info
It's important for the final image to have a shell that is capable of environment variable interpolation, like `sh` or `bash`. This is mostly
due to [behavior on Fly's side](https://community.fly.io/t/using-env-variables-in-release-command/7186),
where expanding environment variables don't currently work correctly on the release command.
:::

## Setting the database URL secret

While running the migration, Atlas needs to know the URL for the database. Fly has support for
defining [secrets](https://fly.io/docs/reference/secrets/), sensitive values that are available during runtime as
environment variables. We can define the database URL using the command below:

```
flyctl secrets set DATABASE_URL="postgres://postgres:pass@0.0.0.0:5432/database?sslmode=disable"
```

If you use the **flyctl** command `postgres attach` the
secret [will be created automatically for you](https://fly.io/docs/postgres/managing/attach-detach/#attach-a-fly-app).

## Configuring `fly.toml`

To tell Fly to execute the release command during a deployment, we need to add a `deploy` block with the release
command provided:

```toml title="fly.toml"
[deploy]
release_command = "sh -c '/atlas migrate apply --url $DATABASE_URL'"
```

With the release command defined, during new deployments a new temporary VM will be created and will execute the release
command. If the commands succeed the deployment will continue, in case of failures the deployment will be aborted.

## Deploying the app

We can deploy the app with the command `flyctl deploy`. Fly will use a Docker installation (or a remote builder) to
build the Docker image and push to the Fly registry.

The output of the release command will be presented to you on your terminal, but if you missed it, you can use the
Monitoring page of your app or the `fly logs` command to see the previous logs entries.

Atlas will provide helpful information during the execution, here are a few examples of logs outputs:

<Tabs
defaultValue="first_deploy"
values={[
{label: 'First deploy', value: 'first_deploy'},
{label: 'Second deploy', value: 'second_deploy'},
{label: 'Migration error', value: 'checksum_mismatch'},
]}>
<TabItem value="first_deploy">

```text
Preparing to run: `/atlas migrate apply --env prod` as root
Migrating to version 20221220000101 (1 migrations in total):
migrating version 20221220000101
-> CREATE TABLE "public"."users" ("id" integer NOT NULL, "name" character varying(100) NULL, PRIMARY KEY ("id"));
-- ok (6.204945ms)
 -------------------------
-- 12.69747ms
-- 1 migrations
-- 1 sql statements
```

</TabItem>
<TabItem value="second_deploy">

```text
Preparing to run: `/atlas migrate apply --env prod` as root
No migration files to execute
```

</TabItem>
<TabItem value="checksum_mismatch">

```text
Preparing to run: `/atlas migrate apply --env prod` as root
You have a checksum error in your migration directory.
This happens if you manually create or edit a migration file.
Please check your migration files and run
'atlas migrate hash'
to re-hash the contents and resolve the error
Error: checksum mismatch
Error release command failed, deployment aborted
```

</TabItem>
</Tabs>

## Improving the deployment pipeline

You can always improve the deployment pipeline by leveraging [Atlas](https://atlasgo.io/integrations/github-actions)
and [Fly](https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/) GitHub Actions. For additional
insights on the database schema and migrations, we recommend giving [Atlas Cloud](https://atlasgo.io/cloud/getting-started) a try.
