How to Run Rails Database Migration Tasks on ECS

When you have a Ruby on Rails app, you need to run database migrations occasioanlly. One way to do this is to deploy the code to production and run the migration manually. In this post, I'll show you the script I wrote to start up an ECS task that will run the migration for me.

I have basic Dockerfile that builds my Rails app:

FROM ruby:2.3

RUN apt-get update && \
  apt-get install -qq -y --no-install-recommends \
    cron && \
  rm -rf /var/lib/apt/lists/*

ENV APP_HOME /usr/src/app
ENV RAILS_LOG_TO_STDOUT true
ENV RAILS_ENV production

RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile $APP_HOME/Gemfile
COPY Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install

COPY . $APP_HOME

RUN bundle exec rake assets:clobber assets:precompile

EXPOSE 4000
CMD bundle exec rails s -p 4000 -b '0.0.0.0'

To get this Docker container to run a migration, we just have to change the CMD to bundle exec rake db:migrate. That's easy with the script I made below:

#!/usr/bin/env ruby

require 'json'

# These should change from project to project
TASK_FAMILY = "barstack-worker".freeze
CONTAINER_NAME = "barstack-worker".freeze

# These probably won't change from project to project
CLUSTER = "kurttomlinson-cluster".freeze

overrides = {
  containerOverrides: [{
    name: CONTAINER_NAME,
    command: ["bundle", "exec", "rake", "db:migrate"]
  }]
}

migration_command = "aws ecs run-task " +
                    "--started-by db-migrate " +
                    "--cluster #{CLUSTER} " +
                    "--task-definition #{TASK_FAMILY} " +
                    "--overrides '#{overrides.to_json}' "

puts migration_command

puts ""

puts `#{migration_command}`

This script can be easily reused by other projects. All that needs to be done is replacing the constants at the top of the file with the appropriate values for your project.

(Before running this script, you must create a Task Definition in ECS that uses your app's Docker image.)

What this script does is run the task that matches the TASK_FAMILY variable and replaces the container's command with ["bundle", "exec", "rake", "db:migrate"]. It also sets the task's started-by to "db-migrate" so it's easy to find in your list of running tasks.

The ECS task that I use for migrations pulls double duty: normally it's used to process my background jobs, and this script repurposes it to migrate the database by overriding the container's Docker command.

Do you have a great way of doing deploys/migrations? Let me know in the comments!

Photo by chuttersnap