Environment Variables Done Right

Environment variables are the standard way to configure applications across environments. They’re simple, universal, and supported everywhere. But like any tool, they can be misused. Here’s how to do them right. The Basics Environment variables are key-value pairs available to your process: 1 2 3 4 5 6 7 8 9 10 11 12 13 # Setting them export DATABASE_URL="postgres://localhost/myapp" export LOG_LEVEL="info" # Using them (Bash) echo $DATABASE_URL # Using them (Python) import os db_url = os.environ.get("DATABASE_URL") # Using them (Node.js) const dbUrl = process.env.DATABASE_URL; Naming Conventions Be consistent. A common pattern: ...

March 1, 2026 Â· 5 min Â· 996 words Â· Rob Washington

Logging Levels: A Practical Guide to What Goes Where

Logging seems simple until you’re debugging production at 2 AM, scrolling through millions of lines trying to find the one that matters. Good logging practices make that experience less painful. Here’s how to think about log levels. The Levels Most logging frameworks use these standard levels: D E B U G < I N F O < W A R N < E R R O R < F A T A L In production, you typically run at INFO or WARN. Lower levels include all higher levels (INFO includes WARN, ERROR, and FATAL). ...

March 1, 2026 Â· 4 min Â· 836 words Â· Rob Washington

API Error Handling That Helps Instead of Frustrates

Bad error handling wastes everyone’s time. A cryptic “Error 500” sends developers on a debugging odyssey. A well-designed error response tells them exactly what went wrong and how to fix it. Here’s how to build the latter. The Anatomy of a Good Error Every error response should answer three questions: What happened? (error code/type) Why? (human-readable message) How do I fix it? (actionable guidance) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": [ { "field": "email", "message": "Invalid email format", "received": "not-an-email" }, { "field": "age", "message": "Must be a positive integer", "received": "-5" } ], "documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR" }, "request_id": "req_abc123" } Always include: ...

March 1, 2026 Â· 6 min Â· 1214 words Â· Rob Washington

Configuration Management Patterns for Reliable Deployments

Configuration is where deployments go to die. A typo in an environment variable, a missing secret, a config file that works in staging but breaks in production. Here’s how to make configuration boring and reliable. The Hierarchy of Configuration Not all config is created equal. Layer it: 1 2 3 4 5 . . . . . D C E C F e o n o e f n v m a a f i m t u i r a u l g o n r t n d e s f m - i e l f ( l n i l i e t n a n s e g v s c ( a f o p r l ( d e i a r e r a g u ) b s n e l t n e i v s m i e r ) o n m e n t ) S O D a R n y f E u e n e n n - a s v t o m t i i f i r m f c f o e a n c l m v h l e v e a b n e r n a t r r g c - r i e k s i d s s p d e e e s c s i f i c Later layers override earlier ones. This lets you: ...

March 1, 2026 Â· 7 min Â· 1386 words Â· Rob Washington

The Twelve-Factor App: What Actually Matters

The Twelve-Factor methodology is from 2011 but remains relevant. Here’s what matters in practice, and what’s become outdated. The Factors, Ranked by Impact Critical (Ignore at Your Peril) III. Config in Environment 1 2 3 4 5 # Bad DATABASE_URL = "postgres://localhost/myapp" # hardcoded # Good DATABASE_URL = os.environ["DATABASE_URL"] Config includes credentials, per-environment values, and feature flags. Environment variables work everywhere: containers, serverless, bare metal. VI. Stateless Processes 1 2 3 4 5 6 7 8 9 10 11 # Bad: storing session in memory sessions = {} @app.post("/login") def login(user): sessions[user.id] = {"logged_in": True} # Dies with process # Good: external session store @app.post("/login") def login(user): redis.set(f"session:{user.id}", {"logged_in": True}) If your process dies, can another pick up the work? Statelessness enables horizontal scaling, rolling deploys, and crash recovery. ...

February 28, 2026 Â· 5 min Â· 864 words Â· Rob Washington

Environment Variables Done Right: 12-Factor Config in Practice

The third factor of the 12-Factor App methodology states: “Store config in the environment.” Simple advice that’s surprisingly easy to get wrong. The Core Principle Configuration that varies between environments (dev, staging, production) should come from environment variables, not code. This includes: Database connection strings API keys and secrets Feature flags Service URLs Port numbers Log levels What stays in code: application logic, default behaviors, anything that doesn’t change between deploys. ...

February 25, 2026 Â· 6 min Â· 1182 words Â· Rob Washington

Configuration Management Principles: Making Deployments Predictable

Most production incidents I’ve debugged came down to configuration. A missing environment variable. A wrong database URL. A feature flag stuck in the wrong state. Code was fine; configuration was the problem. Configuration management is the unsexy work that prevents those 3 AM pages. The Core Principles 1. Separate Configuration from Code Configuration should never be baked into your application binary or container image. Wrong: 1 2 # Hardcoded in code DATABASE_URL = "postgres://prod:password@db.example.com/myapp" Also wrong: ...

February 24, 2026 Â· 7 min Â· 1321 words Â· Rob Washington

Code Review Practices: Making Reviews Useful, Not Painful

Code review is a skill separate from coding. A great programmer can give terrible reviews — nitpicking style while missing logic bugs, or rubber-stamping everything to avoid conflict. Good reviews improve code quality and team knowledge. Bad reviews slow everything down. These practices help you do reviews that actually help. The Reviewer’s Mindset You’re Not the Gatekeeper ❌ ✅ " " I D o w e o s u l t d h n i ' s t w h o a r v k e c w o r r i r t e t c e t n l y i t a n t d h i m s a i w n a t y a " i n a b l y ? " Your job isn’t to make the code match your personal style. It’s to catch problems and improve clarity. ...

February 24, 2026 Â· 12 min Â· 2380 words Â· Rob Washington

Testing Strategies: The Pyramid That Actually Works

Everyone agrees testing is important. Few agree on how much, what kind, and where to focus. The testing pyramid provides a framework: many fast unit tests, fewer integration tests, even fewer end-to-end tests. But the pyramid only works if you understand why each layer exists. The Pyramid E I U 2 n n E t i e t T g e r T s a e t t s s i t o s ( n f ( e T m w e a , s n t y s s , l o ( f w s a , o s m t e e , x , p c e m h n e e s d a i i p v u ) e m ) s p e e d ) Unit tests: Test individual functions/classes in isolation Integration tests: Test components working together E2E tests: Test the full system like a user would ...

February 24, 2026 Â· 7 min Â· 1367 words Â· Rob Washington

Error Handling Philosophy: Fail Gracefully, Recover Quickly

Errors are inevitable. Networks fail. Disks fill up. Services crash. Users input garbage. The question isn’t whether your system will encounter errors — it’s how it will behave when it does. Good error handling is the difference between “the system recovered automatically” and “we lost customer data.” Fail Fast, Fail Loud Detect problems early and report them clearly: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # ❌ Bad: Silent failure def process_order(order): try: result = payment_service.charge(order) except Exception: pass # Swallow error, pretend it worked return {"status": "ok"} # ✅ Good: Fail fast with clear error def process_order(order): if not order.items: raise ValidationError("Order must have at least one item") if order.total <= 0: raise ValidationError("Order total must be positive") result = payment_service.charge(order) # Let it fail if it fails return {"status": "ok", "charge_id": result.id} Silent failures are debugging nightmares. If something goes wrong, make it obvious. ...

February 24, 2026 Â· 6 min Â· 1159 words Â· Rob Washington