Running migrations on `fly deploy` - 3/2/2024
How you can run migrations on Fly
Intro
You just finished your app and now you want to deploy it, for the most part it should be quite easy, but if you a database with migrations to run then things can get annoying super quick. In this post i’m going to show you how to handle that when you are using Fly.io, that allows you to have a private database that is only accessible from the same network.
Summary
Setup
For our example we need:
- Fly.io account
fly cliinstalled in the machine- Working example 🔗
- Not necessary, but if you want to follow along
Running locally
To run the project locally you need to have a working postgres database as a pre requisite and https://github.com/golang-migrate/migrate to run the migrations locally.
$ set DATABASE_URL "postgres://postgres:postgres@localhost:5432/migrations?sslmode=disable"
$ migrate -source "file://db/migrations" -database $DATABASE_URL up
$ go mod download
$ go run main.go
Opening the link http:127.0.0.1:8080 will show the following screen: [image]
Creating Database on Fly
To create you will do the following:
$ fly postgres create 09:49:42
? Choose an app name (leave blank to generate one):
? Select Organization: __redacted__ (personal)
Some regions require a Launch plan or higher (bom, fra).
See https://fly.io/plans to set up a plan.
? Select region: Sao Paulo, Brazil (gru)
? Select configuration: Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
? Scale single node pg to zero after one hour? No
Creating postgres cluster in organization personal
Creating app...
Setting secrets on app holy-glade-8947...
Provisioning 1 of 1 machines with image flyio/postgres-flex:15.6@sha256:d50ff46d9a144aefa440c6c1977bded06bca4aacdeb649e17e3fd00ac1c1161a
Waiting for machine to start...
fMachine 6e82d372fd9278 is created
==> Monitoring health checks
Waiting for 6e82d372fd9278 to become healthy (started, 3/3)
Postgres cluster holy-glade-8947 created
Username: postgres
Password: password
Hostname: holy-glade-8947.internal
Flycast: __IPV6__
Proxy port: 5432
Postgres port: 5433
Connection string: postgres://postgres:[email protected]:5432
Save your credentials in a secure place -- you won't be able to see them again!
Connect to postgres
Any app within the __dedacted__ 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/
And just like that you have a running postgres app in your network. After the creation you should connect to it and create the database itself, for the following i will be using migrations as the database name.
Deploying your app to fly
With the code in hands, it’s time to deploy the project.
# --ha=false = create a single node, by default fly creates two nodes and load balances between them to have high availability
# --no-deploy = create the project but do not deploy, we still need to make changes to it
$ fly launch --ha=false --no-deploy
An existing fly.toml file was found for app fly-migrations-thrumming-glade-8989
? Would you like to copy its configuration to the new app? Yes
Scanning source code
Detected a Dockerfile app
Creating app in /Users/nyx/oss/fly-migrations
We're about to launch your app on Fly.io. Here's what you're getting:
Organization: __redacted_ (fly launch defaults to the personal org)
Name: fly-migrations-thrumming-glade-8989 (from your fly.toml)
Region: Sao Paulo, Brazil (from your fly.toml)
App Machines: shared-cpu-1x, 256MB RAM (from your fly.toml)
Postgres: <none> (not requested)
Redis: <none> (not requested)
? Do you want to tweak these settings before proceeding? No
Created app 'fly-migrations-thrumming-glade-8989' in organization 'personal'
Admin URL: https://fly.io/apps/fly-migrations-thrumming-glade-8989
Hostname: fly-migrations-thrumming-glade-8989.fly.dev
Wrote config file fly.toml
Validating /Users/nyx/oss/fly-migrations/fly.toml
✓ Configuration is valid
Your app is ready! Deploy with `flyctl deploy`
[image from fly dashboard]
After running the command you will have a updated fly.toml file with the following contents:
app = 'fly-migrations-thrumming-glade-8989' # app name
primary_region = 'gru' # primary region to be executed
[build]
[deploy]
# -- REDACTED --
[env]
PORT = '8080' # environment variable for PORT
[http_service]
internal_port = 8080 # port that is exposed by the app [app.Listen(":8080")]
force_https = true
auto_stop_machines = true # stop the machine if it does not have any traffic
auto_start_machines = true # start the machines when traffic hits the app
min_machines_running = 0 # the number of machines that will always be running
processes = ['app'] # process to be executed for the http service
# each app node will have the following specs
[[vm]]
memory = '256mb'
cpu_kind = 'shared'
cpus = 1
If you run fly deploy at this point the app will mostly build, but there is a database connection missing, to add that we do the following:
# Connection url from earlier, with the "migrations" database
$ fly secrets set "DATABASE_URL=postgres://postgres:[email protected]:5432/migrations?sslmode=disable"
Secrets are staged for the first deployment
Now if you deploy, you will be able to connect but the migrations did not run. For that we will go back to the [deploy] section from the fly.toml file:
[deploy]
release_command = "/bin/ash -c 'migrate -source file://db/migrations -database $DATABASE_URL up'"
The code above is responsible for running the migrations every time a new deploy happens. How does that work? Very simple:
[default behaviour]
[release_command behaviour]
The actual command that is executed will depend on the app and how you want to do things.
If everything did go as planned, then you should have a working app just like this
[gif]
Caveats
Running migrations without checking is quite dangerous, even if your database has something like branches. Running like that is just as dangerous as running migrations on CI/CD, always be sure that you won’t nuke your production database because you ran a command that you should not (^^). The post was just a demonstration about doing something on Fly.io, its not a recommendation nor the best way to do it.