Docker Compose Patterns for Local Development

Docker Compose turns “works on my machine” into “works everywhere.” Here’s how to structure it for real development workflows. Basic Structure 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # docker-compose.yml services: app: build: . ports: - "3000:3000" volumes: - .:/app environment: - NODE_ENV=development db: image: postgres:15 environment: POSTGRES_PASSWORD: devpass Start everything: ...

February 28, 2026 Â· 6 min Â· 1088 words Â· Rob Washington

Python Virtual Environments: A Practical Guide

Every Python project should have its own virtual environment. It’s not optional — it’s how you avoid dependency hell, reproducibility issues, and the dreaded “but it works on my machine.” Why Virtual Environments? Without virtual environments: Project A needs requests==2.25 Project B needs requests==2.31 Both use system Python One project breaks With virtual environments: Each project has isolated dependencies Different Python versions per project Reproducible across machines No sudo required for installing packages The Built-in Way: venv Python 3.3+ includes venv: ...

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

Docker Compose Patterns: From Development to Production

Docker Compose started as a development tool but has grown into something usable for small production deployments. Here are patterns that make it work well in both contexts. Environment-Specific Overrides Base configuration with environment-specific overrides: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # docker-compose.yml (base) services: app: image: myapp:${VERSION:-latest} environment: - DATABASE_URL depends_on: - db db: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # docker-compose.override.yml (auto-loaded in dev) services: app: build: . volumes: - .:/app - /app/node_modules environment: - DEBUG=true ports: - "3000:3000" db: ports: - "5432:5432" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # docker-compose.prod.yml services: app: restart: always deploy: replicas: 2 resources: limits: cpus: '0.5' memory: 512M logging: driver: json-file options: max-size: "10m" max-file: "3" db: restart: always Usage: ...

February 24, 2026 Â· 6 min Â· 1253 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

API Client Design: Building SDKs That Developers Love

A well-designed API client turns complex HTTP interactions into simple method calls. It handles authentication, retries, errors, and serialization — so users don’t have to. These patterns create clients that developers actually enjoy using. Basic Structure 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 import httpx from typing import Optional from dataclasses import dataclass @dataclass class APIConfig: base_url: str api_key: str timeout: float = 30.0 max_retries: int = 3 class APIClient: def __init__(self, config: APIConfig): self.config = config self._client = httpx.Client( base_url=config.base_url, timeout=config.timeout, headers={"Authorization": f"Bearer {config.api_key}"} ) def _request(self, method: str, path: str, **kwargs) -> dict: response = self._client.request(method, path, **kwargs) response.raise_for_status() return response.json() def close(self): self._client.close() def __enter__(self): return self def __exit__(self, *args): self.close() Resource-Based Design Organize by resource, not by HTTP method: ...

February 24, 2026 Â· 7 min Â· 1401 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

Makefiles for Project Automation: Simple, Portable Task Running

Make is 47 years old and still useful. Not for building C programs (though it does that), but as a simple task runner. Every Unix system has it. No installation required. One file defines all your project commands. Basic Structure 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # Makefile .PHONY: help build test clean help: @echo "Available targets:" @echo " build - Build the application" @echo " test - Run tests" @echo " clean - Clean build artifacts" build: npm run build test: npm test clean: rm -rf dist/ node_modules/ 1 2 3 make build make test make clean .PHONY Explained Without .PHONY, make checks if a file named “build” exists: ...

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

Git Hooks: Automate Quality Before It Reaches the Repo

Git hooks are scripts that run at specific points in the Git workflow. They’re the first line of defense against bad commits — catching issues before they pollute your repository history. Set them up once, forget about them, and let automation enforce quality. Hook Types Client-side hooks (your machine): pre-commit — Before commit is created prepare-commit-msg — Before commit message editor opens commit-msg — After commit message is entered pre-push — Before push to remote Server-side hooks (repository server): ...

February 23, 2026 Â· 6 min Â· 1086 words Â· Rob Washington

Technical Writing for Developers: Documentation That Gets Read

You can write brilliant code that nobody can use because the documentation is impenetrable. Or you can write average code with excellent docs that becomes everyone’s go-to solution. Documentation is a multiplier. These principles help you write docs that work. Know Your Audience Different readers need different docs: Tutorials (learning-oriented): “Follow these steps to build X” For newcomers Hand-holding is okay Complete, working examples Progressive complexity How-to Guides (task-oriented): “How to do X” ...

February 23, 2026 Â· 13 min Â· 2701 words Â· Rob Washington

Git Workflow Strategies: Branching Models That Scale

Git doesn’t enforce a workflow. You can branch however you want, merge whenever you want, push whatever you want. That freedom is powerful and dangerous. A good branching strategy reduces merge conflicts, enables parallel development, and makes releases predictable. A bad one creates confusion, broken builds, and deployment fear. Trunk-Based Development The simplest model: everyone commits to main, frequently. m a i n : ─ f ( ─ e h ─ a o ● │ t u ─ u r ─ r s ─ e ) ● ─ ─ f ( ─ i h ● │ x o ─ u ─ r ─ s ● ) ─ ─ ─ ● f ( ─ e h ─ a o ─ t u ● │ u r ─ r s ─ e ) ─ ● ─ ─ ─ Rules: ...

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