Database Migrations That Won't Ruin Your Weekend

Database migrations are where deploys go to die. Here’s how to make them safe. The Golden Rule Every migration must be backward compatible. Your old code will run alongside the new schema during deployment. If it can’t, you’ll have downtime. Safe Operations These are always safe: Adding a nullable column Adding a table Adding an index (with CONCURRENTLY) Adding a constraint (as NOT VALID, then validate later) Dangerous Operations These need careful handling: ...

February 28, 2026 Â· 4 min Â· 770 words Â· Rob Washington

Feature Flags: Deploy Doesn't Mean Release

Separating deployment from release is one of the best things you can do for your team’s sanity. Feature flags make this possible. The Core Idea 1 2 3 4 5 6 7 8 9 10 11 12 # Without flags: deploy = release def checkout(): process_payment() send_confirmation() # With flags: deploy != release def checkout(): process_payment() if feature_enabled("new_confirmation_email"): send_new_confirmation() # Deployed but not released else: send_confirmation() Code ships to production. Flag decides if users see it. ...

February 28, 2026 Â· 5 min Â· 924 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

Graceful Shutdown: Stop Dropping Requests

Every deployment is a potential outage if your application doesn’t shut down gracefully. Here’s how to do it right. The Problem 1 2 3 4 5 . . . . . K P Y I U u o o n s b d u - e e r f r r i l s n s a i e p g s t r p h e e e t e s m e o x r e s v i e r e e t q r n d s u o d e r s f i s s r m t S o m s d I m e u G d g r T s i e i E e a t n R r t g M v e c i l o " c y n z e n e e r e c o n t - d i d p o o o n w i n n r t t e i s s m e e t " d e p l o y s The fix: handle SIGTERM, finish existing work, then exit. ...

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

rsync Patterns for Reliable Backups and Deployments

rsync is the standard for efficient file transfer. It only copies what changed, handles interruptions gracefully, and works over SSH. Here’s how to use it well. Basic Syntax 1 rsync [options] source destination The trailing slash matters: 1 2 rsync -av src/ dest/ # Contents of src into dest rsync -av src dest/ # Directory src into dest (creates dest/src/) Essential Options 1 2 3 4 5 6 -a, --archive # Archive mode (preserves permissions, timestamps, etc.) -v, --verbose # Show what's being transferred -z, --compress # Compress during transfer -P # Progress + partial (resume interrupted transfers) --delete # Remove files from dest that aren't in source -n, --dry-run # Show what would happen Common Patterns Local Backup 1 2 3 4 5 # Mirror directory rsync -av --delete /home/user/documents/ /backup/documents/ # Dry run first rsync -avn --delete /home/user/documents/ /backup/documents/ Remote Sync Over SSH 1 2 3 4 5 6 7 8 # Push to remote rsync -avz -e ssh /local/dir/ user@server:/remote/dir/ # Pull from remote rsync -avz -e ssh user@server:/remote/dir/ /local/dir/ # Custom SSH port rsync -avz -e "ssh -p 2222" /local/ user@server:/remote/ With Progress 1 2 3 4 5 # Single file progress rsync -avP largefile.zip server:/dest/ # Overall progress (rsync 3.1+) rsync -av --info=progress2 /source/ /dest/ Exclusions 1 2 3 4 5 6 7 8 # Exclude patterns rsync -av --exclude='*.log' --exclude='tmp/' /source/ /dest/ # Exclude from file rsync -av --exclude-from='exclude.txt' /source/ /dest/ # Include only certain files rsync -av --include='*.py' --exclude='*' /source/ /dest/ Example exclude file: ...

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

Blue-Green Deployments: Zero-Downtime Releases Without the Drama

The scariest moment in software delivery used to be clicking “deploy.” Will it work? Will it break? Will you be debugging at 2 AM? Blue-green deployments eliminate most of that fear. Instead of updating your production environment in place, you deploy to an identical standby environment and switch traffic over. If something’s wrong, you switch back. Done. The Core Concept You maintain two identical production environments: Blue: Currently serving live traffic Green: Idle, ready for the next release To deploy: ...

February 24, 2026 Â· 8 min Â· 1600 words Â· Rob Washington

Feature Flags: Deploy Without Fear

Deployment and release are different things. Deployment puts code on servers. Release makes features available to users. Feature flags separate these concerns. With feature flags, you can deploy daily but release weekly. Ship risky changes but only enable them for 1% of users. Roll back a broken feature in seconds without touching infrastructure. Basic Implementation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class FeatureFlags: def __init__(self): self.flags = {} def is_enabled(self, flag_name: str, user_id: str = None) -> bool: flag = self.flags.get(flag_name) if not flag: return False # Global kill switch if not flag.get("enabled", False): return False # Check user allowlist if user_id and user_id in flag.get("allowed_users", []): return True # Check percentage rollout if user_id and flag.get("percentage", 0) > 0: # Consistent hashing: same user always gets same result hash_val = hash(f"{flag_name}:{user_id}") % 100 return hash_val < flag["percentage"] return flag.get("enabled", False) flags = FeatureFlags() flags.flags = { "new_checkout": { "enabled": True, "percentage": 10, # 10% of users "allowed_users": ["user_123"] # Plus specific users } } # Usage if flags.is_enabled("new_checkout", user_id=current_user.id): return new_checkout_flow() else: return old_checkout_flow() Configuration-Based Flags Store flags in config, not code: ...

February 23, 2026 Â· 7 min Â· 1351 words Â· Rob Washington

Database Migrations Without Downtime: The Expand-Contract Pattern

Database migrations are terrifying. One wrong move and you’ve locked tables, broken queries, or corrupted data. The traditional approach — maintenance window, stop traffic, migrate, restart — works but costs you availability and customer trust. The expand-contract pattern lets you migrate schemas incrementally, with zero downtime, while your application keeps serving traffic. The Core Idea Never make breaking changes in a single step. Instead: Expand: Add the new structure alongside the old Migrate: Move data and update application code Contract: Remove the old structure Each step is independently deployable and reversible. ...

February 23, 2026 Â· 5 min Â· 991 words Â· Rob Washington

Zero-Downtime Deployments: Strategies That Actually Work

“We’re deploying, please hold” is not an acceptable user experience. Whether you’re running a startup or enterprise infrastructure, users expect services to just work. Here’s how to ship code without the maintenance windows. The Goal: Invisible Deploys A zero-downtime deployment means users never notice you’re deploying. No error pages, no dropped connections, no “please refresh” messages. The old version serves traffic until the new version is proven healthy. Strategy 1: Rolling Deployments The simplest approach. Replace instances one at a time: ...

February 22, 2026 Â· 7 min Â· 1329 words Â· Rob Washington

Graceful Shutdown: Don't Drop Requests on Deploy

Your deploy shouldn’t kill requests mid-flight. Every dropped connection is a failed payment, a lost form submission, or a frustrated user. Graceful shutdown ensures your application finishes what it started before dying. Here’s how to do it right. The Problem Without graceful shutdown: 1 1 1 1 1 3 3 3 3 3 : : : : : 0 0 0 0 0 0 0 0 0 0 : : : : : 0 0 0 0 0 0 1 1 1 1 - - - - - R D P C U e e r l s q p o i e u l c e r e o e n s y s t s t s e s g e s i k e s t g i t a n l s e r a l r t l e c r s d o o r n r ( e i n , e c m e x e m c r p i e t e e v d i t c e i o r t d a n i e t e d e r s l e , 2 y s e m s t a e y c b o e n d g i r v e e s s p o u n p s e ) With graceful shutdown: ...

February 19, 2026 Â· 10 min Â· 2111 words Â· Rob Washington