Przejdź do głównej zawartości

Dev container dla Claude Code to plik .devcontainer/devcontainer.json w repozytorium, który instaluje Claude Code przez oficjalny Dev Container Feature Anthropic, dzięki czemu każde polecenie Claude’a wykonuje się w identycznym, odizolowanym środowisku, a nie na hoście. Konfiguracja zespołowa dodaje wolumen utrzymujący logowanie, politykę zarządzaną, firewall ruchu wychodzącego i test dymny w CI.

Nowa osoba dołącza w poniedziałek i traci dwa dni na wersje Node’a, lokalnego PostgreSQL-a i zmienną proxy, której nikt nie spisał. W tym samym czasie ktoś z zespołu uruchamia Claude Code bez nadzoru na laptopie z kluczami SSH i produkcyjnymi poświadczeniami do chmury. Jeden dev container rozwiązuje oba problemy, ale tylko wtedy, gdy jest zbudowany poprawnie: wiele gotowych konfiguracji instaluje Claude Code na nieobsługiwanej wersji Node’a, montuje do kontenera poświadczenia z hosta i powierza izolację sieci samej zmiennej proxy.

Ta strona jest dla programisty, który stawia kontener, i dla tech leada, który odpowiada za niego w zespole. Opisuje uruchamianie Claude Code wewnątrz kontenera. Pisanie Dockerfile’i i manifestów Kubernetes za pomocą Claude Code opisuje strona Docker i Kubernetes z Claude Code, a porównanie trybów uprawnień i sandboksów we wszystkich narzędziach — uprawnienia, sandboksy i tryby zatwierdzania.

  • devcontainer.json, który instaluje Claude Code oficjalnym feature’em i przeżywa przebudowę bez ponownego logowania.
  • Wariant z Dockerfile’em i przypiętą wersją, z polityką organizacji w /etc/claude-code/managed-settings.json.
  • Firewall ruchu wychodzącego typu „domyślnie odrzucaj” i jasny obraz luk w referencyjnym skrypcie Anthropic.
  • Zasady, kiedy --dangerously-skip-permissions jest dopuszczalne, a kiedy lepszym wyborem jest tryb auto albo sandbox Basha.
  • Job w CI, który przy każdej zmianie dowodzi, że kontener jest poprawny, więc nikt nie czyta devcontainer.json linijka po linijce.
  • Trzy prompty do skopiowania: szkielet, audyt i weryfikacja.

Jakiej izolacji potrzebujesz: dev containera, sandboksa Basha czy obu?

Dział zatytułowany „Jakiej izolacji potrzebujesz: dev containera, sandboksa Basha czy obu?”

Claude Code ma dwie warstwy izolacji, które łatwo pomylić. Sandbox Basha jest wbudowany w Claude Code i ogranicza każde polecenie powłoki mechanizmami systemu (Seatbelt na macOS, bubblewrap na Linuksie i WSL2). Dev container odcina od hosta cały toolchain, łącznie z samym Claude Code.

SytuacjaUżyjDlaczego
Jedna osoba, zaufane repozytorium, sesje interaktywneSandbox Basha (/sandbox) na hościeBez narzutu Dockera; znika większość pytań o polecenia powłoki
Zespół, który potrzebuje identycznych narzędzi i szybszego onboardinguDev container z feature’em Claude CodeJeden plik definiuje środowisko; polecenia działają w kontenerze, nie na laptopie
Przebiegi bez nadzoru z mniejszą liczbą pytań o zgodę albo bez nichDev container + firewall wychodzący + użytkownik bez rootaOgranicza to, do czego sięgnie i co zmieni sesja z pominiętymi uprawnieniami
Pełny stos (baza, usługi, dane testowe) na każde zadanie agentaEfemeryczne środowiskaDev container to jedno długowieczne środowisko na programistę, a nie jedno na zadanie
Hostowane, jednorazowe sandboksy (E2B, Daytona, container-use)Porównanie sandboksów dla agentówKatalog narzędzi, poza zakresem tej strony

Warstwy da się łączyć. Wewnątrz dev containera sandbox Basha nadal dokłada granicę na poziomie pojedynczego polecenia, ale ma to swój koszt, opisany w sekcji o awariach.

Dodaj Claude Code do devcontainer.json oficjalnym feature’em

Dział zatytułowany „Dodaj Claude Code do devcontainer.json oficjalnym feature’em”

Anthropic publikuje Claude Code Dev Container Feature. Instaluje on CLI, doinstalowuje Node.js, gdy obraz bazowy go nie ma, i dodaje rozszerzenie Claude Code do VS Code, gdy otwierasz kontener w VS Code lub Codespaces. Używaj go zamiast npm install -g w postStartCommand, który instaluje wszystko od nowa przy każdym starcie. Zacznij od obrazu, który ma już Node 22 lub nowszy, bo tego wymaga pakiet npm (engines: node >=22.0.0 dla @anthropic-ai/claude-code 2.1.283, sprawdzone 2026-09-26). Na starszym Node npm wypisuje tylko ostrzeżenie EBADENGINE i instaluje CLI, którego twoje środowisko nie obsługuje. Awaryjna instalacja w samym feature’ze nie jest bezpieczniejsza: na obrazie Debiana/Ubuntu lub dystrybucji z yum bez Node’a jego install.sh dodaje repozytorium NodeSource 18.x, a inne dystrybucje dostają taki nodejs, jaki ma ich menedżer pakietów (sprawdzone na gałęzi main, wersja feature’a 1.0.5, 2026-09-26).

  1. Zapisz poniższy plik jako .devcontainer/devcontainer.json w katalogu głównym repozytorium albo dołącz bloki features, mounts i containerEnv do istniejącego pliku:

    {
    "name": "app + Claude Code",
    "image": "mcr.microsoft.com/devcontainers/typescript-node:22",
    "features": {
    "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
    },
    "remoteUser": "node",
    "mounts": [
    "source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
    ],
    "containerEnv": {
    "CLAUDE_CONFIG_DIR": "/home/node/.claude"
    },
    "postCreateCommand": "npm ci",
    "forwardPorts": [3000]
    }

    Tag :1.0 śledzi skrypt instalacyjny feature’a z linii 1.0.x (do dokładnego przypięcia użyj :1.0.5), a nie wydanie Claude Code. Feature instaluje najnowsze Claude Code, które potem samo się aktualizuje w kontenerze.

  2. Przebuduj kontener. W VS Code lub Cursorze otwórz paletę poleceń i uruchom Dev Containers: Rebuild Container. Z terminala użyj Dev Containers CLI: npx @devcontainers/cli up --workspace-folder .

  3. Otwórz terminal w kontenerze i uruchom claude. Z kontem Anthropic zaloguj się przez przeglądarkę. Z Amazon Bedrock, Google Cloud lub Microsoft Foundry przekaż poświadczenia chmury jako zmienne środowiskowe (containerEnv, sekret Codespaces albo workload identity), a nie jako zamontowane pliki. Konfigurację dostawców opisuje strona uwierzytelnianie w Claude Code.

  4. W tym samym terminalu sprawdź podstawy: claude --version wypisuje wersję, node --version wypisuje v22 lub nowszą wersję, a whoami wypisuje node, nie root.

Wiele przykładów montuje do kontenera katalog ~/.claude z hosta. Nie rób tego. Ten katalog trzyma twoje poświadczenia i historię sesji ze wszystkich projektów, a sesja z pominiętymi uprawnieniami może odczytać i wynieść wszystko, co jest w kontenerze. Z tego samego powodu przewodnik Anthropic po dev containerach ostrzega przed montowaniem sekretów hosta, takich jak ~/.ssh czy pliki poświadczeń chmury.

Nazwany wolumen z kroku 1 zachowuje logowanie i ustawienia użytkownika między przebudowami, nie dotykając hosta. CLAUDE_CONFIG_DIR jest potrzebne, bo Claude Code trzyma konto OAuth, osobiste serwery MCP i zaufanie do projektów w ~/.claude.json, czyli w pliku poza ~/.claude. Bez tej zmiennej wolumen przetrwa, ale po każdej przebudowie będziesz wylogowany. Przyrostek ${devcontainerId} daje każdemu repozytorium osobny wolumen, więc stan jednego projektu nigdy nie przecieka do drugiego.

Przypnij wersję Claude Code i wymuś politykę zespołu

Dział zatytułowany „Przypnij wersję Claude Code i wymuś politykę zespołu”

Feature zawsze instaluje najnowsze wydanie. Gdy tech lead potrzebuje powtarzalnych buildów albo polityki, której projektowy .claude/settings.json nie nadpisze, przejdź na Dockerfile.

# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/typescript-node:22
# Przypnij CLI. 2.1.274 było dist-tagiem npm "stable" 2026-09-26.
RUN npm install -g @anthropic-ai/claude-code@2.1.274
# Polityka organizacji: najwyższy priorytet w hierarchii ustawień na Linuksie.
RUN mkdir -p /etc/claude-code
COPY managed-settings.json /etc/claude-code/managed-settings.json

Plik polityki leży obok jako .devcontainer/managed-settings.json:

{
"permissions": {
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
}
}

W devcontainer.json wskaż Dockerfile przez "build": { "dockerfile": "Dockerfile" } zamiast image i features, dodaj "customizations": { "vscode": { "extensions": ["anthropic.claude-code"] } }, żeby zachować rozszerzenie VS Code, które wcześniej dodawał feature, a do containerEnv dodaj "DISABLE_AUTOUPDATER": "1", żeby przypięcie się utrzymało. W środowiskach z ograniczoną telemetrią "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" wyłącza też ruch niekonieczny. Wyłącza przy tym ewaluację feature flag, od której zależy Remote Control, więc Remote Control nie działa w kontenerze. Jeśli go potrzebujesz, ustaw tylko DISABLE_TELEMETRY (v2.1.283+); wtedy Remote Control pozostaje dostępny, chyba że organizacja wymaga Trusted Devices.

Udostępnij konfigurację projektu i serwery MCP przez repozytorium

Dział zatytułowany „Udostępnij konfigurację projektu i serwery MCP przez repozytorium”

Wszystko, co ma zasięg projektu, przychodzi razem z checkoutem: CLAUDE.md, .claude/settings.json, .claude/skills/, .claude/agents/ i .mcp.json. Zespołowe serwery MCP definiuj w zasięgu projektu w .mcp.json (zobacz konfigurację serwerów MCP w Claude Code), a potem zrób dwie rzeczy, których checkout za ciebie nie zrobi:

  1. Zainstaluj w Dockerfile’u binaria potrzebne serwerom stdio (uv, klient bazy danych), żeby pierwsze wywołanie MCP nie padło na brakującym pliku wykonywalnym.
  2. Dodaj domenę każdego zdalnego serwera MCP do listy dozwolonych w firewallu opisanym niżej.

Codespaces uruchamia ten sam devcontainer.json w chmurze. Dwie rzeczy różnią się od kontenera lokalnego:

  • ~/.claude przetrwa zatrzymanie i wznowienie codespace’a, ale znika przy przebudowie, więc zostaw konfigurację z wolumenem i CLAUDE_CONFIG_DIR.
  • Żeby pozostać zalogowanym między codespace’ami, zapisz ANTHROPIC_API_KEY albo CLAUDE_CODE_OAUTH_TOKEN wygenerowany przez claude setup-token (wymaga subskrypcji Claude) jako sekret użytkownika Codespaces. Codespaces udostępnia sekrety w kontenerze jako zmienne środowiskowe.

Zadeklaruj sekret w devcontainer.json, żeby Codespaces poprosił o niego przy tworzeniu codespace’a:

{
"secrets": {
"CLAUDE_CODE_OAUTH_TOKEN": {
"description": "Long-lived Claude Code token from `claude setup-token`"
}
}
}

Nigdy nie wpisuj klucza na sztywno do devcontainer.json ani nie przekazuj go przez ${localEnv:...} do pliku, który commitujesz.

Ogranicz ruch wychodzący firewallem „domyślnie odrzucaj”

Dział zatytułowany „Ogranicz ruch wychodzący firewallem „domyślnie odrzucaj””

Własna sieć Dockera plus HTTPS_PROXY niczego nie izoluje: nic nie zmusza ruchu do przejścia przez proxy, a bezpośrednie połączenie nadal wychodzi z kontenera. Zmiennej proxy używaj do routingu (zobacz konfigurację proxy dla Claude Code), a firewalla do izolacji.

Referencyjny dev container Anthropic zawiera init-firewall.sh, który ustawia domyślną politykę iptables na DROP, przepuszcza opublikowane zakresy IP GitHuba i krótką listę rozwiązanych domen, a na koniec sam się sprawdza: kończy się błędem, jeśli https://example.com jest osiągalne albo https://api.github.com nie jest. Skrypt potrzebuje narzędzi, których obraz z kroku 1 nie ma. Skopiuj init-firewall.sh z wersji referencyjnej do .devcontainer/; to wymaga wariantu z Dockerfile’em, a nie pliku z obrazem i feature’em z kroku 1. Dopisz do Dockerfile’a:

RUN apt-get update && apt-get install -y --no-install-recommends \
iptables ipset dnsutils aggregate jq curl \
&& rm -rf /var/lib/apt/lists/*
COPY init-firewall.sh /usr/local/bin/init-firewall.sh
RUN chmod +x /usr/local/bin/init-firewall.sh

Potem podłącz go tak jak w wersji referencyjnej:

{
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"waitFor": "postStartCommand"
}

Nie przejmuj skryptu bez zmian. Jego luki, sprawdzone na gałęzi main 2026-09-26:

  • Rozwiązuje domeny na adresy IP raz, przy starcie. Rejestr albo CDN, które rotują adresy, przestają działać w trakcie sesji. postStartCommand uruchamia skrypt przy każdym starcie kontenera, więc restart kontenera jest sposobem naprawy.
  • Jego lista domen nie pokrywa wszystkiego, czego potrzebuje Claude Code. Przepuszcza api.anthropic.com, ale nie claude.ai ani platform.claude.com, których według wymagań sieciowych Anthropic używają logowanie przez przeglądarkę i odświeżanie tokenów OAuth. Dopisz claude.ai i platform.claude.com do listy for domain in (oraz mcp-proxy.anthropic.com, jeśli używasz connectorów claude.ai) albo uwierzytelniaj się przez CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY.
  • Przepuszcza całą sieć /24 hosta. Każda usługa w tej sieci, w tym inne kontenery, pozostaje osiągalna.
  • Referencyjny Dockerfile startuje z node:20, czyli poniżej minimum Node 22 wymaganego przez obecny pakiet npm. Zmień obraz bazowy, kiedy go kopiujesz.

Dopisz do listy for domain in własne rejestry pakietów, hosta VCS, jeśli to nie GitHub, oraz domeny zdalnych serwerów MCP.

Uruchamiaj Claude Code bez nadzoru wewnątrz kontenera

Dział zatytułowany „Uruchamiaj Claude Code bez nadzoru wewnątrz kontenera”

Kontener uruchamia Claude Code jako użytkownika bez roota i zamyka polecenia w kontenerze. To sprawia, że claude --dangerously-skip-permissions jest rozsądne wyłącznie w kontenerze z opisanym wyżej firewallem i na zaufanym repozytorium. CLI odrzuca tę flagę, gdy działa jako root. Sesja z pominiętymi uprawnieniami nadal może zmienić każdy plik w zamontowanym workspace, a te zmiany natychmiast pojawiają się na hoście.

Najpierw sięgnij po węższą opcję:

  • Tryb auto zostawia klasyfikator, który ocenia każdą akcję. Od v2.1.283 (oba kanały wydań na 2026-10-12) jest trybem startowym interaktywnych sesji w terminalu i VS Code na obsługiwanych modelach. claude -p nadal startuje w trybie Manual (od v2.1.285 w trybie auto u zewnętrznych dostawców lub z wyłączoną telemetrią). Przypięty wyżej obraz 2.1.274 jest starszy niż v2.1.283, więc jego sesje nadal startują w trybie Manual, dopóki nie przesuniesz przypięcia na 2.1.283 lub nowszą (2026-10-12 stable to 2.1.287). W przebiegach headless przekaż --permission-mode auto albo przypnij dokładną listę dozwolonych narzędzi: claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read".
  • Sandbox Basha usuwa większość pytań o polecenia powłoki, zachowując granicę dla każdego polecenia. Włącz go przez /sandbox albo ustaw "sandbox": { "enabled": true } w .claude/settings.json.
  • Żeby zabronić trybu bypass całemu zespołowi, ustaw permissions.disableBypassPermissionsMode na "disable" w ustawieniach zarządzanych.

Pełne porównanie trybów w Claude Code, Codex i Cursorze znajdziesz na stronie uprawnienia, sandboksy i tryby zatwierdzania.

Jak udowodnić, że dev container działa, bez czytania go?

Dział zatytułowany „Jak udowodnić, że dev container działa, bez czytania go?”

Recenzent nie wyczyta z diffu devcontainer.json, czy logowanie nadal przetrwa przebudowę i czy firewall nadal blokuje ruch. Job w CI to sprawdzi. Akcja devcontainers/ci buduje kontener z twojej prawdziwej konfiguracji i uruchamia w nim polecenie. Poniższy job nie potrzebuje żadnych sekretów, bo żadne ze sprawdzeń nie woła modelu:

.github/workflows/devcontainer.yml
name: devcontainer
on:
pull_request:
paths: ['.devcontainer/**', '.mcp.json', '.claude/**']
permissions:
contents: read
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: devcontainers/ci@v0.3
with:
push: never
runCmd: bash .devcontainer/smoke-test.sh
#!/usr/bin/env bash
# .devcontainer/smoke-test.sh: każda linia zatrzymuje job przy regresji
set -euo pipefail
test "$(whoami)" != "root" # --dangerously-skip-permissions wymaga użytkownika bez roota
node -e 'process.exit(+process.versions.node.split(".")[0] >= 22 ? 0 : 1)' # Node 22+
claude --version | grep -q '^2\.1\.274 ' # przypięcie z Dockerfile'a działa
test "$CLAUDE_CONFIG_DIR" = "$HOME/.claude" # logowanie przetrwa przebudowę
test -f /etc/claude-code/managed-settings.json # polityka zespołu jest w obrazie
if curl -s --max-time 5 https://example.com >/dev/null; then
echo "egress not blocked"; exit 1
fi
curl -s --max-time 5 -o /dev/null https://api.anthropic.com # API modelu jest osiągalne
npm test # testy projektu przechodzą w kontenerze

Usuń linie z wersją i managed-settings.json, jeśli używasz feature’a zamiast Dockerfile’a, a sprawdzenie ruchu wychodzącego, jeśli nie uruchamiasz firewalla. Dodaj .devcontainer/ do CODEOWNERS, żeby zmiany zatwierdzał tech lead. Przy zielonym jobie dymnym to zatwierdzenie sprawdza intencję, a nie każdą linijkę.

Kontener nie jest związany z Claude Code. Cursor obsługuje specyfikację Dev Containers, więc otwiera ten sam devcontainer.json, a CLI Claude Code działa w jego zintegrowanym terminalu. Dla Codex dodaj npm install -g @openai/codex do Dockerfile’a i dopisz hosty Codex do firewalla: api.openai.com przy logowaniu kluczem API oraz chatgpt.com i auth.openai.com przy logowaniu kontem ChatGPT (domyślne endpointy w binarce Codex 0.157.1, sprawdzone 2026-09-26). Hostowane środowiska Codex to osobny system, opisany na stronie środowiska chmurowe Codex.

Build zatrzymuje się na Failed to install Node.js and npm albo claude pada przy starcie. Obraz bazowy nie ma Node’a, a feature albo nie zdołał go zainstalować, albo zainstalował Node 18. Dodaj "ghcr.io/devcontainers/features/node:1": { "version": "22" } w features przed feature’em Claude Code, przebuduj kontener i sprawdź, czy node --version wypisuje v22 lub nowszą wersję.

npm install -g @anthropic-ai/claude-code wypisuje EBADENGINE. Node w obrazie jest starszy niż 22. Przejdź na obraz z Node 22 lub nowszym; feature tu nie pomoże, bo zostawia Node’a, którego obraz już ma.

Logowanie w przeglądarce się kończy, ale terminal go nie dostaje. Przekierowanie portów w edytorze nie przepuściło callbacku na localhost. Skopiuj kod z przeglądarki i wklej go w terminalu przy Paste code here if prompted.

Po każdej przebudowie jesteś wylogowany. Wolumen jest zamontowany, ale brakuje CLAUDE_CONFIG_DIR, więc ~/.claude.json ląduje w katalogu domowym, który przebudowa wyrzuca. Ustaw zmienną na ścieżkę docelową wolumenu.

EACCES na ~/.claude po pierwszym starcie. Wolumen utworzył inny użytkownik. Uruchom raz sudo chown -R node:node /home/node/.claude albo usuń wolumen przez docker volume rm i przebuduj.

--dangerously-skip-permissions jest odrzucane. Claude Code działa jako root. Ustaw remoteUser na użytkownika obrazu bez roota i przebuduj.

Polecenia w sandboksie padają z Can't mount proc on /newroot/proc: Operation not permitted. Bubblewrap nie może zamontować świeżego /proc w nieuprzywilejowanym kontenerze. Ustaw sandbox.enableWeakerNestedSandbox na true tylko wtedy, gdy granicą, na której polegasz, są firewall i izolacja użytkownika samego kontenera; to ustawienie wyraźnie osłabia wewnętrzny sandbox.

npm install, zdalny serwer MCP albo logowanie kończą się timeoutem po starcie firewalla. Cel nie jest na liście dozwolonych albo zmienił adres IP po tym, jak skrypt go rozwiązał. Dopisz domenę do init-firewall.sh i zrestartuj kontener.

claude --resume nie pokazuje sesji z hosta. Sesje są zapisywane per ścieżka projektu, a ścieżka w kontenerze (/workspaces/app) różni się od ścieżki na hoście. To oczekiwane zachowanie. Zaczynaj nowe sesje w kontenerze.