Skip to main content

Database migrations & seeding

Vessl runs a release phase (also called the post-deploy command) on every deploy — once, after the build and before traffic switches to the new version. This is where database migrations and seeders belong.

Don't put migrations or seeders in your start command

Your start command is the long-running process that serves the app (e.g. php artisan serve, node server.js, gunicorn). It runs every time a container starts — on every deploy, restart, crash-recovery, and scale-up. If you migrate or seed there you'll re-run it constantly and can duplicate or overwrite data. Use the post-deploy command instead.

How the release command is resolved

Vessl picks the release command from the first of these that applies:

  1. Post-deploy command — the explicit setting on your app (UI / API). Always wins. Set it under your application's settings.
  2. Procfile release line — if your repo has a Procfile with a release: entry (Heroku-compatible), Vessl uses it:
    release: php artisan migrate --force
    web: php artisan serve --host=0.0.0.0 --port=8000
  3. Stack auto-detection — for known stacks (Laravel, Django, Rails) Vessl applies a sensible default (e.g. php artisan migrate --force) and tells you in the deploy logs which default it used.

If none apply, the release phase is skipped.

Migrations

For most apps, migrations are already handled by the auto-detected default (or a one-line post-deploy command):

StackPost-deploy command
Laravelphp artisan migrate --force
Djangopython manage.py migrate --noinput
Railsbin/rails db:migrate

Seeding

Set the post-deploy command to run your seeder after migrating. Laravel example:

php artisan migrate --force && php artisan db:seed --force
The release command runs on EVERY deploy

Because seeding here re-runs each time you deploy, choose the right pattern:

  • Idempotent seeders (safe every deploy). Use firstOrCreate / updateOrCreate in your seeders so re-running can't duplicate rows. Best for reference data (roles, plans, settings).
  • One-time seed (initial data). Put it in a migration instead of a seeder. Migrations are recorded in the migrations table and run exactly once — and since migrate is already the default release command, it just works. (Alternatively: set the post-deploy command to seed, deploy once, then remove it.)

Example — idempotent Laravel seeder

// database/seeders/RoleSeeder.php
foreach (['owner', 'admin', 'member'] as $name) {
Role::firstOrCreate(['name' => $name]); // safe to run on every deploy
}

Heads-up

  • The release command runs before the new version takes traffic. If it fails (e.g. a bad migration), the deploy fails and your previous version keeps serving — no half-migrated downtime.
  • Migrations/seeders run in the app's environment, so they use the same database connection and env vars (DATABASE_URL, DB_*) Vessl injects for your add-ons.
  • Need a true one-off command outside a deploy (a manual re-seed, a quick tinker)? That's on the roadmap — for now, use a migration or a temporary post-deploy command as above.