En AI-kodeagent som Claude Code er nyttig, fordi den ikke bare foreslår kode. Den kører kommandoer, installerer pakker, læser filer og retter dem. Det er netop det, der gør den hurtig, og det er også det, der gør det værd at tænke over, hvor den kører.
Kører agenten direkte på din udviklermaskine, kører den med dine rettigheder. Den kan i princippet nå alt, du selv kan nå: SSH-nøgler i ~/.ssh, cloud-nøgler, browserdata, andre projekter på disken og hele dit lokale netværk. Det kræver ikke ond vilje fra agentens side. Det er nok, at en afhængighed opfører sig uventet, eller at en tekst i et repository forsøger at styre agenten i en anden retning end den, du bad om.
En Dev Container flytter agenten ind i en afgrænset Docker-container. Editoren bliver på din maskine, mens terminal, værktøjer og Claude Code kører i containeren og kun ser projektmappen. I dette indlæg gennemgår vi opsætningen med VS Code og JetBrains-IDE'erne, hvad den beskytter mod, og hvad den ikke beskytter mod.
Hvad er en Dev Container?
En Dev Container er et udviklingsmiljø beskrevet som kode. Filen .devcontainer/devcontainer.json (eventuelt med en Dockerfile eller en Docker Compose-fil) beskriver image, værktøjer, porte, volumes og kommandoer, der skal køre, når containeren oprettes. Formatet er en åben specifikation på containers.dev, og filen må indeholde kommentarer (JSON with Comments).
Arbejdsdelingen ser sådan ud:
- Editoren kører på værten. VS Code, WebStorm eller PyCharm viser og redigerer filerne som normalt.
- Alt andet kører i containeren: den integrerede terminal, language servers, build-værktøjer, tests og Claude Code.
- Projektmappen er bind-mountet ind i containeren. Ændringer, som agenten laver, ses straks i dit lokale repository, og du kan gennemse dem med
git diffsom altid.
Resten af maskinen deles ikke, medmindre du selv mounter det ind.

Claude Code i containeren
Den nemmeste vej er Anthropics Dev Container Feature. En minimal devcontainer.json kan se sådan ud:
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
}
}Tre detaljer er værd at kende (Claude Code-dokumentationen beskriver dem):
- Tagget
:1.0låser installationsscriptet, ikke Claude Code-versionen. Featuren installerer altid den nyeste udgave, og Claude Code opdaterer sig selv i containeren. - Featuren installerer selv Node.js, hvis base-imaget ikke har det.
- Skal versionen låses for reproducerbare builds, installeres Claude Code i stedet fra Dockerfilen med en fast version, og opdateringer slås fra.
DISABLE_UPDATES=1blokerer både den automatiske opdatering ogclaude update, så versionen kun ændres, når nogen retter den i Dockerfilen og bygger containeren igen:
FROM mcr.microsoft.com/devcontainers/typescript-node:5.2.3-24-bookworm
# Fast Claude Code-version i stedet for featuren
ARG CLAUDE_CODE_VERSION=2.1.310
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
# Ingen automatisk opdatering og ingen `claude update`
ENV DISABLE_UPDATES=1Behold login på tværs af genopbygninger
Containerens hjemmemappe smides væk, når containeren genopbygges, og så skal man logge ind igen. Claude Code gemmer login, indstillinger og historik under ~/.claude. Kontooplysningerne og dit svar på spørgsmålet "stoler du på filerne i denne mappe?", som Claude Code stiller første gang, den startes i et projekt, ligger derimod i filen ~/.claude.json ved siden af. Derfor er det ikke nok at mounte et volume på ~/.claude. Sæt også CLAUDE_CONFIG_DIR til samme sti, så .claude.json havner inde i volumet:
"remoteUser": "node",
"mounts": [
"source=mit-projekt-claude-config,target=/home/node/.claude,type=volume"
],
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/node/.claude"
}Sikkerhed: hvad containeren gør, og hvad den ikke gør
En container er en god grænse, men den er kun så god som konfigurationen. Det her er de vigtigste greb:
- Kør som en almindelig bruger. Sæt
remoteUsertil en bruger uden root-rettigheder, f.eks.node, og fjern brugerens adgangskodefri sudo, når opsætningen er kørt. Mange Dev Container-images giver den som standard, og ellers kan agenten stadig blive root. Claude Code afviser i øvrigt--dangerously-skip-permissions, hvis den køres som root. - Mount ikke værtens hemmeligheder ind. Ingen
~/.ssh, ingen cloud-nøglefiler. Brug tokens, der er begrænset til det ene repository og udløber hurtigt. - Begræns udgående netværk. Anthropics referencecontainer har et firewallscript,
init-firewall.sh, der som udgangspunkt afviser al udgående trafik og kun tillader en liste af værter (Anthropic, npm, GitHub og lignende). Det kræver, at containeren får kapabiliteterneNET_ADMINogNET_RAWviarunArgs.
Et uddrag af princippet fra vores egen version af scriptet, hvor alt afvises, undtagen svar på tilladte forbindelser og trafik til listen af tilladte adresser:
# Default deny, then allow replies and the allowlist; reject the rest immediately.
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m set --match-set allowed-hosts dst -j ACCEPT
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibitedVær også ærlig om begrænsningen: Claude Code-dokumentationen skriver selv, at en Dev Container kørt med --dangerously-skip-permissions ikke forhindrer et ondsindet projekt i at sende alt, der er tilgængeligt inde i containeren, ud af huset. Det gælder også Claude Codes egne loginoplysninger i ~/.claude. Brug derfor kun opsætningen med repositories, du stoler på, og hold øje med, hvad agenten laver. Firewallen gør det sværere at sende data til vilkårlige værter, men den kan ikke stoppe misbrug af de værter, der står på listen.
Vil du bare have færre spørgsmål uden at slå sikkerhedstjek fra, er auto mode et bedre første skridt: her vurderer en klassifikator handlingerne, før de udføres.
Mount eller klon: hvor stor er åbningen?
Indtil nu har projektmappen været bind-mountet ind i containeren. Det er det praktiske valg: du redigerer, kører git og gennemser ændringer på din egen maskine, mens agenten arbejder på de samme filer i containeren. Men mountet er også en bevidst åbning i isolationen. Agenten kan ikke bryde ud af containeren, men den kan skrive i alle filer i projektmappen, også dem, din maskine selv kører senere:
.git/hooks/og.git/config. Enpre-commit-hook eller encore.hooksPath, som agenten har lagt, kører på din maskine, næste gang du kørergit commitder.- Editorens konfiguration, f.eks.
.vscode/tasks.jsonog.idea/runConfigurations/, som kører, når du starter en task eller en run configuration lokalt. - Scripts i
package.json,Makefileellerscripts/, hvis du kører dem uden for containeren.
Risikoen er afgrænset, for den kræver, at du selv kører filen på værten. Men den er værd at kende, især når agenten stiller færre spørgsmål om tilladelse til at få adgang til filer og køre filer.
Alternativet er at klone. Både VS Code (Dev Containers: Clone Repository in Container Volume) og JetBrains-IDE'erne (Create Dev Container and Clone Sources) kan hente repositoryet fra Git ind i et Docker-volume i stedet for at mounte din lokale mappe. Så findes arbejdskopien kun i containeren, din lokale mappe røres ikke, og ændringer forlader kun containeren via git commit og git push, hvor de kan gennemses i et pull request. Prisen er, at du arbejder i en separat kopi. Kun det, der er committet og pushet, kommer med, også i .devcontainer/. Og JetBrains-IDE'erne genbruger klon-volumet næste gang, så en ny container kan starte med en gammel kopi af koden.
Bliver du ved med at mounte, lukker disse greb det meste af hullet:
- Mount
.gitskrivebeskyttet i containeren (se nedenfor). Agenten kan stadig rette koden, men ikke ændre hooks eller git-konfigurationen.git addoggit commitkøres så på værten. - Kig efter, før du committer på værten, hvis
.giter skrivbar:ls .git/hooks | grep -v sampleoggit config --local --list. - Klon i stedet, når repositoryet ikke er dit eget, eller når agenten kører med
--dangerously-skip-permissions.
"mounts": [
"source=${localWorkspaceFolder}/.git,target=${containerWorkspaceFolder}/.git,type=bind,readonly"
]VS Code
I VS Code skal du kun bruge Docker og VS Code-pluginet Dev Containers. Resten står i devcontainer.json:
- Installér Docker, f.eks. Docker Desktop, og VS Code-pluginet Dev Containers.
- Åbn projektmappen. VS Code finder
.devcontainer/devcontainer.jsonog tilbyder at genåbne projektet i containeren, eller du vælger Dev Containers: Reopen in Container i kommandopaletten. - Første gang bygges imaget. Næste gang starter containeren på få sekunder.
- Åbn en terminal. Den kører i containeren, og
claudestarter agenten. Første gang logger du ind via browseren, og login gemmes i et volume.
Ændrer du konfigurationen, vælger du Dev Containers: Rebuild Container. Containeren bygges forfra, mens login bliver liggende i volumet.
JetBrains: WebStorm, PyCharm og Rider
JetBrains-IDE'erne læser den samme devcontainer.json (WebStorm, PyCharm og Rider):
- Åbn
.devcontainer/devcontainer.jsoni IDE'en og klik på ikonet Create Dev Container i venstre margen. - Vælg Mount Sources for at arbejde på dine lokale filer eller Clone Sources for at arbejde i en klonet kopi (se "Mount eller klon" ovenfor).
- Vælg IDE, f.eks. WebStorm. Når containeren er bygget, åbnes projektet i et nyt vindue, der er forbundet til containeren.
- Åbn terminalen og start
claude, præcis som i VS Code.
Næste gang åbner du containeren fra File → Remote Development → Dev Containers. Fordi VS Code og JetBrains-IDE'erne bruger den samme definition, kan holdet frit vælge editor uden at ændre noget i projektet.
Et komplet eksempel på konfiguration af en Dev Container
Til sidst et komplet eksempel for den fiktive virksomhed Space Travel, hvis website består af et Next.js-frontend og et Strapi-CMS i samme repository, med PostgreSQL i Docker. Konfigurationen består af to filer, devcontainer.json og en Dockerfile, og containeren:
- bygger på et node-image med samme version som i produktion og installerer Claude Code i en fast version i Dockerfilen;
- starter udviklingsdatabaserne på værten (
initializeCommand) og kobler containeren på projektets Docker-netværk,space-travel-network, så Strapi når PostgreSQL under servicenavnetspace-travel-db; - lægger
~/.claudeog beggenode_modules-mapper i volumes, så login overlever genopbygning, og så Linux-pakkerne ikke blandes med værtens; - bruger samme projektmappe og samme login-volume i VS Code, WebStorm, PyCharm og Rider, så du kan skifte editor uden at logge ind igen;
- kører
npm cii frontend og backend og fjerner bagefter den generelle sudo-regel; - videresender port 3000 (Next.js) og 1337 (Strapi);
- har firewallen som et valg, der slås til med
ENABLE_FIREWALL=on.
// Space Travel website: Next.js (src/frontend) + Strapi (src/backend) + Claude Code.
// Spec: https://containers.dev/
{
"name": "Space Travel website (Next.js + Strapi)",
"build": { "dockerfile": "Dockerfile" },
// Runs on the host before the container starts: the dev and test Postgres from
// docker-compose.dev.yml. This also creates space-travel-network, which the container joins.
"initializeCommand": "docker compose -f docker-compose.dev.yml up -d",
"runArgs": [
"--network=space-travel-network",
// Only needed for the optional firewall (ENABLE_FIREWALL=on).
"--cap-add=NET_ADMIN",
"--cap-add=NET_RAW"
],
// Claude Code is installed at a pinned version in the Dockerfile (DISABLE_UPDATES=1), not by the feature.
// The same path in VS Code and JetBrains, so `claude --continue` finds sessions from both editors.
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/space-travel,type=bind",
"workspaceFolder": "/workspaces/space-travel",
"remoteUser": "node",
"mounts": [
// Claude Code login, settings and sessions survive rebuilds. A fixed name, shared by VS Code and JetBrains.
"source=space-travel-claude-config,target=/home/node/.claude,type=volume",
// Linux node_modules stay out of the host bind mount (faster on macOS, host installs keep working).
"source=space-travel-frontend-node_modules-${devcontainerId},target=${containerWorkspaceFolder}/src/frontend/node_modules,type=volume",
"source=space-travel-backend-node_modules-${devcontainerId},target=${containerWorkspaceFolder}/src/backend/node_modules,type=volume"
],
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
// Postgres by service name on space-travel-network. Environment variables win over src/backend/.env.
"DATABASE_HOST": "space-travel-db",
"DATABASE_PORT": "5432",
"TEST_DATABASE_HOST": "space-travel-db-test",
"TEST_DATABASE_PORT": "5432",
// Strapi runs in the same container.
"STRAPI_URL": "http://localhost:1337",
"STRAPI_PUBLIC_URL": "http://localhost:1337",
"STRAPI_TELEMETRY_DISABLED": "true",
// "on": restrict outbound traffic to an allowlist on every start (.devcontainer/init-firewall.sh,
// installed as /usr/local/bin/project-firewall.sh).
"ENABLE_FIREWALL": "off"
},
"forwardPorts": [3000, 1337],
"portsAttributes": {
"3000": { "label": "Next.js", "onAutoForward": "notify" },
"1337": { "label": "Strapi", "onAutoForward": "notify" }
},
// Volume ownership, npm ci in both apps, then removes node's general sudo (see the script).
"postCreateCommand": ".devcontainer/post-create.sh",
"postStartCommand": "if [ \"$ENABLE_FIREWALL\" = on ]; then sudo /usr/local/bin/project-firewall.sh; fi",
"waitFor": "postStartCommand",
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "vitest.explorer"]
},
"jetbrains": {
"backend": "WebStorm"
}
}
}# Space Travel dev container: Node as in production, Claude Code at a pinned version,
# and the tools for the optional egress firewall.
FROM mcr.microsoft.com/devcontainers/typescript-node:5.2.3-24-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
iptables ipset iproute2 dnsutils aggregate jq \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# node gets the uid of the host user (`id -u` on the host), so it can write the bind-mounted
# project on Colima, which shows files with the host owner. Harmless when the uids already match.
ARG USER_UID=501
RUN usermod -u "${USER_UID}" node \
&& find / -xdev -uid 1000 -exec chown -h node {} +
# Claude Code changes only when a developer changes this version and rebuilds the container.
ARG CLAUDE_CODE_VERSION=2.1.310
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} && npm cache clean --force
# Blocks auto-update, `claude update` and `claude install`, so the version stays the one above.
ENV DISABLE_UPDATES=1
# Mount point of the Claude config volume. A new, empty volume takes this owner, so node can save its login.
RUN install -d -m 0700 -o node -g node /home/node/.claude
# The firewall script is the only thing node may run as root without a password.
COPY init-firewall.sh /usr/local/bin/project-firewall.sh
RUN chmod 0755 /usr/local/bin/project-firewall.sh \
&& echo "node ALL=(root) NOPASSWD: /usr/local/bin/project-firewall.sh" > /etc/sudoers.d/node-firewall \
&& chmod 0440 /etc/sudoers.d/node-firewall# Byg og start containeren uden editor (kræver Docker)
npx @devcontainers/cli up --workspace-folder .
# Kør en kommando i den, f.eks. Claude Code-versionen
npx @devcontainers/cli exec --workspace-folder . claude --versionKommandoerne ovenfor bruger Dev Containers CLI, referenceimplementeringen af Dev Container-specifikationen. npx henter og kører CLI'en fra npm uden at installere den globalt. up bygger imaget og starter containeren ud fra .devcontainer/devcontainer.json, og --workspace-folder . angiver projektmappen, som konfigurationen hører til. Med exec kører du en kommando i den kørende container. Det er en hurtig måde at afprøve konfigurationen på uden en editor, og den samme definition bruges uændret, når projektet åbnes i VS Code eller WebStorm.
Efter første opstart er det værd at kontrollere opsætningen i containerens terminal:
idviser brugerennode, ogsudo -n truefejler. Så kan agenten hverken blive root eller slå firewallen fra.- Databaserne kan nås under deres servicenavne, og frontend- og backendtests kører.
- Med
ENABLE_FIREWALL=onafvisescurl https://example.com.
Bemærk, at firewallen kun begrænser udgående trafik til en liste af tilladte værter. Containeren kan stadig kalde npm, GitHub og Anthropic, for ellers kunne hverken pakkeinstallation, git eller Claude Code virke. Derfor gælder rådet fra sikkerhedsafsnittet stadig: brug kun opsætningen med repositories, du stoler på.
Tjekliste: det skal du kunne forvente af din Dev Container
- Claude Code kører som en almindelig bruger: id viser f.eks. node, og sudo -n true fejler, så agenten ikke kan blive root.
- Dine private filer er ikke synlige: ~/.ssh, cloud-nøgler (f.eks. ~/.aws og ~/.config/gcloud), nøgleringen og browserdata findes ikke i containeren. Kun projektmappen er delt.
- Tokens, som agenten har brug for, er begrænset til det ene repository og udløber hurtigt.
- Login overlever genopbygning: ~/.claude ligger i et volume, og CLAUDE_CONFIG_DIR peger på det.
- Med firewallen slået til kan containeren kun nå de tilladte værter, f.eks. npm, GitHub og Anthropic, og curl https://example.com afvises.
- Den almindelige bruger kan ikke fjerne firewallreglerne igen: sudo iptables -F fejler.
- Mountes projektmappen, er .git skrivebeskyttet i containeren, eller du kigger hooks og git-konfiguration efter, før du committer på din egen maskine.
- Du bruger kun opsætningen med repositories, du stoler på, og gennemgår agentens ændringer som enhver anden ændring.
Konklusion: hav en plan for, hvor agenten må arbejde
En AI-kodeagent er et stærkt værktøj, netop fordi den handler selv: den læser filer, kører kommandoer og installerer pakker. Det gør den med de rettigheder, den får. Kører den direkte på din maskine, er det dine rettigheder, og så handler det ikke længere kun om produktivitet, men om datasikkerhed.
Uden en afgrænsning kan en agent, ved en fejl, via en ondsindet afhængighed eller fordi tekst i et repository styrer den i en anden retning:
- læse private data som SSH-nøgler, cloud-nøgler, tokens i
.env-filer og browserdata og sende dem ud af huset; - slette eller overskrive filer uden for projektet, også i andre repositories og i dine dokumenter;
- bruge dine adgange til at pushe til andre repositories, ændre cloud-ressourcer eller kalde interne API'er;
- nå dit lokale netværk, f.eks. NAS, router og databaser, der ikke er beregnet til at blive tilgået af et program, du ikke har fuld kontrol over;
- efterlade noget, du selv kører senere, f.eks. en git-hook eller et script i projektet;
- installere pakker og værktøjer, der ændrer din maskine permanent.
Derfor bør enhver udvikler, der bruger en AI-kodeagent, have en bevidst strategi for, hvordan agenten indkapsles. En Dev Container er en enkel måde at gøre det på: agenten ser kun projektmappen, kører uden root og når kun de værter, du tillader, mens dine private data bliver på din egen maskine. En virtuel maskine, en separat brugerkonto eller et cloudbaseret udviklingsmiljø kan løse samme opgave. Det vigtige er ikke værktøjet, men at grænsen er valgt med vilje, beskrevet som kode og afprøvet.
Giv agenten adgang til det projekt, den skal arbejde i, og ikke til resten af din maskine.
Vil I sætte AI-kodeagenter sikkert i drift?
Vi hjælper gerne med at finde en opsætning, der passer til jeres projekter, værktøjer og sikkerhedskrav. Tag en uforpligtende snak.
- docker
- dev-containers
- claude-code
- sikkerhed
- vscode
- jetbrains



