Migrations and Seeds
A Preview Environment is only as good as the data loaded into it. It is best practice to have a fresh set of data loaded for each environment in order to produce predictable results. In some cases, this might be a static set of data defined in a seeds file. For other use cases, you might want to fetch some production data and scrub it before loading it into your Preview Environment. No matter what data you want to load, you will always want to make sure that the database schema is up-to-date.
When setting up database migrations and seeds, be sure to keep the following best practices in mind to avoid issues.
- Each database should have an owner who is responsible for maintaining the database schema and seeds. If more than one application is making changes to a database, it can cause conflicts or unpredictable results.
- If possible, use a tool that can run migrations or seeds in an idempotent manner. It should execute each update in a transaction and block any competing updates until it has finished. You don't want to have more than one process trying to update the database schema at the same time.
Nullstone provides a few different ways to run database migrations and load data into your Preview Environment. Each option along with their benefits and drawbacks are described below.
If your application is a container application, you can add an entrypoint that runs before your application boots up.
When To Use
- If you want to run migrations and seeds each time your application deploys, for each code commit and deploy.
- This is not the best option if you want to load a set of data a single time when the Preview Environment is launched.
- If the migrations or seeds fail, the application will not start; and the previous version of your application will remain running.
- It is guaranteed to run before your application starts up each time.
- It runs for every instance of your container. If you have more than one instance, the migrations and seeds scripts will attempt to run at the same time. You will need to make sure that these are idempotent and can run in parallel.
To use this option, first add an entrypoint to your Dockerfile. Below is an example of this for a Rails application.
FROM ruby:3.1.0 WORKDIR /root COPY . . RUN chmod +x ./entrypoint.sh ENV PORT 80 ENV RAILS_ENV production ENV RAILS_LOG_TO_STDOUT true RUN gem install bundler && bundle install EXPOSE 80 ENTRYPOINT ["./entrypoint.sh"] CMD ["bundle", "exec", "rails", "server"]
The key line is line 13, which specifies the entrypoint script to run.
Next, create the entrypoint script.
#!/bin/sh set -e if [ -f tmp/pids/server.pid ]; then rm tmp/pids/server.pid fi echo "Running migrations..." rails db:migrate echo "Running seeds..." rails db:seed exec "$@"