Windows without WSL2: If the developer machine runs Windows without WSL2 enabled, Docker Desktop will not work with Linux containers. Options: (1) Enable WSL2 (recommended -- requires Windows 10 version 2004 or later), (2) Use Podman Desktop as a Docker alternative, (3) Skip Step 3 and install services natively (lose reproducibility). The workflow strongly recommends WSL2 for Windows developers.
Monorepo with multiple services: If the project is a monorepo containing multiple services (frontend, backend API, background worker), each service needs its own entry in docker-compose.yml (Step 3) and its own CI job (Step 4). Step 1 may need to be repeated for each service with its own language and dependency setup. Use a single .gitignore at the repository root with patterns for all languages.
Existing project with no tests: If Step 1 is applied to an existing project that has no test suite, the CI pipeline in Step 4 will have nothing to run in the test step. Add a placeholder test file in Step 1 that verifies the application can import successfully (a smoke test). This ensures the CI pipeline structure is in place when tests are added later.
Restricted corporate network: If the development machine is behind a corporate proxy or firewall that blocks Docker Hub, npm registry, or PyPI, configure proxy settings for each tool in Step 1 (pip, npm, Docker) before attempting dependency installation. Some corporate environments require a private registry mirror -- check with IT before attempting public registry access.
Apple Silicon (ARM64) compatibility: If developers use Apple Silicon Macs while CI runs on x86_64 Linux, some Docker images may not have ARM64 variants. In Step 3, specify platform: linux/amd64 for services that lack ARM64 images, and accept the performance cost of emulation during local development. Test on both architectures if the team is mixed.
Project with multiple programming languages: If the project uses multiple languages (e.g., Python backend and TypeScript frontend), Step 1 must be performed for each language with its own runtime version management and dependency file. Step 3 becomes especially valuable -- Docker Compose unifies all language runtimes into a single docker compose up command, eliminating the need for each developer to install every language runtime locally. The CI pipeline in Step 4 should use matrix builds to test each language component independently.
Adding development environment to a project with existing contributors: If team members already have local setups that differ from each other, introduce Docker Compose (Step 3) as optional first. Document both approaches in the README: native setup and containerized setup. Once all team members have validated the containerized setup produces identical behavior, deprecate the native setup instructions. Forcing a switch without a migration period causes friction and lost productivity.
Project that requires GPU access for development: If the project involves machine learning, video processing, or GPU-accelerated computation, the standard Docker Compose setup needs NVIDIA Container Toolkit (Linux) or GPU passthrough (Windows WSL2). Not all developers will have GPUs. Design the development environment with a CPU fallback mode that uses smaller models or test datasets, reserving GPU testing for CI runners with GPU instances or a shared development server.
Secrets management in local development: Never commit real API keys, database passwords, or service credentials to the repository. Step 3's .env.example file documents which secrets are needed with placeholder values. For teams, consider using a secrets manager (1Password CLI, Doppler, or AWS Parameter Store) with a local sync command that populates the .env file from the shared secret store. This prevents developers from sharing secrets through insecure channels like Slack messages.
Project with large binary assets (ML models, media files): If the project includes large files that should not be stored in Git (trained models, video assets, dataset files), configure Git LFS in Step 2 for tracked file types. In Step 3, mount the large file directory as a Docker volume rather than copying into the image to avoid bloating the container image size. The CI pipeline in Step 4 should cache these assets separately from code dependencies.
Slow dependency installation blocking development: If dependency installation takes more than 5 minutes (common in large Python or Node.js projects with native extensions), add a dependency caching layer. In Step 3, use a named Docker volume for the virtual environment or node_modules so that docker compose down and docker compose up does not reinstall all dependencies. In Step 4, use the CI platform's caching mechanism with a hash of the dependency lock file as the cache key, so cache invalidation only happens when dependencies actually change.
Development environment for a microservices architecture: If the project consists of multiple services that communicate over the network, Step 3's Docker Compose file should define all services with a shared network and service discovery via container names. Use profiles (docker compose --profile backend) to allow developers to run only the services they are actively developing, with the rest running as pre-built images from the container registry. This reduces local resource usage while maintaining realistic inter-service communication.
Project with database seed data for development: If the application requires realistic data to be useful during development (product catalog, user accounts, sample transactions), create a seed script in Step 1 that populates the database with deterministic test data. In Step 3, add a Docker Compose service or startup hook that runs the seed script when the database container starts for the first time. Use a marker (seed_applied table or file) to prevent re-seeding on subsequent container restarts. Include enough data to exercise all application features -- a single user with zero records is not a useful development environment.
Onboarding a non-technical team member: If designers, product managers, or QA testers need to run the application locally, create a simplified onboarding script that wraps Steps 1-3 into a single command (make setup or a dedicated setup script). The script should check prerequisites (Docker installed, Git configured), clone the repository, copy .env.example, and run docker compose up. Print a clear success message with the URL to access the application. Include a troubleshooting section in the README covering the 5 most common setup failures.
Development environment with background job processing: If the application includes background workers (email sending, report generation, queue consumers), Step 3's Docker Compose configuration should include a separate worker service that shares the application code but runs a different entrypoint command (e.g., celery worker, sidekiq, bull worker). Use the same image as the application service with an overridden command. This ensures the worker runs the same code version as the API during development. Add a queue management UI container (Flower for Celery, Bull Board for Bull) to monitor job processing during development without needing CLI access to the queue.
Keeping the development environment documentation current: After completing all four steps, add a "Development Setup" section to the README that documents the exact commands a new developer runs, the expected output at each step, and common troubleshooting steps. Treat this documentation as code: update it whenever the setup process changes. A stale README that describes a setup process from six months ago is worse than no README -- it builds false confidence and wastes debugging time when the documented steps fail.