Running migrations on Google Cloud Run

Hi, all!

I’ve been playing with an Elixir pet project that I deployed on Google Cloud Run following @dazuma community tutorial I found on Google Cloud page (Run an Elixir Phoenix app on Compute Engine)

This tutorial is pretty neat and I managed to adapt it to Mix Release (the tutorial uses Distillery) and have it CD’d using Github Actions.

The one thing I’m not totally comfortable with is the way it handles migrations. The procedure suggested is to proxy the database connection and run the migrations locally, which seems to be a step you should automate. (It seems to be risky as well?)

So, I stumbled upon this post Migrations not being run/detected on a deployed mix release and learned you can use eval to circumvent the fact you won’t have access to Mix once the image is deployed to something like Cloud Run, but here is what I don’t get:

How do you go about running this command?
I naively tried to add it to my Dockefile on CMD and the command does run, it performs the migration, but then the deployment fails. I don’t understand Docker quite well, but it seems to be because there is only one command the image will run, yeah?

Also, I spend a lot of time looking around and I haven’t seen anyone trying to run the migration step within Dockerfile so I’m assuming there is a reason not to do it. But at the same time I don’t understand how to run eval (or access the release in any way) once the image is built.

Any ideas?

Thanks :slight_smile:

Hey @tjdam! Yeah you definitely don’t want to run migrations in the dockerfile, because the dockerfile doesn’t represent something that happens when your app runs it describes stuff that happens to build your app, and then your app is run later.

As for running migrations inside of a release, we have the following module in our app:

defmodule Sensetra.Repo.Migrator do
  use GenServer
  require Logger

  def start_link(_) do
    GenServer.start_link(__MODULE__, [], [])
  end

  def init(_) do
    migrate!()
    {:ok, nil}
  end

  def migrate! do
    path = Application.app_dir(:sensetra, "priv/repo/migrations")

    Ecto.Migrator.run(Sensetra.Repo, path, :up, all: true)
  end
end

Then, we put that in our application.ex tree:

children = [
  Sensetra.Repo,
  Sensetra.Repo.Migrator,
  # ... other children
]

This simply runs the migrations when the application tries to boot. Works great for us!

5 Likes

Ohh, that’s smart!

Can’t wait to try that! Thank you!

A question, though: how do you handle rollbacks?

1 Like

We do not rollback migrations in production, we treat that as a developer affordance. If we want to undo something we did, we make a new migration that does the desired change, and migrate forward to that.

2 Likes

Good to read that. I had an intuition that I shouldn’t rollback in production but my confidence sometimes get caught in the idea that if the tools are out there I should be using them… :sweat:

Thank you again!

I don’t think there is 1 and only 1 answer to doing database migrations in production, it depends a lot on your scale, team size, and deployment patterns. For us at least though, this pattern has worked quite well.

2 Likes

Interesting thread so far. I’m looking at moving from our GCP Kubernetes deployments to Cloud Run and am wondering how to handle DB migrations.

Currently with our kubernetes setup we have a CI deploy job that does the following:

  • builds a docker image of our Phoenix App
  • pushes it to GCR
  • applies a k8s job that runs database migrations (using the newly pushed image)
  • upon success, apply updated deployment manifest for the app (with the new image)

This works nicely as our migrations are a separate step from actually running the server. We’ve defined a “ReleaseTask” that runs the migrations and is run using eval. (e.g. path/to/app_release eval "MyApp.ReleaseTasks.migrate()")

I was wondering if a similar thing is possible with Cloud Run. I.e. can one use it to run an arbitrary command that terminates prior to rolling out a new release?

We do the migrations during app start phase as mentioned before. I cloud imagine doing them in cloud build before an actual deploy, but cloud run does not seem to support anything else.

1 Like