Thumbnail image for Using Devcontainers Without Overcomplicating Your Setup
July 12, 2026
engineering series
Devcontainers
DeveloperExperience
Docker

Using Devcontainers Without Overcomplicating Your Setup

Devcontainers work best when they stay small. Here is the approach that has held up over another year of using them daily.

Last year, I wrote about why I use Devcontainers. Since then, they’ve remained my default development environment for almost every personal project outside of native Apple development.

What has changed isn’t whether I use them, but how I use them.

After another year of building with Devcontainers, both professionally and in personal projects, I’ve learned that the biggest challenge isn’t adopting them. It’s resisting the temptation to let them become another system you have to maintain.

That mattered most on a larger team, where a complicated stack meant a new laptop rarely ended up configured the same way twice, and onboarding could drag on for weeks before someone was doing any real feature work. Moving that environment into a devcontainer instead of a setup document was the first real fix, even if it never solved everything.

Start Small

One of the easiest mistakes is trying to put everything into the container.

Docker Compose.

Half a dozen services.

Every CLI you’ve ever used.

Helper scripts.

Custom shell configuration.

It starts with good intentions. After all, if you’re defining the environment, why not define everything?

The problem shows up later. Every additional dependency you put in the container is something you now have to maintain, on top of the project itself.

Today, my personal Devcontainers are deliberately minimal, using the open Dev Container Specification directly rather than layering extra tooling on top of it. A minimal devcontainer.json for one of my TypeScript projects looks close to this:

{
  "name": "project",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode"
      ]
    }
  }
}

A prebuilt base image, the language runtime it already includes, and a small, fixed list of editor extensions. No Compose file for a single service. No extra tooling containers. No shell customization baked into the image.

If a project genuinely needs a database or another service alongside it, that’s the one case I still reach for a docker-compose.yml, and even then only for the services themselves, not for anything else.

Everything else stays out.

Know Where the Boundary Is

This was probably the biggest lesson from using Devcontainers in a larger team.

The container doesn’t own your computer.

It owns your project.

That distinction matters.

The host machine is still responsible for:

The container is responsible for:

At work, we still dealt with Windows, WSL, Docker Desktop and corporate tooling like Zscaler. Devcontainers couldn’t solve those problems because they weren’t project problems in the first place. Getting Docker-in-Docker and Minikube to behave behind that kind of security tooling was its own recurring headache, and nobody thanked the devcontainer when it was the thing that broke.

Once we stopped trying to make the container responsible for everything, the setup became much easier to maintain. It’s the same principle I keep coming back to for solo work: keep the operational surface small and let something else own what it’s already good at, as in Lightweight Architecture for Solo Builders.

Rebuild Instead of Repair

One habit I’ve picked up is treating the development environment as disposable.

If I accidentally pollute the container, install conflicting dependencies or end up in a strange state, I don’t spend much time trying to repair it.

I rebuild it.

That’s only possible because the environment itself is version-controlled alongside the project.

Instead of carefully preserving a handcrafted development setup, I assume I should be able to recreate it at any point.

They Still Save Time

Despite their limitations, Devcontainers continue to solve the problems I actually care about.

I can return to a project months later without remembering which tools it needed.

New contributors start from the same environment.

Project-specific tooling doesn’t accumulate across my machine.

And because the environment is already defined as a portable specification rather than a set of personal setup notes, running the same project in GitHub Codespaces becomes almost effortless.

Those benefits have stayed true since I first adopted them, and they compound differently on a team than they do solo. A standardized environment removes an entire category of onboarding cost and cross-machine inconsistency, the kind of friction that otherwise falls on whoever happens to be free to debug it, rather than being handled by the setup itself.

When I Don’t Use Them

I don’t use Devcontainers everywhere.

Native Apple development already revolves around Xcode, so my Swift projects don’t use them.

Likewise, I wouldn’t create one for a quick script or a throwaway experiment.

Like any engineering tool, they’re useful because they solve a specific problem, not because they should become a default for every repository.

Final Thoughts

The biggest lesson over the past year wasn’t discovering another Devcontainer feature.

It was learning to remove things.

The simpler my Devcontainers became, the easier they were to understand, maintain and rebuild.

Ironically, that’s also when they became the most useful. It’s the same restraint I try to apply across the rest of my stack, described in My Solo Developer Stack in 2026.

If you’re new to Devcontainers and want to know why I adopted them in the first place, you can also read Why I Use Devcontainers.

If you're still here, might as well subscribe :)
Get notified via

Related Articles