Skip to main content

Deploying to the Cloud

We will now deploy our application to the cloud! We will use as the cloud provider since it has an excellent user experience and a free tier that is perfect for this tutorial (especially given how little memory an Exograph app needs). We will use the route of deploying a Docker image. Since that is common with many cloud providers, you can adapt this tutorial for them.

We will show two ways to use Postgres:

  • provided Postgres: This allows us to work with a single cloud provider. However, the Postgres provided by is not a managed database, which puts the onus on you to keep its image updated etc.
  • External Postgres: We will illustrate using Neon, a "multi-cloud fully managed Postgres with a generous free tier". This free tier is perfect for this tutorial. If you want to use other Postgres providers, the basic steps are the same: create a database and set the EXO_POSTGRES_URL environment variable to point to it.

Installing the prerequisites

You need to install Docker and Flyctl.

The exo deploy fly command

Exograph has a dedicated command to work with It will:

  • Create a Docker image for your application
  • Configure a fly.toml file
  • Provide deployment instructions

It offers several options to customize the deployment, and you can learn more by running exo deploy fly --help.

exo deploy fly --help
Deploy to

Usage: exo deploy fly [OPTIONS] --app <app-name> [model]

[model] The path to the Exograph model file. [default: index.exo]

-a, --app <app-name> The name of the application to deploy to
-v, --version <version> The version of application (Dockerfile will use this as tag) [default: latest]
-e, --env <env> Environment variables to pass to the application (e.g. -e KEY=VALUE). May be specified multiple times.
--env-file <env-file> Path to a file containing environment variables to pass to the application
--use-fly-db Use database provided by
-h, --help Print help

Setting up

Let's use it to deploy the concert management application we have developed. We want the app name in to be "concert-management". With that in mind, from the application's directory, we will run the following command:

We want to use the Postgres database provisioned on, hence we pass the --use-fly-db option.

exo deploy fly --app concert-management --use-fly-db

If you intend to consume the API through a web application, you would need to set CORS domains by passing -e EXO_CORS_DOMAINS=<domain> to exo deploy fly or changing the generated fly.toml file.

You will get the following output:

If you haven't already done so, run `fly auth login` to login.

To deploy the app for the first time, run:
fly apps create concert-management
fly secrets set --app concert-management EXO_JWT_SECRET=<your-jwt-secret>
fly postgres create --name concert-management-db
fly postgres attach --app concert-management concert-management-db
In a separate terminal: fly proxy 54321:5432 -a concert-management-db
exo schema create | psql postgres://concert_management:@localhost:54321/concert_management<APP_DATABASE_PASSWORD>
fly deploy --local-only

To deploy a new version of an existing app, run:
fly deploy --local-only

The command will create a fly.toml file in the current directory. You can edit it to customize the deployment. For example, you can change the number of instances, the regions, etc. See the documentation for more details.

Similarly, it will create a Dockerfile in the current directory. You can edit it to customize the Docker image. For example, you can add more dependencies, set up the timezone, etc. See the Docker documentation for more details.

Performing the deployment

Let's follow the instructions (for us, it is a first-time deployment, so the first set of commands apply).

Create the app

The first command will create the app in When presented with the option to select an organization, select an appropriate one. In our case, we will select the personal organization.

fly apps create concert-management
? Select Organization: <your account name>
New app created: concert-management

Set the JWT secret

It is a good idea to use a secret for sensitive information like the JWT secret (more specifically, you should not use an environment variable). has a feature to store secrets for an application. In the command below, replace <your-jwt-secret> with your own.

fly secrets set --app concert-management EXO_JWT_SECRET=<your-jwt-secret>
Secrets are staged for the first deployment

Create the database

Since we opted to use the database, let's create one:

fly postgres create --name concert-management-db
? Select Organization: <your account name>
Some regions require a paid plan (fra, maa).
See to set up a plan.

? Select region: San Jose, California (US) (sjc)
? Select configuration: Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
Creating postgres cluster in organization personal
Creating app...
Setting secrets on app concert-management-db...
Provisioning 1 of 1 machines with image flyio/postgres-flex:15.2@sha256:e1b0c961...
Waiting for machine to start...
Machine 3d8d9de3c13089 is created
==> Monitoring health checks
Waiting for 3d8d9de3c13089 to become healthy (started, 3/3)

Postgres cluster concert-management-db created
Username: postgres
Hostname: concert-management-db.internal
Flycast: fdaa:0:98b2:0:1::a
Proxy port: 5432
Postgres port: 5433
Connection string: postgres://postgres:<DATABASE_PASSWORD>@concert-management-db.flycast:5432

Save your credentials in a secure place -- you won't be able to see them again!

Connect to postgres
Any app within the <your account name> organization can connect to this Postgres using the above connection string

Now that you've set up Postgres, here's what you need to understand:

The DATABASE_PASSWORD part is the password for the database. As the command helpfully suggests, you should save it in a secure place.

Attach the database to the app

The next step attaches the database to the app, which creates the database instance and the user for the app.

fly postgres attach --app concert-management concert-management-db
Checking for existing attachments
Registering attachment
Creating database
Creating user

Postgres cluster concert-management-db is now attached to concert-management
The following secret was added to concert-management:

Here, too, note down the database URL and the password (the APP_PASSWORD part).

Create the database schema

We are almost there. We need to create the database schema. We will use the exo command line tool to do that. requires creating a proxy to the database (which, in turn, creates a Wireguard tunnel), to connect to the database. There are other ways to connect to the database, so please consult the documentation for more details.

In a separate terminal, run the following command:

In a separate terminal
fly proxy 54321:5432 -a concert-management-db

The proxy allows you to connect to the database on port 54321. Now, in the terminal where you deployed the app, create the database schema and populate the database:

exo schema create | psql postgres://concert_management:<APP_PASSWORD>@localhost:54321/concert_management

Deploy the app

Finally, we follow the suggested command to deploy the app:

fly deploy --local-only
==> Verifying app config
--> Verified app config
==> Building image
Searching for image 'concert-management:latest' locally...
image found: sha256:1d2a9f56b823a...
==> Pushing image to fly
The push refers to repository []
f2e0eda7d9b9: Pushed
5f70bf18a086: Pushed
a7bee79badaa: Pushed
deployment-01GXCS...: digest: sha256:1c50cc89... size: 2842
--> Pushing image done
==> Creating release
--> release v2 created

--> You can detach the terminal anytime without stopping the deployment
==> Monitoring deployment

1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully

Test the app

Since we didn't set the EXO_INTROSPECTION environment variable, it will be false by default, which is a good practice in production. This default makes the GraphQL playground unavailable with the production URL. Thanks to exo playground command, this is not a problem. Run the following command to open the playground:

exo playground --endpoint

This will print a URL to the playground. Open it in your browser. You should see the playground.

Starting playground server connected to the endpoint at:
- Playground hosted at:

Now open http://localhost:9876/playground to see the GraphiQL Playground.


Run the fly logs command to see the logs of the app. Since our tutorial also monitors the time taken by each query, you can see logs showing that. Note that the most significant factor will be the regions in which your app and database are deployed. If they are in the same region, the latency will be smaller.

Issue the following query to get the list of concerts:

query {
concerts {

You should see the following:

"data": {
"concerts": []

You can execute queries and mutations.