Skip to main content
Commander provisions development environments from the devcontainer standard. 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:
{
  "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:
{
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
}
Available base images: javascript-node, python, go, rust, java, dotnet, universal. See the full list. Use a Dockerfile when you need additional tools (package managers, CLIs, databases):
{
  "build": {
    "dockerfile": "Dockerfile"
  }
}
You can pass build arguments, set a target stage, or use a remote cache:
{
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "NODE_VERSION": "22"
    },
    "target": "development",
    "cacheFrom": ["ghcr.io/your-org/your-repo-devcontainer:latest"]
  }
}
Create .devcontainer/Dockerfile:
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
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.

Port Configuration

Declaring Ports

List all ports your application uses in forwardPorts. Commander reads this to set up port forwarding:
{
  "forwardPorts": [3000, 5432, 6379]
}

Adding Labels

Use portsAttributes so Commander’s UI shows meaningful service names instead of just port numbers:
{
  "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:
{
  "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:
{
  "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:
{
  "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:
{
  "initializeCommand": "cp .env.example .env"
}

postCreateCommand

Runs once after the container is first created. Use for installing dependencies:
{
  "postCreateCommand": "pnpm install"
}

postStartCommand

Runs every time the container starts (including restarts). Use for starting services:
{
  "postStartCommand": "node scripts/dev.js"
}
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.

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:
{
  "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:
# 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
  1. Mount the Docker socket in devcontainer.json:
{
  "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:
# Instead of:
volumes:
  - .:/app

# Use an environment variable:
volumes:
  - ${PROJECT_ROOT:-.}:/app
Then detect Codespaces in your startup script and set PROJECT_ROOT:
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:
{
  "features": {
    "ghcr.io/devcontainers/features/sshd:1": {}
  }
}
  1. GitHub account connected in Commander (Settings → Integrations)
  2. Codespaces enabled on the repository or organization
  • 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

MachineRAMBest For
2-core8 GBSimple projects, no Docker infrastructure
4-core16 GBMedium projects with a database or two
8-core32 GBMonorepos with Docker Compose stacks
16-core64 GBLarge 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:
{
  "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
{
  "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
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
{
  "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
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:
1

Open Settings

Press Cmd+, or go to Settings → Environments.
2

Select your repository

Click the repository tab at the top.
3

Choose environment mode

Select Local Docker or GitHub Codespace.
4

Select config (if multiple)

If your repo has multiple devcontainer configs, pick which one to use from the dropdown.
5

Enable auto-provision (optional)

Turn on Auto-Provision so new workspaces automatically get an environment.
6

Create a workspace

New workspaces will provision the environment automatically. Existing workspaces need a manual provision via the environment panel’s rebuild button.

Settings Reference

These settings are configured per-repository in Commander (not in devcontainer.json):
SettingDescriptionDefault
Environment ModeNone, Local Docker, or GitHub CodespaceNone
Auto-ProvisionAutomatically provision for new workspacesOff
Devcontainer ConfigWhich config file to useAuto-detected
Preserve VolumesKeep Docker volumes across rebuildsOn
Offset Ports on ConflictAuto-assign ports when conflicts occurOn
CPU Limit (Local)Max CPU cores for the containerUnlimited
Memory Limit (Local)Max RAM for the containerUnlimited
Machine Type (Codespace)GitHub Codespaces machine size2-core
Idle Timeout (Codespace)Minutes before auto-suspend30
Additional Env VarsExtra variables (for secrets, overrides)Empty
Additional PortsExtra ports beyond devcontainer.jsonEmpty
Additional MountsExtra volume mountsEmpty
Post-Provision ScriptsScripts to run after provisioningEmpty

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 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. SSH connects to wrong container Add the sshd feature to your devcontainer.json — see 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.