Deploying to the Cloud
We will now deploy our application to the cloud! We will use Fly.io 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:
- Fly.io provided Postgres: This allows us to work with a single cloud provider. However, the Postgres provided by Fly.io 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 Fly.io. 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 Fly.io
Usage: exo deploy fly [OPTIONS] --app <app-name> [model]
Arguments:
[model] The path to the Exograph model file. [default: index.exo]
Options:
-a, --app <app-name> The name of the Fly.io 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 Fly.io
-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 Fly.io to be "concert-management". With that in mind, from the application's directory, we will run the following command:
- Fly Postgres
- External Postgres
We want to use the Postgres database provisioned on Fly.io, hence we pass the --use-fly-db
option.
exo deploy fly --app concert-management --use-fly-db
exo deploy fly --app concert-management
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:
- Fly Postgres
- External Postgres
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
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 secrets set --app concert-management EXO_POSTGRES_URL=<your-postgres-url>
exo schema create | psql <your-postgres-url>
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 Fly.io 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 Fly.io. 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). Fly.io 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
- Fly Postgres
- External Postgres
Since we opted to use the Fly.io 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 https://fly.io/plans 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
Password: <DATABASE_PASSWORD>
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: https://fly.io/docs/postgres/getting-started/what-you-should-know/
The DATABASE_PASSWORD
part is the password for the database. As the command helpfully suggests, you should save it in a secure place.
Sign up unless you already have an account. Create a new project by following the instructions here.
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
- External Postgres
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:
DATABASE_URL=postgres://concert_management:<APP_PASSWORD>@concert-management-db.flycast:5432/concert_management
Here, too, note down the database URL and the password (the APP_PASSWORD
part).
You will need to set the EXO_POSTGRES_URL
environment variable to point to the database. You can get the database URL from the Neon dashboard. An example URL will look like postgres://...neon.tech/concerts-db
.
fly secrets set --app concert-management EXO_POSTGRES_URL=<your-postgres-url>
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.
- Fly Postgres
- External Postgres
Fly.io 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 Fly.io documentation for more details.
In a separate terminal, run the following command:
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
exo schema create | psql postgres://...neon.tech/neondb
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 [registry.fly.io/concert-management]
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
Logs: https://fly.io/apps/concert-management/monitoring
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 https://concert-management.fly.dev/graphql
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: https://concert-management.fly.dev/graphql
- Playground hosted at:
http://localhost:9876/playground
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 {
id
title
}
}
You should see the following:
{
"data": {
"concerts": []
}
}
You can execute queries and mutations.