Skip to content

Meeting 13 - Laravel Environment & Configuration

Goal: Understand how Laravel handles environments (local, staging, production, testing), how .env is loaded, how configuration files work, secrets management, performance optimizations, and safe deployment practices.

1. Core Concepts

ConceptDescription
EnvironmentLogical context: local, staging, production, testing, etc. Affects debugging, logging, caching, and 3rd party integrations.
.env FileKey/value pairs loaded early (via vlucas/phpdotenv) into $_ENV & env() during bootstrap. Never commit real secrets.
Config FilesPHP arrays in config/*.php centralize settings. Access with config('app.timezone').
Service ContainerResolves dependencies; some bindings differ per environment.
CachingConfig, routes, events, views can be cached to speed bootstrap.
APP_KEYCryptographic key used for encryption & hashing (cookies, password reset tokens). Must be set & kept secret.

2. Environment Variables (.env)

Example minimal .env:

dotenv
APP_NAME="MyApp"
APP_ENV=local
APP_KEY=base64:GENERATED_KEY
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret

CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
APP_NAME="MyApp"
APP_ENV=local
APP_KEY=base64:GENERATED_KEY
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret

CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file

Notes:

  • APP_DEBUG should be false in production.
  • Separate .env.testing can override values during automated tests.
  • Use different DBs per environment to avoid data loss.

3. How Laravel Loads Configuration

  1. public/index.php -> bootstrap/app.
  2. .env is parsed early; values are accessible with env() only during config load.
  3. Each config/*.php file returns an array; many items call env('KEY').
  4. After loading, you should use config() helper (not env()) inside your application logic (controllers, services, jobs). Why? Because when config is cached, env() calls return null.

Example (config/app.php excerpt):

php
'timezone' => env('APP_TIMEZONE', 'UTC'),
'name' => env('APP_NAME', 'Laravel'),
'timezone' => env('APP_TIMEZONE', 'UTC'),
'name' => env('APP_NAME', 'Laravel'),

Usage in code:

php
config('app.timezone'); // good
// env('APP_TIMEZONE');  // avoid (except inside config/*.php)
config('app.timezone'); // good
// env('APP_TIMEZONE');  // avoid (except inside config/*.php)

4. Config Caching & Optimization

Boost performance in production by reducing file I/O.

CommandPurposeWhen to Use
php artisan config:cacheMerge all config into one fileOn deploy (after composer install)
php artisan route:cacheCache route definitionsFor stable route sets (mostly production)
php artisan view:cachePrecompile Blade templatesOptional for prod
php artisan event:cacheCache event/listener mappingsIf using events heavily
php artisan optimizeRuns several optimizers (Laravel 10+)Convenience wrapper
php artisan clear-compiledClear compiled files (older versions)Rarely needed

Clear caches when needed:

bash
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear

5. Environment Separation Strategy

AspectLocalStagingProduction
APP_ENVlocalstagingproduction
APP_DEBUGtruefalsefalse
Loggingverboseinfo/warningwarning/error
Cache Driverfileredisredis/memcached
Queue Driverdatabaseredisredis
DBlocal dev dbstaging dbproduction db
3rd Party Keystest keystest keyslive keys

6. Secrets Management

Best practices:

  • NEVER commit .env to version control (only .env.example).
  • Use infrastructure secret stores for production (e.g., Docker secrets, Kubernetes secrets, AWS SSM, Vault). Inject into environment before PHP-FPM starts.
  • Rotate keys (DB, API, OAuth) regularly.
  • Restrict APP_KEY access: treat like a password—it secures encrypted cookies.

Example .env.example snippet (no real secrets):

dotenv
APP_NAME="MyApp"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=
APP_NAME="MyApp"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=

7. Service Container & Environment

You can bind different implementations by environment:

php
// In a service provider
public function register(): void
{
    if (app()->environment('local')) {
        $this->app->bind(App\Contracts\PaymentGateway::class, App\Services\FakeGateway::class);
    } else {
        $this->app->bind(App\Contracts\PaymentGateway::class, App\Services\StripeGateway::class);
    }
}
// In a service provider
public function register(): void
{
    if (app()->environment('local')) {
        $this->app->bind(App\Contracts\PaymentGateway::class, App\Services\FakeGateway::class);
    } else {
        $this->app->bind(App\Contracts\PaymentGateway::class, App\Services\StripeGateway::class);
    }
}

Multiple environment detection helpers:

php
app()->environment();            // current name
app()->environment('local');      // boolean
app()->environment(['staging','production']); // boolean
app()->environment();            // current name
app()->environment('local');      // boolean
app()->environment(['staging','production']); // boolean

8. Configuration Patterns

8.1 Centralized Configuration Class (Optional Wrapper)

php
class FeatureFlags
{
    public static function isBetaEnabled(): bool
    {
        return (bool) config('features.beta');
    }
}
class FeatureFlags
{
    public static function isBetaEnabled(): bool
    {
        return (bool) config('features.beta');
    }
}

config/features.php:

php
return [
  'beta' => env('FEATURE_BETA', false),
];
return [
  'beta' => env('FEATURE_BETA', false),
];

8.2 Per-Environment File Override (Advanced)

You can load extra config per environment by checking app()->environment() within a service provider and merging arrays (config([...])). Keep this minimal to avoid confusion.

9. Testing Environment

  • PHPUnit automatically loads .env.testing if present.
  • Use dedicated database: set DB_DATABASE=myapp_test.
  • Run migrations fresh for isolation:
bash
php artisan test --env=testing
# or
php artisan test --parallel
php artisan test --env=testing
# or
php artisan test --parallel
  • Use RefreshDatabase trait in tests to migrate before each test suite.

10. Deployment Checklist (Environment Focused)

  1. Copy/Inject .env (or environment variables) – ensure APP_KEY set.
  2. Run composer install --no-dev --optimize-autoloader.
  3. Run database migrations: php artisan migrate --force.
  4. Cache config & routes: php artisan config:cache && php artisan route:cache.
  5. (Optional) Cache views & events: php artisan view:cache && php artisan event:cache.
  6. Queue workers / horizon restarted: php artisan queue:restart.
  7. Ensure storage & bootstrap/cache writable.
  8. Monitor logs & health endpoint after deploy.

11. Common Pitfalls

IssueCauseFix
env() returns null in app codeConfig cached; you used env() outside config filesUse config() helper
Stale config after editing .envConfig cache not clearedRun php artisan config:clear
APP_KEY missing errorKey not generatedphp artisan key:generate
Debug mode exposedAPP_DEBUG=true in productionSet false & clear config
Mixed staging & production keysReused .env accidentallyMaintain separate secrets store

12. Summary

Laravel environment management = .env + config/*.php + caching + container decisions. Treat configuration as code (but secrets as data). Cache for speed, isolate per environment, and never leak secrets through debug pages.