0

Environment — local / staging / production: what differs in each

Beginner5 min read·eng-18-002
interview

Concept

Environment — a configuration of software, infrastructure, and data that code runs in. Different environments serve different purposes and have different access controls and expectations.

Common environments:

  • Local / Development: Developer's own machine. Fake data, relaxed security, debug tools enabled. APP_ENV=local.
  • Testing / CI: Automated tests run here. Isolated from real databases and services. Fake mail, in-memory cache.
  • Staging / UAT: A copy of production. Real-ish infrastructure, anonymized data. Used for QA and client sign-off.
  • Production: Live, real users, real data. Errors should NOT be displayed. Logging, monitoring, backups active.

Environment variables: Values that configure the application per-environment, set in the environment (not hardcoded). Database credentials, API keys, feature flags. In Laravel: .env file, loaded by Dotenv.

Why environments matter:

  • Prevents development changes from breaking production.
  • Allows testing dangerous changes (schema migrations) on staging first.
  • Different security configurations per environment.
  • "Works on my machine" is reduced by making environments consistent.

Environment parity: The more different environments are from each other, the more likely bugs will slip through. "Works on staging but not prod" usually means the environments differ (OS version, PHP version, environment variable, database config).

12-Factor App: The twelve-factor methodology says configuration should be in environment variables, not code. Never commit .env to version control.

Code Example

php
<?php
// Laravel .env file — NOT committed to git
// .env.example IS committed (template with no secrets)

// .env (local development — git-ignored)
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:...

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=zira_dev
DB_USERNAME=root
DB_PASSWORD=secret

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
MAIL_MAILER=log        // emails just logged, not sent

// .env.testing (for PHPUnit)
APP_ENV=testing
DB_DATABASE=zira_test
CACHE_DRIVER=array     // in-memory, resets per request
QUEUE_CONNECTION=sync  // run jobs immediately, no worker needed
MAIL_MAILER=array      // Mail::fake() still works

// .env.production (set in server/CI secrets, never in a file)
APP_ENV=production
APP_DEBUG=false        // never expose errors in production
DB_DATABASE=zira_prod
CACHE_DRIVER=redis
MAIL_MAILER=ses        // real email provider

// Accessing env vars in Laravel
$driver = config('cache.default');    // reads from config/cache.php
$driver = env('CACHE_DRIVER', 'file'); // direct env read (avoid in code, use config())

// config/cache.php uses env:
'default' => env('CACHE_DRIVER', 'database'),
// config() is cached after php artisan config:cache
// env() does NOT work after config:cache — always use config()

// Checking environment in code
if (app()->environment('local', 'testing')) {
    // debug-only behavior
}

if (app()->isProduction()) {
    // prod-only behavior
}