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.
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:
- Post-deploy command — the explicit setting on your app (UI / API). Always wins. Set it under your application's settings.
Procfilerelease line — if your repo has aProcfilewith arelease:entry (Heroku-compatible), Vessl uses it:release: php artisan migrate --forceweb: php artisan serve --host=0.0.0.0 --port=8000- 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):
| Stack | Post-deploy command |
|---|---|
| Laravel | php artisan migrate --force |
| Django | python manage.py migrate --noinput |
| Rails | bin/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
Because seeding here re-runs each time you deploy, choose the right pattern:
- Idempotent seeders (safe every deploy). Use
firstOrCreate/updateOrCreatein 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
migrationstable and run exactly once — and sincemigrateis 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.