skip to content

Why does a Laravel seeder built on model factories fail or stall during a production deploy, and what must change to seed there safely?

level: seniorimportance: should knowfreq 32%

answer

  1. fakerphp/faker sits in require-dev
  2. composer install --no-dev drops it
  3. production confirmation prompt
  4. --force; migrate --seed forces db:seed
  5. separate seeder for required rows

basics

~20 s

The skeleton lists fakerphp/faker under require-dev, so a --no-dev production install lacks it and fake() fails; and db:seed asks for confirmation in production, cancelling non-interactive runs without --force. Seed production from a dedicated seeder with fixed values.

solid answer

~40 s

Two Laravel mechanics bite. First, the skeleton's `composer.json` puts `fakerphp/faker` in `require-dev`; a production build with `composer install --no-dev` does not install it, so any factory `definition()` that calls `fake()` dies with a class-not-found error. Second, `db:seed` uses the production confirmation: with `APP_ENV=production` it prompts, and a non-interactive deploy answers no, prints that the command was cancelled and exits with a failure code; `--force` skips the prompt, and `migrate --seed` passes `--force` to the `db:seed` it runs. The fix is to keep demo factories out of production entirely: run a dedicated seeder, such as `php artisan db:seed --class=ReferenceDataSeeder --force`, that writes fixed, known rows with no Faker, and design it so a second run does no harm.

code

php · 17 lines
php
<?php

namespace Database\Seeders;

use App\Models\Specialty;
use Illuminate\Database\Seeder;

// Production-safe: literal values, no fake(), repeatable
class ReferenceDataSeeder extends Seeder
{
    public function run(): void
    {
        foreach (['cardiology', 'dermatology', 'paediatrics'] as $slug) {
            Specialty::firstOrCreate(['slug' => $slug]);
        }
    }
}

go deeper

for a junior

Recall that db:seed asks for confirmation in production and that --force skips the prompt.

for a middle

Explain why factories that call fake() fail after composer install --no-dev, and how migrate --seed handles --force.

for a senior

Separate demo seeding from required production rows, run a dedicated seeder by class on deploy, and make it safe to repeat.

for a principal

Decide where required reference data lives for the team, a seeder run on deploy or a migration, and own the release process that enforces it.

## The situation A hospital-appointments app has a `DatabaseSeeder` that calls `DoctorSeeder`, `PatientSeeder` and `AppointmentSeeder`, all built on **model factories** that use `fake()`. It works on every laptop. Someone adds `php artisan db:seed` to the production deploy so the new `specialties` table gets its rows, and the deploy either stops with an error or reports that the command was cancelled. Both outcomes come from Laravel's own defaults. ## Failure 1: Faker is a development dependency The Laravel skeleton's `composer.json` lists `fakerphp/faker` under **`require-dev`**, next to PHPUnit and the other development tools. A production image is commonly built with `composer install --no-dev`, and then Faker is not installed there. - The `fake()` helper builds a `Faker\Generator` for the locale in `config('app.faker_locale')` (`APP_FAKER_LOCALE`, default `en_US`); without the package that class does not exist and PHP throws a class-not-found error. - The base factory itself tolerates a missing Faker (its `$faker` property is simply null), so the failure appears only when `definition()` actually generates a value. - Moving Faker to `require` to "fix" it ships a development tool to production and still leaves you writing random data into a live database. ## Failure 2: the production confirmation `db:seed` is one of Laravel's **confirmable** commands: 1. When the application environment is `production`, it prints an "Application In Production" warning and asks whether to continue. 2. In a non-interactive shell, such as a deploy script, the prompt returns its default, **no**; the command prints that it was cancelled and exits with a failure status, which usually fails the deploy step. 3. `--force` skips the prompt. 4. `migrate --seed` passes `--force` to the `db:seed` it calls, so the confirmation you see there belongs to `migrate` itself, which also needs `--force` in production. ## What to do instead | Need | Approach | |---|---| | demo or local data | factories and `DatabaseSeeder`, never run in production | | rows the app cannot work without | a dedicated seeder with literal values, no `fake()` | | running it on deploy | `php artisan db:seed --class=ReferenceDataSeeder --force` | | re-running safely | write rows so a second run changes nothing | Practical rules: - Keep factories and demo seeders development-only; `DatabaseSeeder` stays the developer's entry point. - Give production its own seeder class and call it explicitly with `--class`, so a deploy can never pick up demo data by accident. - Make that seeder safe to repeat, because deploys retry. How to design repeatable data changes is a general data-migration topic; in Laravel it comes down to looking rows up by a natural key before writing them. - Remember that `db:seed` runs inside `Model::unguarded()`, so a production seeder is not filtered by `$fillable`; pass only the columns you mean to write. - `DB::prohibitDestructiveCommands()` guards `migrate:fresh`, `migrate:refresh`, `migrate:reset`, `migrate:rollback` and `db:wipe`, but not `db:seed`; the confirmation prompt is its only built-in guard. ## A deploy checklist Before a seeding step goes into a production pipeline, check: 1. **Which class runs.** The step names it with `--class`; a bare `db:seed` would run `DatabaseSeeder` and its demo data. 2. **What it depends on.** Nothing in that seeder, or in any factory it touches, calls `fake()` or relies on another `require-dev` package. 3. **How it confirms.** The step passes `--force`, because the environment is `production` and the shell is not interactive. 4. **What a retry does.** Running the step twice leaves the same rows as running it once. 5. **Where it sits.** It runs after `migrate --force`, so the tables it writes to exist. A seeder that passes all five is a normal deploy step; one that fails any of them is a demo seeder that should never have reached the pipeline. ## How to explain it in an interview A strong answer names both mechanics (the `require-dev` Faker and the production confirmation with `--force`), explains why moving Faker to `require` is the wrong fix, and separates demo seeding from required data with its own seeder and an explicit `--class` in the deploy.

  • Why is moving fakerphp/faker from require-dev to require the wrong fix?
    It ships a development library to every production install and keeps the real problem: random demo records written into a live database. Production needs a seeder with fixed values that does not call `fake()`, run explicitly by class.
  • Does DB::prohibitDestructiveCommands() block db:seed?
    No. It prohibits `migrate:fresh`, `migrate:refresh`, `migrate:reset`, `migrate:rollback` and `db:wipe`. `db:seed` is guarded only by the production confirmation, which `--force` bypasses, so the deploy script's choice of seeder class is the real control.

saying these in an interview costs you the question

  • Faker ships with laravel/framework, so fake() works everywhere
  • db:seed refuses to run in production under any flags
  • A non-interactive db:seed in production seeds without asking
  • The fix is moving fakerphp/faker into require
  • prohibitDestructiveCommands() also blocks db:seed