What is a dev container?
Development containers, or dev containers, are a way to package the entire development environment (tools, dependencies, and configuration) into a containerized format. What this gives us is a way to have code that you are working on behave the same no matter where you run it, no matter if it is your local machine, someone else’s, or in the private or public cloud.
They rely on the Development Container Specification, which is a way to provide containers with more content and metadata needed to enable development inside of them. It is a way to define a repeatable development environment that includes the execution environment that the application needs. For this, the devcontainer.json file serves as the central configuration file in the project repository.
Now, how can a dev container be created and configured from that? Well, that is done through the development container CLI. This is actually used by tools like code editors when they detect a devcontainer.json file, but it can also be used by individual users. So no need for some tool, as this can be installed via “npm install -g @devcontainers/cli”
This means that it is tool-agnostic, and no matter if you are using some IDE or your terminal, the same devcontainer.json file will be used to produce the same development environment for a dev container.
How to set up dev container
Prerequisites
For starters, there are some prerequisites you need before setting up the dev container itself. And those are:
Container runtime – This is needed to create and manage your containers. Docker is the most commonly used and best documented runtime. On Mac and Windows, you would use Docker Desktop (with the WSL2 backend required on Windows) and Docker Engine for Linux.
Integrated Development Environment (IDE) – While not obligatory, it is easier to use them, and not all IDEs support dev containers, so you will need one that supports it. Two of the most popular ones are VS Code (with the Dev Containers extension) and JetBrains IDEs.
The setup
Now, after all of that is installed and set up, let’s start with the setup of the development container itself.
The first thing you would need to add is the .devcontainer folder that will be located in the root of your repo. Then, inside, you will add a devcontainer.json file, which acts as a config file used to build and run the container.
Example of devcontainer.json file:
{ “name”: “Python Project”, “image”: “mcr.microsoft.com/devcontainers/python:3.14.7”, “features”: { “ghcr.io/devcontainers/features/docker-in-docker:4.1.0”: {} }, “customizations”: { “vscode”: { “settings”: { “python.defaultInterpreterPath”: “/usr/local/bin/python” }, “extensions”: [ “ms-python.python”, “ms-python.vscode-pylance”, “ms-python.pylint” ] } }, “forwardPorts”: [8000], “portsAttributes”: { “8000”: { “label”: “Flask app”, “onAutoForward”: “notify” } }, “postCreateCommand”: “pip install -e .”, “remoteUser”: “vscode”}
Let’s break down what this configuration includes:
name – is a human-readable label for your dev container, which is shown in your IDE.
image – the name of an image in a container registry that supporting services/tools should use to create the dev container.
features – tools to be added to the primary container.
In this example, Docker-in-Docker will allow Docker commands to be run from within the dev container itself. This is useful when your development depends on running Docker commands for different purposes like building and testing images as part of the project, or running tests, for example.
customizations – these are config for tools and are scoped by IDEs.
forwardPorts – an array of port numbers that should be forwarded from the primary container to the local machine.
portsAttributes – this property allows you to set default properties for specific forwarded ports.
postCreateCommand – A command string or list of command arguments to run after the container is created.
remoteUser – defines the user that supporting services and tools, defined in devcontainer.json, run as in the container
It is worth noting that an image is one of the options for configuring dev containers, but for that, we have two other options, and those are:
build – instead of pulling a pre-build image, with build.dockerfile you can point to the local Dockerfile, with the path relative to the devcontainer.json. With this, you can fully customize container setup if you need specific packages or custom installation steps, as well as a base image that is not publicly available.
dockerComposeFile – references docker-compose.yaml file, which allows you to define and run multiple services together, like for example, your app and database.
Lifecycle hooks
Now, besides the already mentioned postCreateCommand command, let’s mention other lifecycle hooks available, with an explanation of at which point in the container’s life they are run:
HookTime of executioninitializeCommandRuns on the host machine during initialisation, and on container creation on subsequent starts.onCreateCommandRuns first when the dev container is created and executes inside the container.updateContentCommandRuns after onCreateCommand whenever new content is available in the source tree during the creation process.postCreateCommandThe third and last command that finalises container setup, it runs once the dev container has been assigned to a user for the first time.postStartCommandRuns each time the container is successfully started.postAttachCommandRuns each time a tool has successfully attached to the container.
When and why to use it
Are dev containers perfect for every project? Not quite.
So let’s see when to use them.
If you have a setup that matches the following scenarios, you could improve your development experience by adopting them and utilizing the advantages that they offer.
These are some of the cases where you would want to use them:
CI/CD parity – an underrated benefit of dev containers is the ability to closely mirror, or even reuse, the same container image used in your CI/CD pipeline. With the usage of tools like devcontainers/ci you can run existing devcontainer.json or Dockerfile setup inside of CI providers. This gives you better assurance that if something runs successfully on your local machine that it will work the same in the CI. This eliminates the potential drift between local and CI that can come over time.
Enforcing team-wide tooling – this is especially useful for bigger teams, and when you want to standardize the development environment, since the container is pre-configured with needed tools, dependencies, and so on. This allows programmers to focus less on setup and more on coding. In this case, you are also potentially reducing “works on my machine” situations.An alternative for this could be having documentation-based onboarding, where you would have detailed README with instructions, or setup script to run, though this still requires a developer to follow certain steps to make it work correctly.
The multi-project version conflict – in cases where you have multiple projects, and let’s say project A needs Python 3.9 and project B needs Python 3.12 version, in that case it can become a problem to maintain versions, and system paths could eventually break. Dev containers solve this by isolating these runtimes by project directory and keeping the host machine clean.For comparison, you could maybe try to solve this with the use of a lock-based package manager (uv.lock, package-lock.json, for ex.), and though they do handle dependency isolation, dev containers go further by also isolating system-level tools and OS packages.
Fast onboarding – for expanding teams, fast onboarding is key, and with the use of dev containers, this becomes much quicker and more efficient, as it doesn’t require a manual installation of specific versions of languages, dependencies, and extensions.Again, teams try to solve this with onboarding documentation to explain what is needed to start working on your project, which requires a new developer to manually set up the needed things, and can take significant time to make it all work.
When and why NOT to use it
Now that the use cases for when to use it have been covered, even though dev containers have powerful capabilities, let’s see when it is not a good approach.
Cases to rethink their usage:
Lightweight and solo projects – if you are building a simple website or a project, and especially if it is a solo project, in that case, the overhead of the setup might not be valuable to what it offers you.
Configuration management – besides the initial configuration setup, it later becomes something extra that the team needs to manage. And with project growth, the configuration will probably change as well.
Secrets and credential management – some secret management tools rely on host-level interaction or browser-based authentication flows, which aren’t easily accessible from isolated containers, it can get tricky to make this work and can require some additional workarounds. In cases like this, teams often just run certain tools or scripts outside the container to authenticate or access secrets.
Usage of Git – actions like git push that need a commit signature can run into friction when running from inside a container. This is because verified commits often rely on SSH and GPG signing, which require access to certain resources on the host machine, such as private keys and cryptographic tools.
Resource-intensive – this comes up especially when you are running multiple containers at the same time, which may require better hardware. This is noticeable on Macs, as Docker runs inside a VM rather than natively. As a workaround, Windows runs the Linux kernel directly.
Loss of unsaved state on rebuild – dev containers are frequently rebuilt, and every time this happens, everything that existed just inside of a container is wiped out. Then it needs to be recreated from scratch, and those are things like manually installed packages, shell history, or cached build artifacts, to name a few.
Dev containers and AI sandboxing
As artificial intelligence becomes more widely used, especially vibe-coding, it is reaching a point where isolating code agents is increasingly important. The problem with running an AI agent on a local machine is that it acts like you and has your privileges, and in some cases it can take unwanted actions after it starts “hallucinating”.
That is now becoming a security concern that needs to be handled. This is where dev containers come into play. With them, you create a fully isolated environment for your AI agent to run in, with your host protected as it can’t touch your home directory.
Dev containers give you control over customization by writing your own Dockerfile to set up the development environment per your needs. The concern with AI agents is that they have access to the host filesystem, which, with this, can be configured so that it only has access to projects it’s working on.
Setting up AI agents in devcontainers is quite simple. For example, for Claude Code, as per their official guidance, you just need to add this to the features property:
“features”: { “ghcr.io/anthropics/devcontainer-features/claude-code:1.0.5”: {}},
After adding this, you rebuild the container, sign in to Claude Code inside it, and every command you run stays in that isolated environment, while edits show up in your local repository as your work.
Today, this is probably the safest path you can take to minimize the harm that agents can do. Though nothing is completely safe, it is best to still watch out for anything that is suspicious.
Conclusion
To conclude, dev containers are useful in modern development, as they boost productivity. They ensure a consistent development environment without the need to install any tools manually, besides the prerequisites. Using this approach means some setup complexity at the beginning, but it allows for quick, easy onboarding for new team members while maintaining consistency and portability.
That said, dev containers are not a one-size-fits-all solution. As covered in one of the previous sections, there are some cases where they introduce their own overhead. This can introduce more friction then it helps out with the project, especially for smaller or solo projects.
If used in the right setting, dev containers can be powerful and helpful, so make sure you weigh your needs before committing to one.