> ## Documentation Index
> Fetch the complete documentation index at: https://commander-docs.cyshel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Setting Up Devcontainers

> Configure your repository so Commander can provision development environments automatically.

Commander provisions development environments from the [devcontainer standard](https://containers.dev/). This guide walks through setting up `devcontainer.json` for your repository — from a minimal config to a full-stack setup with Docker Compose, port forwarding, and GitHub Codespaces support.

## Quick Start

Create `.devcontainer/devcontainer.json` in your repository root:

```json theme={null}
{
  "name": "My Project",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
  "postCreateCommand": "npm install",
  "forwardPorts": [3000]
}
```

Then in Commander, go to **Settings → Environments**, select your repository, and set the mode to **Local Docker** or **GitHub Codespace**.

That's it for simple projects. Read on for full-stack setups.

## Choosing Your Base

### Image Only (Simplest)

Use a pre-built devcontainer image when you don't need custom tooling:

```json theme={null}
{
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
}
```

Available base images: `javascript-node`, `python`, `go`, `rust`, `java`, `dotnet`, `universal`. See the [full list](https://mcr.microsoft.com/en-us/catalog?search=devcontainers).

### Dockerfile (Recommended for Most Projects)

Use a Dockerfile when you need additional tools (package managers, CLIs, databases):

```json theme={null}
{
  "build": {
    "dockerfile": "Dockerfile"
  }
}
```

You can pass build arguments, set a target stage, or use a remote cache:

```json theme={null}
{
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "NODE_VERSION": "22"
    },
    "target": "development",
    "cacheFrom": ["ghcr.io/your-org/your-repo-devcontainer:latest"]
  }
}
```

Create `.devcontainer/Dockerfile`:

```dockerfile theme={null}
FROM mcr.microsoft.com/devcontainers/javascript-node:22

# Install your package manager
RUN corepack enable && corepack prepare pnpm@9 --activate

# Install any CLIs your project needs
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
    && unzip -q /tmp/awscliv2.zip -d /tmp \
    && /tmp/aws/install \
    && rm -rf /tmp/awscliv2.zip /tmp/aws
```

<Note>
  **Why Dockerfile over features?** Devcontainer features (`"features": { ... }`) work well locally but can cause issues in GitHub Codespaces — SSH may connect to the wrong container layer. Baking everything into a Dockerfile is more reliable.
</Note>

## Port Configuration

### Declaring Ports

List all ports your application uses in `forwardPorts`. Commander reads this to set up port forwarding:

```json theme={null}
{
  "forwardPorts": [3000, 5432, 6379]
}
```

### Adding Labels

Use `portsAttributes` so Commander's UI shows meaningful service names instead of just port numbers:

```json theme={null}
{
  "forwardPorts": [3000, 5432, 6379],
  "portsAttributes": {
    "3000": { "label": "Web App" },
    "5432": { "label": "PostgreSQL" },
    "6379": { "label": "Redis" }
  }
}
```

These labels appear in Commander's environment panel next to each port.

### Multiple Workspaces (Port Conflicts)

When running multiple workspaces for the same repo, ports will conflict. Enable **Offset Ports on Conflict** in Commander settings (on by default) — Commander will automatically find available ports (e.g., 3001 instead of 3000 for the second workspace).

For projects with their own multi-instance port scheme (like instance-indexed ports), declare the actual ports:

```json theme={null}
{
  "forwardPorts": [13010, 14100, 15430, 16370],
  "portsAttributes": {
    "13010": { "label": "Admin Panel" },
    "14100": { "label": "GraphQL API" },
    "15430": { "label": "PostgreSQL" },
    "16370": { "label": "Redis" }
  }
}
```

## Environment Variables

### Inside the Container

Use `containerEnv` to set environment variables inside the running container:

```json theme={null}
{
  "containerEnv": {
    "NODE_ENV": "development",
    "DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/myapp",
    "REDIS_URL": "redis://localhost:6379"
  }
}
```

### Codespace-Specific Variables

Use `remoteEnv` for variables that should only be set in GitHub Codespaces:

```json theme={null}
{
  "remoteEnv": {
    "CODESPACES": "true",
    "PROJECT_ROOT": "/var/lib/docker/codespacemount/workspace/${localWorkspaceFolderBasename}"
  }
}
```

### Commander-Side Variables

Commander also supports **Additional Env Vars** in its environment settings (Settings → Environments). These are merged with `containerEnv` at runtime — useful for secrets you don't want in version control.

## Lifecycle Commands

Commander executes these hooks at specific points in the container lifecycle. All commands run inside the container at the workspace folder.

### initializeCommand

Runs on the **host machine** (not inside the container) before the container is created. Use for host-side setup:

```json theme={null}
{
  "initializeCommand": "cp .env.example .env"
}
```

### postCreateCommand

Runs **once** after the container is first created. Use for installing dependencies:

```json theme={null}
{
  "postCreateCommand": "pnpm install"
}
```

### postStartCommand

Runs **every time** the container starts (including restarts). Use for starting services:

```json theme={null}
{
  "postStartCommand": "node scripts/dev.js"
}
```

<Note>
  **Tip:** The `postStartCommand` should be idempotent — safe to run multiple times. If it starts
  Docker containers, make sure it handles the case where they're already running.
</Note>

### postAttachCommand

Runs each time Commander attaches to the container (e.g., after reconnection). Rarely needed.

### Multi-Command Syntax

For multiple setup steps, use the object form — each command runs in parallel:

```json theme={null}
{
  "postCreateCommand": {
    "install": "pnpm install",
    "setup-db": "pnpm run db:migrate",
    "seed": "pnpm run db:seed"
  }
}
```

### Execution Order

```
initializeCommand (host)
  → container created
    → postCreateCommand (container, once)
      → postStartCommand (container, every start)
        → postAttachCommand (container, every attach)
```

## Docker-in-Docker Support

If your project runs Docker containers (e.g., PostgreSQL, Redis via Docker Compose), you need Docker available inside the devcontainer.

### Setup

1. Install Docker CLI in your Dockerfile:

```dockerfile theme={null}
# Install Docker CLI and Compose
RUN curl -fsSL https://get.docker.com | sh
RUN apt-get update && apt-get install -y docker-compose-plugin && rm -rf /var/lib/apt/lists/* \
    && groupadd -f docker && usermod -aG docker node
```

2. Mount the Docker socket in `devcontainer.json`:

```json theme={null}
{
  "mounts": ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"]
}
```

### Codespaces: Docker Volume Mounts

In GitHub Codespaces, Docker runs on the host VM, not inside the devcontainer. This means Docker volume mounts use the **host's** filesystem, not the devcontainer's.

Your `docker-compose.yml` volumes need to use the host path:

```yaml theme={null}
# Instead of:
volumes:
  - .:/app

# Use an environment variable:
volumes:
  - ${PROJECT_ROOT:-.}:/app
```

Then detect Codespaces in your startup script and set `PROJECT_ROOT`:

```javascript theme={null}
function getDockerProjectRoot() {
  if (process.env.CODESPACES === 'true') {
    const repoName = path.basename(process.cwd());
    return `/var/lib/docker/codespacemount/workspace/${repoName}`;
  }
  return process.cwd();
}

// Pass to Docker Compose via environment
const composeEnv = {
  ...process.env,
  PROJECT_ROOT: getDockerProjectRoot(),
};
```

## GitHub Codespaces Requirements

For Codespace environments to work:

### Required

1. **sshd feature** — Commander uses `gh codespace ssh` to run commands:

```json theme={null}
{
  "features": {
    "ghcr.io/devcontainers/features/sshd:1": {}
  }
}
```

2. **GitHub account connected** in Commander (Settings → Integrations)
3. **Codespaces enabled** on the repository or organization

### Recommended

* **`gh` CLI** installed locally — needed for `environment_exec` and `sync_files` agent tools
* **4-core machine or larger** for monorepos with Docker infrastructure (2-core may run out of memory)

### Machine Sizing Guide

| Machine | RAM   | Best For                                   |
| ------- | ----- | ------------------------------------------ |
| 2-core  | 8 GB  | Simple projects, no Docker infrastructure  |
| 4-core  | 16 GB | Medium projects with a database or two     |
| 8-core  | 32 GB | Monorepos with Docker Compose stacks       |
| 16-core | 64 GB | Large monorepos with build-heavy workflows |

Configure in Commander: Settings → Environments → Machine Type.

## Multiple Configurations

For monorepos or projects with different environment needs, define multiple configs using named subdirectories:

```
.devcontainer/
  frontend/
    devcontainer.json
    Dockerfile
  backend/
    devcontainer.json
    Dockerfile
  full-stack/
    devcontainer.json
    Dockerfile
```

Commander discovers all configs and lets you choose which one to use in Settings → Environments → Devcontainer Config. Each config is independent with its own Dockerfile, ports, lifecycle commands, and environment variables.

## Examples

### Simple Node.js API

A minimal setup with no Docker infrastructure:

```json theme={null}
{
  "name": "Node API",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
  "forwardPorts": [3000],
  "portsAttributes": {
    "3000": { "label": "API" }
  },
  "postCreateCommand": "npm install",
  "postStartCommand": "npm run dev"
}
```

### Python + PostgreSQL

A Dockerfile-based setup with Docker Compose infrastructure:

**`.devcontainer/devcontainer.json`**

```json theme={null}
{
  "name": "Python App",
  "build": { "dockerfile": "Dockerfile" },
  "features": {
    "ghcr.io/devcontainers/features/sshd:1": {}
  },
  "mounts": ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"],
  "forwardPorts": [8000, 5432],
  "portsAttributes": {
    "8000": { "label": "Django" },
    "5432": { "label": "PostgreSQL" }
  },
  "containerEnv": {
    "DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/myapp"
  },
  "postCreateCommand": "pip install -r requirements.txt",
  "postStartCommand": "docker compose up -d db && python manage.py runserver 0.0.0.0:8000"
}
```

**`.devcontainer/Dockerfile`**

```dockerfile theme={null}
FROM mcr.microsoft.com/devcontainers/python:3.12

RUN curl -fsSL https://get.docker.com | sh \
    && apt-get update && apt-get install -y docker-compose-plugin \
    && rm -rf /var/lib/apt/lists/*
```

### Full-Stack TypeScript Monorepo

Here's the full devcontainer setup used by the Cyshel Platform — a TypeScript monorepo with Docker Compose infrastructure (PostgreSQL, Redis, Restate, LocalStack):

**`.devcontainer/devcontainer.json`**

```json theme={null}
{
  "name": "Platform Dev",
  "build": {
    "dockerfile": "Dockerfile"
  },
  "features": {
    "ghcr.io/devcontainers/features/sshd:1": {}
  },
  "mounts": ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"],
  "forwardPorts": [
    13010, 13020, 13030, 13040, 14100, 14560, 15430, 16370, 16380, 18080, 19070, 19080, 19090
  ],
  "portsAttributes": {
    "13010": { "label": "Admin Panel" },
    "13020": { "label": "Lineapp" },
    "13030": { "label": "Editing Studio" },
    "13040": { "label": "Landing Page" },
    "14100": { "label": "GraphQL" },
    "14560": { "label": "LocalStack" },
    "15430": { "label": "PostgreSQL" },
    "16370": { "label": "Redis" },
    "16380": { "label": "Redis (Restate)" },
    "18080": { "label": "Restate Ingress" },
    "19070": { "label": "Restate Admin" },
    "19080": { "label": "Restate Service" },
    "19090": { "label": "Metrics" }
  },
  "postCreateCommand": "pnpm install",
  "postStartCommand": "node scripts/dev.js --index 0"
}
```

**`.devcontainer/Dockerfile`**

```dockerfile theme={null}
FROM mcr.microsoft.com/devcontainers/javascript-node:22

# pnpm
RUN corepack enable && corepack prepare pnpm@9 --activate

# AWS CLI
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
    && unzip -q /tmp/awscliv2.zip -d /tmp \
    && /tmp/aws/install \
    && rm -rf /tmp/awscliv2.zip /tmp/aws

# Docker CLI + Compose (uses host socket)
RUN curl -fsSL https://get.docker.com | sh
RUN apt-get update && apt-get install -y docker-compose-plugin \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd -f docker && usermod -aG docker node
```

## Commander Settings

After adding the devcontainer to your repo:

<Steps>
  <Step title="Open Settings">Press `Cmd+,` or go to **Settings → Environments**.</Step>
  <Step title="Select your repository">Click the repository tab at the top.</Step>
  <Step title="Choose environment mode">Select **Local Docker** or **GitHub Codespace**.</Step>

  <Step title="Select config (if multiple)">
    If your repo has multiple devcontainer configs, pick which one to use from the dropdown.
  </Step>

  <Step title="Enable auto-provision (optional)">
    Turn on **Auto-Provision** so new workspaces automatically get an environment.
  </Step>

  <Step title="Create a workspace">
    New workspaces will provision the environment automatically. Existing workspaces need a manual
    provision via the environment panel's rebuild button.
  </Step>
</Steps>

### Settings Reference

These settings are configured per-repository in Commander (not in devcontainer.json):

| Setting                  | Description                                | Default       |
| ------------------------ | ------------------------------------------ | ------------- |
| Environment Mode         | None, Local Docker, or GitHub Codespace    | None          |
| Auto-Provision           | Automatically provision for new workspaces | Off           |
| Devcontainer Config      | Which config file to use                   | Auto-detected |
| Preserve Volumes         | Keep Docker volumes across rebuilds        | On            |
| Offset Ports on Conflict | Auto-assign ports when conflicts occur     | On            |
| CPU Limit (Local)        | Max CPU cores for the container            | Unlimited     |
| Memory Limit (Local)     | Max RAM for the container                  | Unlimited     |
| Machine Type (Codespace) | GitHub Codespaces machine size             | 2-core        |
| Idle Timeout (Codespace) | Minutes before auto-suspend                | 30            |
| Additional Env Vars      | Extra variables (for secrets, overrides)   | Empty         |
| Additional Ports         | Extra ports beyond devcontainer.json       | Empty         |
| Additional Mounts        | Extra volume mounts                        | Empty         |
| Post-Provision Scripts   | Scripts to run after provisioning          | Empty         |

## Troubleshooting

**"No devcontainer config found"**
Commander didn't detect your config. Check that the file is at `.devcontainer/devcontainer.json` (not `.devcontainers/`), the JSON is valid (JSONC with comments is fine), and the file is committed and present in the workspace's worktree.

**"No projects found in /app"**
Your Docker Compose volumes can't access the workspace files. Set `PROJECT_ROOT` to the Docker host path — see [Docker Volume Mounts](#codespaces-docker-volume-mounts) above.

**"Cannot find module" in postStartCommand**
The command runs from the workspace folder, but your working directory might not be what you expect. Use absolute paths or explicitly `cd /workspaces/my-project` first.

**Port forwarding exits immediately**
Services aren't listening yet. Commander retries port forwarding automatically up to 5 times. Ensure your `postStartCommand` actually starts the services.

**Container build fails**
Check your Dockerfile builds outside Commander: `docker build -t test .devcontainer/`. If using `cacheFrom`, ensure the cache image is accessible. Large images may time out — use multi-stage builds.

**Codespace shows Alpine instead of your image**
Devcontainer features didn't install correctly. Use a Dockerfile instead of `"image"` + `"features"`. See [Choosing Your Base](#choosing-your-base).

**SSH connects to wrong container**
Add the `sshd` feature to your devcontainer.json — see [Codespaces Requirements](#github-codespaces-requirements).

**Docker commands fail inside container**
Verify the Docker socket mount is in your config. Check the container user is in the `docker` group. In Codespaces, Commander automatically fixes Docker socket permissions.

**2-core codespace runs out of memory**
Your project is too large for the smallest machine. Increase to 4-core or 8-core in Settings → Environments → Machine Type.

**Environment stuck in "provisioning" or "building"**
Check Docker is running (for local mode) and internet connectivity (for image pulls). Try destroying the environment and re-provisioning via the environment panel.

For more details on the environment UI and agent tools, see [Environments](/features/environments).
