
This month at Stir Trek 2025, I presented on Dev Containers and GitHub Codespaces, demonstrating how these tools streamline both local and cloud-based development workflows. The session covered the essentials of creating portable development environments, customizing containers with features and extensions, and launching Codespaces directly from your repository. A lively Q&A followed, with attendees asking about strategies for running and working with multiple containers. Below, I’ve distilled those discussions and provided a deeper dive into shared container configurations across multiple projects-including folder structures, Docker Compose setups, VS Code workflows, and advanced tips you can apply in your own work.
Recap: Dev Containers and Codespaces#

Dev Containers are Docker-based environments enriched with development-specific tooling, settings, and startup tasks as defined in a devcontainer.json file. They enable you to use a container as a full-featured development environment-isolating dependencies, standardizing tool versions, and enabling reproducible setups locally or remotely (Dev Containers).
GitHub Codespaces builds on Dev Containers by providing cloud-hosted environments that spin up in seconds with configurable CPU, memory, and storage. Codespaces leverages the same open specification as Dev Containers, making your devcontainer.json a first-class citizen whether you connect via VS Code, IntelliJ, or directly in the browser (GitHub Docs).
Q&A: Running Multiple Containers#
Can I connect to multiple containers at once?#
By default, VS Code allows only one container per window, but you can open additional windows and attach each to a different container to work on multiple services in parallel (Visual Studio Code).
What about using a single window for multiple containers?#

If you use Docker Compose, define multiple services in your docker-compose.yml and create separate devcontainer.json configurations for each service-each referencing the common Compose file. VS Code will then list each configuration in its Dev Container picker, letting you reopen the current window to connect to a different service without duplicating your Compose setup (Dev Containers).
How do I configure separate containers for multiple projects?#
To maintain isolation and clarity, place each container configuration in its own subdirectory under .devcontainer, following the pattern .devcontainer/<project>/devcontainer.json. Tools supporting the spec recognize this layout and list all found configurations in the Codespaces or VS Code Dev Container dropdown (containers.dev, GitHub Docs).
Shared .devcontainer Folder Structure#
Centralize your container configurations in a single root directory:
dev-container/
├─ .devcontainer/
│ ├─ .env
│ ├─ docker-compose.yml
│ ├─ project-a-node-js/
│ │ └─ devcontainer.json
│ ├─ project-b-node-js/
│ │ └─ devcontainer.json
│ ├─ project-c-python/
│ │ └─ devcontainer.json
│ └─ project-d-go-lang/
│ └─ devcontainer.json
├─ project-a-node-js/
├─ project-b-node-js/
├─ project-c-python/
└─ project-d-go-lang/This layout lets all projects share a single Compose definition and environment variables-reducing duplication and easing updates (containers.dev).
Common Docker Compose File#
In .devcontainer/docker-compose.yml, define every project service alongside shared dependencies:
services:
project-a-node-js:
image: mcr.microsoft.com/devcontainers/base:latest
volumes:
- ..:/workspaces:cached
ports:
- "8001:8000"
command: sleep infinity
project-b-node-js:
image: mcr.microsoft.com/devcontainers/base:latest
volumes:
- ..:/workspaces:cached
ports:
- "8002:8000"
depends_on:
- postgres
project-c-python:
image: mcr.microsoft.com/devcontainers/base:latest
volumes:
- ..:/workspaces:cached
ports:
- "8003:8000"
depends_on:
- postgres
project-d-go-lang:
image: mcr.microsoft.com/devcontainers/base:latest
volumes:
- ..:/workspaces:cached
ports:
- "8004:8000"
postgres:
image: postgres:latest
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:Each service listens on port 8000 internally and is mapped to a unique host port (8001-8004) to prevent collisions (Visual Studio Code).
Workspace Mounts & Folder Mapping#
Mounting the root-level folder into /workspaces in each container gives uniform access to all projects. In each devcontainer.json, point workspaceFolder at the specific subdirectory:
"workspaceFolder": "/workspaces/project-b-node-js"This ensures your editor is scoped appropriately when connected (Dev Containers).
Per-Project devcontainer.json#
Each project’s configuration references the shared Compose file and specifies its service:
{
"name": "Project B Dev Container",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "project-b-node-js",
"workspaceFolder": "/workspaces/project-b-node-js",
"shutdownAction": "none"
}Using "shutdownAction": "none" keeps all containers running when you close one window, so you don’t inadvertently tear down shared services (Dev Containers).
Building & Switching Between Containers#
- Open the root folder (
dev-container/) in VS Code. - Reopen in Container: Run Dev Containers: Reopen in Container and select your desired project.
- Switch Container: Later, use Dev Containers: Switch Container to hop to another project without restarting the Docker stack (Visual Studio Code, GitHub Docs).
Advanced Multi-Project Strategies#
Environment-Specific Overrides#
Layer additional Compose files for environment-specific tweaks:
docker-compose -f docker-compose.yml \
-f docker-compose.override.yml \
-f docker-compose.dev.yml up -dOverrides can redefine images, ports, mounts, or feature flags per environment (Visual Studio Code).
Isolated Networks & Namespaces#
Define separate Docker networks to segment traffic:
networks:
dev-a: {}
dev-b: {}
services:
project-a-node-js:
networks: [dev-a]
project-b-node-js:
networks: [dev-b]
postgres:
networks: [dev-a, dev-b]This prevents unintended inter-service communication between project environments (Visual Studio Code).
GitHub Codespaces Integration#
Codespaces recognizes the same .devcontainer layout:
- Configuration Dropdown: Multiple
devcontainer.jsonfiles under.devcontainer/are automatically listed when creating a Codespace (GitHub Docs). - Port Forwarding: Host-mapped ports (8001-8004) surface as forwarded ports in the Codespaces UI.
- Pre-builds & Secrets: Enable pre-builds in
devcontainer.jsonand leverage repository or organization secrets instead of a local.envfile (The GitHub Blog).
Lifecycle Customization#
Use Dev Container lifecycle hooks per project to automate setup:
"postCreateCommand": "cd /workspaces/project-a-node-js && npm ci",
"postStartCommand": "npm run migrate",
"initializeCommand": "git submodule update --init"These commands ensure each container is fully prepared for development immediately (Dev Containers).
Troubleshooting Tips#
- Stuck at “Rebuilding container…”: Clear the Docker build cache or raise VS Code’s Docker logging level.
- Ports not forwarding: Verify
forwardPortsindevcontainer.jsonor check Codespaces port settings. - Volume performance issues: On macOS/Windows, consider isolating cache directories (e.g.,
node_modules) in named volumes to speed up I/O (Some Natalie’s corner of the internet, pamela fox’s blog).
Conclusion#
By centralizing Dev Container configurations and sharing a unified docker-compose.yml, you eliminate duplication, streamline dependency management, and enable seamless switching between multiple projects-both locally and in GitHub Codespaces. This pattern scales from a handful of services to extensive microservice landscapes, delivering consistent, reproducible developer environments across your entire workspace.




