
Did You Know? 10 Developer Tips from Real Codebases
A collection of practical software development tips, workarounds, and clever solutions discovered in production codebases. Part 1 of an ongoing series.
Key takeaways
- Docker BuildKit cache mounts can cut Python build times from 3 minutes to 30 seconds.
- Codemagic requires separate YAML workflows for iOS and macOS builds.
- One Docker container can serve dev and production with an environment variable switch.
Welcome to "Did You Know?" - a series where I share practical tips, clever workarounds, and non-obvious solutions discovered in real production codebases. These aren't theoretical best practices - they're battle-tested patterns from actual projects.
1. Codemagic Can't Build iOS and macOS Simultaneously (But There's a Workaround)
The Problem: You have a Flutter app targeting both iOS and macOS. You'd expect to build them in one pipeline, but Codemagic doesn't allow selecting both platforms in a single workflow.
Did you know? The solution is to use custom codemagic.yaml files with separate workflows:
workflows:
ios-build:
name: iOS Build
instance_type: mac_mini_m2
environment:
xcode: latest
scripts:
- flutter build ios --release
artifacts:
- build/ios/ipa/*.ipa
macos-build:
name: macOS Build
instance_type: mac_mini_m2
environment:
xcode: latest
scripts:
- flutter build macos --release
artifacts:
- build/macos/**/*.app
You can trigger both workflows in parallel from your CI, or chain them sequentially. The key insight: don't fight the UI limitations - embrace YAML-based configuration for full control.
2. Docker BuildKit Cache Mounts Can Cut Build Times by 80%
The Problem: Every Docker build reinstalls all dependencies from scratch, even if nothing changed.
Did you know? BuildKit cache mounts persist pip and poetry caches across builds:
RUN --mount=type=cache,mode=0755,target=/root/.cache/pip pip install poetry==1.8.4
RUN --mount=type=cache,mode=0755,target=/root/.cache/pypoetry poetry install --no-root
This reuses downloaded packages across builds. A typical Python project with 100+ dependencies goes from 3 minutes to 30 seconds on subsequent builds.
3. One Docker Container, Two Behaviors
The Problem: You need different startup commands for development (hot reload) vs production (gunicorn), but don't want to maintain separate Dockerfiles.
Did you know? You can switch behavior at runtime using environment variables in your command:
command: >
sh -c "if [ \"$DEVELOPMENT_MODE\" = \"True\" ]; then
poetry run python manage.py runserver 0.0.0.0:8000;
else
gunicorn --bind :8000 --workers 8 musicdatalab.wsgi:application;
fi"
Same image, different modes. Deploy once, configure per environment.
4. Health Checks Prevent Race Conditions in Docker Compose
The Problem: Your app crashes on startup because the database isn't ready yet, even though you used depends_on.
Did you know? depends_on only waits for the container to start, not for the service to be ready. Use health checks:
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# In the db service:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser"]
interval: 2s
timeout: 2s
retries: 10
Now Docker waits until PostgreSQL actually accepts connections.
5. Celery Workers Can Auto-Restart on Memory Leaks
The Problem: Your Celery workers slowly consume more memory until they get OOM-killed, taking running tasks with them.
Did you know? Celery has a built-in flag to gracefully restart workers that exceed a memory threshold:
command: celery -A myapp worker --loglevel=info --concurrency=4 --max-memory-per-child=100000
Workers restart themselves after processing tasks if they exceed ~100MB. Memory leaks become a minor nuisance instead of a production incident.
6. Firebase's Immutable Cache Headers Give You Free CDN Performance
The Problem: Your static assets (images, fonts, JS bundles) get re-downloaded on every visit, wasting bandwidth and slowing load times.
Did you know? If your assets have hashed filenames (like app.a1b2c3.js), you can cache them forever:
{
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp|ico|avif)",
"headers": [{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}]
}
]
}
One year cache, marked immutable. Browsers won't even send conditional requests. Combined with Nuxt or Next.js hash-based filenames, cache invalidation happens automatically on deploy.
7. Pre-commit Hooks Can Enforce Conventional Commits
The Problem: Your git history is a mess of "fix", "update", and "wip" messages. You want semantic versioning but can't parse the commits.
Did you know? You can reject non-conventional commits before they're even created:
# .pre-commit-config.yaml
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.0.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
Now git commit -m "fixed stuff" fails, but git commit -m "fix: resolve login timeout issue" succeeds. Your changelog writes itself.
8. Shell Scripts Can Fail Fast with Dependency Checks
The Problem: Your startup script fails midway through because a required Python module is missing, leaving the system in a partial state.
Did you know? You can validate critical imports before starting:
#!/usr/bin/env sh
set -eu
# Fail-fast import check
python - <<'PY'
import importlib
for m in ("fastapi", "uvicorn", "sqlalchemy"):
importlib.import_module(m)
PY
exec python -m uvicorn app.main:app --host 0.0.0.0 --port "$PORT"
If any import fails, the script exits immediately with a clear error. No partial startups.
9. Nuxt Can Auto-Discover Routes by Crawling Links
The Problem: You're using Nuxt's static generation but have to manually list every route for prerendering.
Did you know? Nuxt can automatically discover pages by following internal links:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'static',
prerender: {
routes: ['/'],
crawlLinks: true // Magic happens here
}
}
})
Start from /, and Nuxt will find and prerender every linked page. Add new content, link to it from anywhere, and it's automatically included in the build.
10. AWS in Docker: Disable EC2 Metadata to Force Environment Variables
The Problem: Your containerized app ignores the AWS credentials you set in environment variables and tries to use EC2 metadata (which doesn't exist outside EC2).
Did you know? You can explicitly disable the metadata service:
environment:
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_EC2_METADATA_DISABLED=true
- AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=
The AWS SDK has a credential chain that checks metadata before environment variables. Disabling metadata forces it to use your explicit credentials.
Bonus: Ruff Lets You Apply Different Rules to Different Paths
Did you know? You can be strict in production code but lenient in tests:
# ruff.toml
[lint.per-file-ignores]
"__init__.py" = ["E402"] # Allow late imports in init files
"**/tests/*" = ["E402", "S101"] # Allow assert statements in tests
"**/migrations/*" = ["E501"] # Allow long lines in migrations
No more disabling rules globally just because they don't apply everywhere.
What's Next?
This is Part 1 of an ongoing series. Each installment will bring 10 new tips from real codebases - no fluff, just practical knowledge.
Got a tip of your own? Found something clever in a codebase you're working on? drop me a line - I might feature it in a future edition.
Dev Meeting 001: Kubernetes is a Framework
My notes on Kubernetes as a framework: from Google's scaling challenges and Borg to Operators, CRDs, and modern container orchestration basics.
Export Ableton Locators to JSON via Max for Live
Build a Max for Live device that exports arrangement locators and BPM to JSON, ready for external tools and AI music analysis pipelines.