Skip to main content

Command Palette

Search for a command to run...

Using Dev Containers (.devcontainer)

When Dev Containers help—and when they get in the way

Updated
5 min read
Using Dev Containers (.devcontainer)
N
Senior-level Fullstack Web Developer with 10+ years experience, including 2 years of Team Lead position. Specializing in responsive design and full-stack web development across the Vue.js and .NET ecosystems. Skilled in Azure/AWS cloud infrastructure, focused on DevOps techniques such as CI/CD. Experienced in system design, especially with software architecture patterns such as microservices, BFF (backend-for-frontend). Hands-on with Agile practices in team leading, and AI-assisted coding.

Series: Containers, Actually: Building Real Local Dev Environments

Dev Containers are one of those tools that feel revolutionary the first time you use them—and constraining the tenth time you try to bend them.

Used correctly, they can dramatically improve onboarding and consistency.
Used blindly, they can hide too much of the system to be educational or flexible.

This article explains what Dev Containers actually are, how they work under the hood, and when they complement—not replace—the Docker-based workflow described throughout this series.


What Dev Containers Actually Are (Not the Marketing Version)

A Dev Container is not a new container runtime.

It is:

  • A convention

  • A configuration layer

  • A VS Code feature

At its core, a Dev Container is a folder named:

.devcontainer/

containing configuration that tells VS Code:

  • Which container to run

  • How to connect to it

  • Which tools to install

  • Which commands to execute on startup

That’s it.

Everything else—Docker, docker-compose, volumes, networks—already existed.


The Mental Model That Matters

Think of Dev Containers as:

“A predefined editor session inside a container.”

They answer questions like:

  • Where does my editor attach?

  • What tools should be available?

  • How do I ensure everyone uses the same runtime?

They do not answer:

  • How should my system be architected?

  • How should services communicate?

  • How should state persist?

Those answers still belong to Docker and docker-compose.


A Minimal .devcontainer Setup

Let’s start with the smallest useful example.

Folder Structure

.devcontainer/
├─ devcontainer.json

devcontainer.json

{
  "name": "PHP App Dev Container",
  "image": "php:8.2-cli",
  "workspaceFolder": "/workspace",
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind"
  ],
  "extensions": [
    "bmewburn.vscode-intelephense-client"
  ]
}

What this does:

  • Starts a PHP container

  • Mounts your project into /workspace

  • Attaches VS Code to that container

  • Installs a PHP language extension

This is the simplest Dev Container: single runtime, no services.


Dev Containers With docker-compose (The Real Use Case)

Dev Containers become interesting when combined with docker-compose.

Folder Structure

.devcontainer/
├─ devcontainer.json
docker-compose.yml

devcontainer.json

{
  "name": "Full Stack Dev",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/var/www/html",
  "shutdownAction": "stopCompose",
  "extensions": [
    "bmewburn.vscode-intelephense-client",
    "xdebug.php-debug"
  ]
}

What This Means

  • VS Code runs inside the app container

  • All other services (MySQL, Redis, etc.) are started via compose

  • The editor sees the same filesystem and runtime as the app

This gives you perfect runtime parity—at the cost of flexibility.


Where Dev Containers Shine

Dev Containers are extremely good at three things.

1. Onboarding Speed

For new developers:

  • Clone repo

  • Open in VS Code

  • Click “Reopen in Container”

That’s it.

No:

  • Local runtime installs

  • Version mismatches

  • “Works on my machine” debates

This is Dev Containers at their best.

2. Tooling Consistency

Dev Containers guarantee:

  • Same Node version

  • Same PHP version

  • Same CLI tools

  • Same editor extensions

This eliminates an entire class of subtle drift.

3. CI / Dev Alignment (When Done Well)

If your CI builds the same image used by Dev Containers, then:

  • CI failures reproduce locally

  • Tooling discrepancies vanish

  • Debugging gets simpler

This alignment is powerful—but fragile if overextended.


Where Dev Containers Hurt (And Why)

Now for the uncomfortable part.

1. Editor Lock-In

Dev Containers are a VS Code feature.

That means:

  • JetBrains users are excluded

  • CLI-only workflows are awkward

  • Editor becomes infrastructure

If your team standardizes on VS Code, this is fine.
If not, it’s a constraint.

2. Hidden Docker Complexity

Dev Containers abstract Docker very effectively.

That’s good—until something breaks.

Common symptoms:

  • “The container won’t start”

  • “Reopen in Container hangs”

  • “Docker is broken” (it isn’t)

Developers who don’t understand Docker fundamentals struggle to debug Dev Containers.

This is why this article appears after Docker fundamentals in the series.

3. Overcoupling Editor and Runtime

When everything runs inside the container:

  • Editor performance depends on container health

  • Debugging infrastructure issues becomes harder

  • Simple tasks require container context

Sometimes, separation is healthier.


Comparing Dev Containers to the Series’ Approach

Let’s be precise.

Docker + WSL (This Series)

  • Editor runs on Windows

  • Runtime runs in containers

  • Clear separation of concerns

  • Docker is explicit and visible

Dev Containers

  • Editor attaches into containers

  • Runtime and tooling are unified

  • Faster onboarding

  • Less architectural transparency

Neither is “better” universally.


A Hybrid Approach (Often the Best Choice)

In practice, the most successful teams use both.

Pattern That Works Well

  • Primary workflow: Docker + WSL + VS Code Remote WSL

  • Optional: .devcontainer for onboarding or CI parity

This lets:

  • Experienced devs keep flexibility

  • New devs ramp up quickly

  • Docker knowledge remain transferable


Example: Optional Dev Container

You can make Dev Containers opt-in.

{
  "name": "Optional Dev Container",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/var/www/html",
  "remoteUser": "www-data"
}

Developers choose:

  • Open normally → WSL workflow

  • “Reopen in Container” → Dev Container workflow

Same repo. Same stack. Different entry points.


What Dev Containers Are Not

Dev Containers are not:

  • A replacement for Docker knowledge

  • A substitute for architecture decisions

  • A fix for bad repo structure

  • A performance optimization tool

They are a developer experience layer.

Used wisely, they reduce friction.
Used blindly, they increase opacity.


When I Would Choose Dev Containers

I would strongly consider Dev Containers when:

  • Team is fully on VS Code

  • Onboarding speed is critical

  • Tooling parity matters more than flexibility

  • CI and local environments must match closely

I would avoid them when:

  • Team uses multiple editors

  • Developers need deep Docker control

  • System architecture is still evolving

  • Performance tuning is frequent


The Final Takeaway

If Docker makes systems visible,
Dev Containers make systems
comfortable.

Comfort is valuable—but only after clarity.

Dev Containers are not a fork in the road.

They are an overlay.

If your Docker stack is well-designed, Dev Containers feel like a convenience.
If your Docker stack is brittle, Dev Containers feel like a trap.

The order matters:

  1. Learn Docker

  2. Design the system

  3. Then consider Dev Containers

That’s how they become empowering instead of confusing.

Containers, Actually: Building Real Local Dev Environments

Part 5 of 18

This series explores full-stack local development with Docker—from core concepts and best practices to a real Windows implementation. The goal is to understand how things run, why they work, and how to build reproducible production environment.

Up next

Containerized: Lessons Learned & What I’d Do Differently

What worked, what didn’t, and why it matters