Skip to main content

Development Environment (Dev Containers)

We standardize on VS Code Dev Containers to ensure a consistent, isolated, and high-fidelity development environment for every engineer. This approach eliminates "works on my machine" inconsistencies and reduces project onboarding time to minutes.

Dev Container Fundamentals

A Dev Container is a standardized environment defined by a devcontainer.json configuration. It enables you to utilize a Docker container as a full-featured integrated development environment.

Engineering Workflow

  1. Clone the service repository.
  2. Open the directory in VS Code.
  3. Reopen in Container when prompted by the IDE.
  4. Environment Boot: VS Code builds the environment and installs pre-defined tools.
  5. Ready: Runtimes, compilers, and extensions are pre-configured and active.
Architecture Detail

The VS Code UI executes on your host machine, while the VS Code Server, terminal, debugger, and all runtimes (Node, Python, Go) execute within the container.


Strategic Comparison: Local vs. Container

FeatureHost Machine DevelopmentDev Container Strategy
ConsistencyDrift between developer OS/versions.Identical for all engineers.
OnboardingManual execution of 20+ steps.Automated one-click setup.
IsolationDependency pollution on host.Cleanly isolated per project.
Conflict ManagementVersion conflicts (e.g., Node 18 vs 20).Project-specific runtimes.

Orchestration with Docker Compose

For microservices, we integrate Dev Containers with Docker Compose to manage the application and its sidecar dependencies (e.g., Database, Cache) in unison.


Configuration Standard (devcontainer.json)

A standard service configuration integrates infrastructure layers and developer experience tools:

devcontainer.json (Excerpts)
{
"name": "Platform Service",
"dockerComposeFile": [
"../../infra/compose.yaml",
"../../infra/compose.dev.yaml"
],
"service": "api",
"workspaceFolder": "/workspace/api",
"remoteUser": "vscode",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"dbaeumer.vscode-eslint"
],
"settings": { "editor.formatOnSave": true }
}
},
"postCreateCommand": "npm install && npm run db:migrate"
}

Monorepo vs Polyrepo: Compose File Paths

The dockerComposeFile paths in devcontainer.json differ based on your repository strategy:

StrategydockerComposeFile ExampleExplanation
Polyrepo"../../infra/compose.yaml"infra/ is in a sibling repo or shared directory.
Monorepo"../../compose.yaml"Compose files are at the monorepo root; devcontainer.json is per-service under services/.
Monorepo devcontainer.json
{
"name": "API Gateway",
"dockerComposeFile": [
"../../compose.yaml",
"../../compose.dev.yaml"
],
"service": "api-gateway",
"workspaceFolder": "/workspace/services/api-gateway"
}
Key Difference

In a monorepo, the workspaceFolder and service name must match the service's subdirectory path. In a polyrepo, workspaceFolder typically maps to the repo root.


Dev Container Features

Features are pre-packaged installable units that add tools, runtimes, or CLI utilities to a Dev Container without modifying the Dockerfile. They are the modular, declarative way to extend your development environment.

Usage

Add Features in devcontainer.json under the "features" key:

{
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/terraform:1": { "version": "1.7" }
}
}

Features vs. Dockerfile vs. Compose

ApproachWhen to UseTrade-offs
Dev Container FeaturesStandard tools shared across projects (AWS CLI, Git, Terraform).Declarative, auto-updated, no Dockerfile changes. Limited to available catalog.
Dockerfile RUNCustom or project-specific tool installation.Full control, but increases image build time and Dockerfile complexity.
Compose commandRuntime configuration, env setup, service orchestration.Affects container lifecycle, not tool availability.
Recommendation

Use Features for anything available in the Dev Container Feature catalog. Only fall back to Dockerfile installation for tools not in the catalog or requiring custom build steps.


Dev Container CLI

The Dev Container CLI (@devcontainers/cli) is the reference implementation of the Development Containers Specification. It allows you to build and manage dev containers from the command line, without requiring VS Code.

Installation

# Install globally
npm install -g @devcontainers/cli

# Or use directly via npx
npx @devcontainers/cli --help

Common Commands

CommandDescription
devcontainer upCreate and start a dev container from devcontainer.json
devcontainer exec <cmd>Execute a command inside a running dev container
devcontainer buildBuild a dev container image (for publishing)
devcontainer read-configurationRead and resolve the full devcontainer.json config

Typical Use Cases

Run commands in a dev container without VS Code:

devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . npm test

Pre-build and publish a dev container image in CI/CD:

devcontainer build --workspace-folder . --push true --image-name ghcr.io/my-org/devcontainer:latest

This is useful for speeding up container startup by pre-building the image in a CI pipeline and pushing it to a container registry.

When to Use the CLI

Use the CLI when you need to interact with dev containers outside of VS Code — such as in CI/CD pipelines, headless servers, or non-VS Code editors. For daily development, the VS Code Dev Containers extension provides the most seamless experience.


Multi-Root Workspaces (.code-workspace)

A .code-workspace file defines a multi-root workspace, allowing you to manage multiple projects in a single VS Code window. This is essential for both monorepo and polyrepo workflows.

File Structure

*.code-workspace
{
"folders": [
{ "path": "services/api" },
{ "path": "services/web" },
{ "path": "packages/shared" }
],
"settings": {
"editor.formatOnSave": true
},
"extensions": {
"recommendations": ["ms-python.python", "dbaeumer.vscode-eslint"]
}
}
FieldPurpose
foldersList of folders to include in the workspace
settingsWorkspace-scoped VS Code settings (override user settings)
extensions.recommendationsSuggested extensions when opening this workspace

Monorepo Example

Single repository with multiple service directories:

platform.code-workspace
{
"folders": [
{ "path": "services/api-gateway" },
{ "path": "services/user-service" },
{ "path": "services/notification-service" },
{ "path": "infrastructure/terraform" }
],
"settings": {
"editor.formatOnSave": true,
"files.exclude": { "**/node_modules": true }
}
}

Polyrepo Example

Multiple repositories under a shared parent directory:

projects/
├── project-core/ # Main application repo
├── project-shared/ # Shared library repo
├── project-infra/ # Infrastructure repo
└── platform.code-workspace
platform.code-workspace
{
"folders": [
{ "path": "../project-core" },
{ "path": "../project-shared" },
{ "path": "../project-infra" }
],
"settings": {
"editor.formatOnSave": true
}
}
Path Resolution

In polyrepo setups, paths in .code-workspace are relative to the workspace file location. Use ../ to reference sibling repositories.

Integration with Dev Containers

.code-workspace files work seamlessly with Dev Containers. Each folder in the workspace can have its own devcontainer.json:

projects/
├── project-core/
│ └── .devcontainer/
│ └── devcontainer.json
├── project-shared/
│ └── .devcontainer/
│ └── devcontainer.json
└── platform.code-workspace

Open the workspace in VS Code, then reopen individual folders in containers as needed via the Dev Containers extension.


Security: SSH Agent Forwarding

To maintain security, private keys remain on your host machine. We utilize SSH Agent Forwarding to allow the container to request authentication signatures from the host.

Host Preparation

Ensure your private key is registered with the host's SSH agent:

ssh-add ~/.ssh/id_ed25519

Container Verification

# Execute within the container terminal
echo $SSH_AUTH_SOCK # Expected: /run/host-services/ssh-auth.sock
ssh-add -l # Lists keys available from host agent

Operational Troubleshooting

SymptomPrimary CauseRemediation
"fatal: detected dubious ownership"Host/Container UID mismatchSet updateRemoteUserUID: true in devcontainer.json.
SSH Auth FailureKey not registered on hostExecute ssh-add -l on host to verify key availability.
Startup LatencyHeavy postCreateCommandMove stable tool installation to the base Dockerfile.