# Claude Code в dev-контейнере

Курс: Claude Code: уровень профи — https://zero2claude.ru/learn/claude-code-pro
Канонический URL: https://zero2claude.ru/learn/claude-code-pro/6-1-dev-konteyner

Dev-контейнер держит внутри весь процесс Claude Code, а не только команды оболочки. Фича добавляет CLI в любой контейнер, именованный том сохраняет вход между пересборками, а эталонный контейнер с файрволом и обычным пользователем — то место, где допустим запуск без вопросов.

В уроке про песочницу Claude Code ограничивал **команды оболочки**. Dev-контейнер идёт дальше: это Docker-контейнер, к которому подключается редактор, — на твоём компьютере или в облаке вроде GitHub Codespaces. Внутри него живёт **весь процесс** Claude Code: инструменты правки файлов, MCP-серверы, хуки. Команды выполняются в контейнере, а правки попадают в твой репозиторий через монтирование.

## Три слоя

Каждый следующий слой опирается на предыдущий:

| Слой | Что даёт |
|---|---|
| Фича Claude Code | CLI внутри любого dev-контейнера |
| Именованный том в `~/.claude` | Вход переживает пересборку |
| Эталонный контейнер | Обычный пользователь и файрвол — место для запуска без вопросов |

## Слой 1: фича

Фича добавляется одной строкой в `.devcontainer/devcontainer.json`:

```json
{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "lts" },
    "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
  },
  "postCreateCommand": "sudo apt-get update && sudo apt-get install -y bubblewrap socat"
}
```

Подвох в теге `:1.0`. Он закрепляет **скрипт установки фичи**, а не Claude Code: фича ставит последний релиз, и дальше тот обновляется сам. `postCreateCommand` доставляет `bubblewrap` и `socat` — без них на Linux не работает песочница для Bash.

## Слой 2: вход, который не пропадает

Войди в Claude Code, пересобери контейнер — и `claude auth status` вернёт код 1. Пересборка сохраняет папку с проектом, но выбрасывает домашнюю папку, а с ней и `~/.claude`. В Codespaces то же самое: после **Rebuild Container** `/workspaces` на месте, а вход пропал.

Решение — **именованный том** Docker в `~/.claude` плюс переменная окружения `CLAUDE_CONFIG_DIR` с тем же путём. Том живёт отдельно от контейнера, поэтому вход и настройки в нём переживают пересборку:

```json
"mounts": [
  "source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"containerEnv": {
  "CLAUDE_CONFIG_DIR": "/home/node/.claude"
}
```

Если вход всё равно пропадает — проверь, что `target` тома и `CLAUDE_CONFIG_DIR` называют **один и тот же** путь.

## Слой 3: эталонный контейнер

В репозитории Claude Code лежит эталонный `.devcontainer/` из трёх файлов: `devcontainer.json`, `Dockerfile` и `init-firewall.sh`. Он добавляет:

- **обычного пользователя** `node` вместо root;
- **файрвол**: исходящий трафик идёт только на адреса из списка в `init-firewall.sh`.

Именно такой контейнер документация поддерживает для прогонов с `--dangerously-skip-permissions`. От root CLI этот флаг вообще не примет.

Перед первой сборкой:

1. **Прочитай все три файла.** `init-firewall.sh` выполняется от root при каждом старте контейнера — как `postStartCommand`.
2. **Добавь домены входа.** Рядом с `api.anthropic.com` в списке должны быть `claude.ai` и `platform.claude.com`, иначе вход и его обновление упрутся в файрвол. Нужен PyPI или приватный реестр пакетов — тоже допиши в список и пересобери.
3. **Закрепи версию.** В аргументе сборки `CLAUDE_CODE_VERSION` замени `latest` на конкретную версию, а в `containerEnv` добавь `"DISABLE_AUTOUPDATER": "1"`.

Проверить файрвол просто: внутри контейнера запрос к `example.com` должен падать, а к `api.github.com` — проходить.

Когда всё сходится, один прогон без присмотра внутри контейнера — с теми же лимитами, что в уровне про автономный запуск:

```bash
claude -p "Добавь в начало README.md однострочное описание репозитория" \
  --dangerously-skip-permissions --model sonnet --max-turns 10
```

## Уборка

Том переживает контейнер — а значит, и твой вход в нём. Закончил — выполни `claude auth logout` внутри контейнера, затем удали контейнер и тома, чьи имена начинаются с `claude-code-config-` и `claude-code-bashhistory-` (`docker volume ls`, потом `docker volume rm`). Codespace удали отдельно: остановленный он всё равно занимает место.

**Осторожно.** 
Контейнер не мешает проекту унести всё, что доступно внутри: в том числе учётные данные Claude Code в ~/.claude. Расширение Dev Containers в VS Code ещё и пробрасывает в контейнер твои Git-учётки и SSH-агент, а эталонный файрвол пропускает GitHub — так что прогон может запушить от твоего имени. Используй контейнер только с репозиториями, которым доверяешь, и никогда не монтируй туда ~/.ssh или файлы с облачными ключами. А после прогона без вопросов прочитай .git/config, список .git/hooks и дифф .devcontainer — до пересборки и до git commit.
