DEV Community

Chris Ayers
Chris Ayers

Posted on • Originally published at chris-ayers.com on

Stir Trek 2025 and Multiple Dev Containers

Stir Trek 2025

Stir Trek 2025

This month, at Stir Trek 2025, I had the pleasure of presenting on Dev Containers and GitHub Codespaces, showcasing how these tools streamline local and cloud-based development workflows. The talk covered the fundamentals of creating portable development environments, customizing containers with features and extensions, and spinning up Codespaces directly from your repository. A lively Q&A session followed, with several attendees asking about strategies for running and working with multiple containers. Below, I’ve distilled those discussions and integrated a deeper dive into shared container configurations across multiple projects-complete with folder structures, Docker Compose configurations, VS Code workflows, and advanced tips-you can apply in your own work.

Recap of Dev Containers and Codespaces

Dev Containers

Dev Containers are Docker-based environments enriched with development-specific tooling, settings, and startup tasks as defined in a devcontainer.json file. They allow 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

Q1: 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).

Q2: What about using a single window for multiple containers?

Docker Compose

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).

Q3: 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

Rather than scattering .devcontainer folders per project, centralize them 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/

Enter fullscreen mode Exit fullscreen mode

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:

Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

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"
}

Enter fullscreen mode Exit fullscreen mode

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

  1. Open the root folder (dev-container/) in VS Code.
  2. Reopen in Container: Run Dev Containers: Reopen in Container and select your desired project.
  3. 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 -d

Enter fullscreen mode Exit fullscreen mode

Overrides 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]

Enter fullscreen mode Exit fullscreen mode

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.json files 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.json and leverage repository or organization secrets instead of a local .env file (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"

Enter fullscreen mode Exit fullscreen mode

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 forwardPorts in devcontainer.json or 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.

Top comments (0)