Skip to content
khiopost
Go back

Docker AI 샌드박스 구축 4단계 — Mac 안전한 코드 실행 환경 가이드

AI agent가 코드를 생성하고 실행하는 시대가 왔다. Claude Code, Cursor, Devin 같은 도구들이 로컬 환경에서 직접 코드를 실행하는데, 솔직히 처음에는 좀 무서웠다. Agent가 rm -rf를 실행하거나, 시스템 파일을 건드리거나, 외부에 민감한 데이터를 전송하면 어쩌나 하는 생각이 들었다.

해결책은 간단하다. Docker 컨테이너 안에서 실행하면 된다. 컨테이너 내부에서 뭘 하든 호스트 시스템에는 영향이 없고, 문제가 생기면 컨테이너를 날리고 새로 만들면 그만이다. 이 글에서는 MacBook M1 32GB RAM 환경을 기준으로, AI agent용 Docker 샌드박스를 처음부터 끝까지 구축하는 방법을 정리한다.

목차

  1. Docker Desktop M1 설치 및 Apple Silicon 설정
  2. Rosetta 에뮬레이션 이슈와 ARM 네이티브 이미지
  3. 메모리/CPU 할당 최적화 (32GB 기준)
  4. AI agent용 Dockerfile 작성
  5. 볼륨 마운트로 프로젝트 파일 공유
  6. 네트워크 격리 설정
  7. docker-compose로 multi-container 환경
  8. Claude Code를 Docker 안에서 실행하기
  9. 컨테이너 리소스 모니터링
  10. AI가 생성한 코드를 안전하게 실행하는 패턴

Docker Desktop M1 설치 및 Apple Silicon 설정

2024년 이후로 Docker Desktop의 Apple Silicon 지원이 많이 안정화됐다. 예전에는 M1에서 Docker를 돌리면 뭔가 하나씩 안 되는 게 있었는데, 지금은 대부분의 이미지가 ARM64 네이티브로 제공된다. 관련 내용은 Claude Code 권한 보안 설정에서도 다루고 있다.

설치 방법

Docker Desktop 공식 사이트에서 Apple Silicon용 .dmg 파일을 받아 설치한다. Homebrew로도 가능하다:

# Homebrew Cask로 설치 (권장)
$ brew install --cask docker

설치 후 Docker Desktop 앱 실행

$ open /Applications/Docker.app

설치 확인

$ docker —version Docker version 27.5.1, build 9f9e405

$ docker compose version Docker Compose version v2.32.4

Apple Silicon 아키텍처 확인

$ docker info | grep -i arch Architecture: aarch64

테스트 컨테이너 실행

$ docker run —rm hello-world Hello from Docker! This message shows that your installation appears to be working correctly.

Architecture: aarch64가 출력되면 ARM64 네이티브로 정상 동작하는 것이다. 만약 x86_64로 표시된다면 Rosetta를 통해 에뮬레이션되고 있는 것이니 설정을 확인해야 한다.

초기 설정에서 챙겨야 할 것들

Docker Desktop을 처음 실행하면 설정 마법사가 뜬다. 여기서 두 가지를 확인한다:

Rosetta 에뮬레이션 이슈와 ARM 네이티브 이미지 선택

Docker — 설정 및 실행 결과 화면

M1 Mac에서 Docker를 쓸 때 가장 많이 부딪히는 문제가 아키텍처 불일치이다. Docker Hub의 많은 이미지가 linux/amd64만 지원하는 경우가 아직 있다. 이런 이미지를 M1에서 그냥 실행하면 이런 경고가 뜬다:

$ docker run --rm postgres:14
WARNING: The requested image's platform (linux/amd64) does not match
the detected host platform (linux/arm64/v8) and no specific platform
was requested.

이 경고가 뜨면 Rosetta 에뮬레이션으로 실행되는 것

성능이 20~40% 정도 떨어집니다

ARM64 네이티브 이미지를 명시적으로 지정하는 방법

$ docker run —rm —platform linux/arm64 postgres:16

postgres:16은 ARM64 네이티브 이미지를 제공합니다

이미지의 지원 아키텍처 확인

$ docker manifest inspect python:3.12-slim | grep architecture “architecture”: “amd64”, “architecture”: “arm64”,

arm64가 있으면 M1 네이티브 지원

내가 AI agent용 샌드박스를 만들면서 겪은 실제 이슈가 하나 있었다. chromadb라는 벡터 DB의 공식 이미지가 한동안 amd64만 지원해서, M1에서 실행하면 메모리를 비정상적으로 많이 먹었다. 이런 경우에는 해당 프로젝트의 GitHub에서 ARM64 빌드를 확인하거나, 직접 Dockerfile을 작성해서 ARM64 베이스 이미지 위에 올리는 게 낫다.

이미지 선택 원칙

AI agent 환경을 구축할 때 이미지 선택 기준을 정리하면 이렇다:

메모리/CPU 할당 최적화 (32GB 기준)

MacBook M1 32GB RAM 환경에서 Docker에 리소스를 얼마나 줄 것인지는 꽤 중요한 문제이다. 너무 많이 주면 macOS 자체가 느려지고, 너무 적게 주면 컨테이너 안에서 OOM(Out of Memory) 에러가 터진다.

Docker Desktop > Settings > Resources에서 설정한다. 내가 여러 조합을 시도해보고 안정적이었던 설정은 이렇다:

항목 추천 값 비고
Memory 12GB 32GB 중 12GB. LLM 추론까지 돌리려면 16GB
CPU 6 cores M1의 8코어 중 6개. 호스트에 2개는 남겨둬야 함
Swap 4GB 메모리 부족 시 버퍼 역할
Disk image size 100GB AI 관련 이미지가 크므로 여유있게

주의할 점은 Docker Desktop이 할당받은 메모리를 항상 점유하는 건 아니라는 것이다. Apple Virtualization Framework를 사용하면 실제 사용량만큼만 물리 메모리를 소비한다. 다만 상한선을 12GB로 잡아두면, 컨테이너가 폭주해도 macOS가 멈추는 상황은 막을 수 있다.

개인적으로는 AI agent 작업 중에 VS Code, 브라우저, Slack까지 동시에 띄워놓기 때문에 Docker에 16GB 이상은 주지 않는다. 만약 Docker만 집중적으로 사용하는 상황이라면 20GB까지 올려도 괜찮다.

AI agent용 Dockerfile 작성

AI agent가 코드를 실행하려면 Python과 Node.js가 둘 다 필요한 경우가 많다. Python은 데이터 분석, ML 추론, API 호출에, Node.js는 웹 스크래핑, 프론트엔드 빌드, 각종 npm 도구에 쓰인다. 하나의 컨테이너에 두 런타임을 함께 넣되, 이미지 크기가 과도하게 커지지 않도록 multi-stage build를 활용한다.

# AI Agent Sandbox Dockerfile
# 타겟: Apple Silicon (linux/arm64)

FROM python:3.12-slim AS base

시스템 패키지 설치

RUN apt-get update && apt-get install -y —no-install-recommends
curl
git
build-essential
jq
wget
unzip
&& rm -rf /var/lib/apt/lists/*

Node.js 22 LTS 설치 (ARM64 네이티브)

RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
&& apt-get install -y nodejs
&& rm -rf /var/lib/apt/lists/*

Python 가상환경 생성

RUN python -m venv /opt/venv ENV PATH=“/opt/venv/bin:$PATH”

자주 쓰는 Python 패키지 미리 설치

COPY requirements.txt /tmp/requirements.txt RUN pip install —no-cache-dir -r /tmp/requirements.txt

작업 디렉터리 설정

WORKDIR /workspace

비root 사용자 생성 (보안)

RUN useradd -m -s /bin/bash agent
&& chown -R agent:agent /workspace /opt/venv

USER agent

기본 셸 설정

SHELL [“/bin/bash”, “-c”]

CMD [“bash”]

위 Dockerfile과 함께 사용할 requirements.txt는 이렇게 구성한다:

# requirements.txt — AI agent 기본 패키지
requests==2.32.3
httpx==0.28.1
beautifulsoup4==4.12.3
pandas==2.2.3
numpy==2.2.1
pydantic==2.10.5
python-dotenv==1.0.1
tiktoken==0.8.0
openai==1.59.7
anthropic==0.42.0
langchain-core==0.3.30
chromadb==0.6.3
pytest==8.3.4

핵심 포인트는 useradd로 비root 사용자를 만들어서 컨테이너 내에서도 권한을 제한하는 것이다. AI agent가 apt-get install이나 시스템 파일 수정을 시도해도 권한 에러로 차단된다. 이게 샌드박스의 첫 번째 보안 레이어이다.

이미지 빌드 및 테스트

# ARM64 네이티브로 빌드
$ docker build --platform linux/arm64 -t ai-sandbox:latest .

빌드 시간: M1 기준 약 3~5분 (네트워크 상태에 따라 다름)

이미지 크기 확인

$ docker images ai-sandbox REPOSITORY TAG IMAGE ID CREATED SIZE ai-sandbox latest a3b7c9d2e1f0 10 seconds ago 1.87GB

컨테이너 진입 테스트

$ docker run —rm -it ai-sandbox:latest

agent@c4f2a1b3d5e6:/workspace$ python —version Python 3.12.8

agent@c4f2a1b3d5e6:/workspace$ node —version v22.13.0

agent@c4f2a1b3d5e6:/workspace$ whoami agent

agent@c4f2a1b3d5e6:/workspace$ sudo apt-get update bash: sudo: command not found

sudo가 없으므로 시스템 변경 불가 — 의도된 동작

볼륨 마운트로 프로젝트 파일 공유

컨테이너를 삭제하면 내부 데이터가 전부 사라진다. AI agent가 생성한 코드나 결과물을 호스트에서도 접근하려면 볼륨 마운트가 필요하다. 다만 마운트 범위를 최소화하는 것이 보안상 중요하다.

# 프로젝트 디렉터리만 마운트 (읽기/쓰기)
$ docker run --rm -it \
    -v $(pwd)/project:/workspace/project \
    ai-sandbox:latest

읽기 전용 마운트 (agent가 수정하면 안 되는 데이터)

$ docker run —rm -it
-v $(pwd)/reference-data:/workspace/data:ro
-v $(pwd)/output:/workspace/output
ai-sandbox:latest

절대로 하면 안 되는 것: 홈 디렉터리 전체 마운트

$ docker run -v $HOME:/workspace/home ai-sandbox:latest

↑ SSH 키, .env 파일, 브라우저 쿠키 등 모든 민감 정보가 노출됨

마운트 전략을 정리하면 이렇다:

M1 Mac에서 볼륨 마운트 시 성능 이슈가 있을 수 있다. Docker Desktop 4.30 이후로 VirtioFS가 기본 파일 시스템으로 설정되면서 많이 개선됐지만, node_modules처럼 파일 수가 수만 개인 디렉터리를 마운트하면 여전히 느린다. 이 경우 node_modules는 컨테이너 내부에만 두고 마운트에서 제외하는 것이 좋다:

# node_modules를 마운트에서 제외하는 패턴
$ docker run --rm -it \
    -v $(pwd)/project:/workspace/project \
    -v /workspace/project/node_modules \
    ai-sandbox:latest

두 번째 -v에 호스트 경로가 없으면 anonymous volume이 생성됨

호스트의 node_modules와 컨테이너의 node_modules가 분리됩니다

네트워크 격리 설정

AI agent 샌드박스에서 가장 신경 써야 할 부분이 네트워크이다. Agent가 생성한 코드가 외부 서버에 데이터를 전송하거나, 악의적인 다운로드를 시도할 수 있으니까.

# 네트워크 완전 차단 (가장 엄격)
$ docker run --rm -it --network none ai-sandbox:latest

agent@abc123:/workspace$ curl https://google.com curl: (6) Could not resolve host: google.com

외부 접근 완전 불가

내부 네트워크만 허용 (컨테이너 간 통신은 가능)

$ docker network create —internal ai-internal-net

$ docker run —rm -it —network ai-internal-net ai-sandbox:latest

agent@def456:/workspace$ curl https://google.com curl: (6) Could not resolve host: google.com

외부는 차단되지만, 같은 네트워크의 다른 컨테이너에는 접근 가능

특정 도메인만 허용하는 방법 (iptables 활용)

이건 컨테이너 내부가 아니라 호스트에서 설정합니다

$ docker run —rm -it
—cap-drop=ALL
—security-opt=no-new-privileges
ai-sandbox:latest

실무에서 내가 사용하는 전략은 이렇다. 코드 생성 단계에서는 네트워크를 열어두되, 코드 실행 단계에서는 --network none으로 완전히 차단한다. Agent가 pip install이나 npm install이 필요하면 코드 생성 단계에서 의존성을 설치하고, 실행은 격리된 환경에서 하는 방식이다.

Linux capabilities 제한

네트워크 차단과 함께 컨테이너의 Linux capabilities도 최소화하면 보안이 더 강화된다:

# 모든 capabilities 제거 후 필요한 것만 추가
$ docker run --rm -it \
    --cap-drop=ALL \
    --cap-add=CHOWN \
    --cap-add=SETUID \
    --cap-add=SETGID \
    --security-opt=no-new-privileges \
    --read-only \
    --tmpfs /tmp:size=512m \
    -v $(pwd)/project:/workspace/project \
    ai-sandbox:latest

—read-only: 컨테이너 파일시스템을 읽기 전용으로 마운트

—tmpfs /tmp: /tmp만 쓰기 가능 (크기 제한)

—security-opt=no-new-privileges: 권한 상승 차단

docker-compose로 multi-container 환경

AI agent가 단순히 코드만 실행하는 게 아니라, 벡터 DB에 임베딩을 저장하거나, 로컬 LLM을 호출하는 경우도 있다. 이런 복합 환경은 docker-compose로 관리하는 게 편하다.

# docker-compose.yml
version: "3.9"

services:

AI Agent 실행 환경

sandbox: build: context: . dockerfile: Dockerfile platform: linux/arm64 volumes: - ./project:/workspace/project - ./output:/workspace/output networks: - ai-internal deploy: resources: limits: cpus: “4” memory: 8G reservations: memory: 2G stdin_open: true tty: true

벡터 데이터베이스 (ChromaDB)

vectordb: image: chromadb/chroma:0.6.3 platform: linux/arm64 volumes: - chroma-data:/chroma/chroma networks: - ai-internal deploy: resources: limits: memory: 2G environment: - IS_PERSISTENT=TRUE - ANONYMIZED_TELEMETRY=FALSE

Redis (캐싱, 세션 관리)

redis: image: redis:7.4-alpine platform: linux/arm64 networks: - ai-internal deploy: resources: limits: memory: 512M command: redis-server —maxmemory 256mb —maxmemory-policy allkeys-lru

networks: ai-internal: internal: true # 외부 인터넷 접근 차단 driver: bridge

volumes: chroma-data:

이 구성의 핵심은 internal: true 네트워크이다. 세 컨테이너끼리는 서로 통신할 수 있지만, 외부 인터넷에는 접근할 수 없다. sandbox 컨테이너에서 http://vectordb:8000으로 ChromaDB에 접근하고, redis:6379로 Redis에 접근할 수 있지만, https://evil-server.com으로는 데이터를 보낼 수 없다.

# 전체 환경 시작
$ docker compose up -d
[+] Running 4/4
 ✔ Network ai-sandbox_ai-internal  Created
 ✔ Container ai-sandbox-redis-1     Started
 ✔ Container ai-sandbox-vectordb-1  Started
 ✔ Container ai-sandbox-sandbox-1   Started

sandbox 컨테이너에 접속

$ docker compose exec sandbox bash

agent@sandbox:/workspace$ # 내부 서비스 접근 테스트 agent@sandbox:/workspace$ curl http://vectordb:8000/api/v1/heartbeat {“nanosecond heartbeat”:1712345678901234567}

agent@sandbox:/workspace$ redis-cli -h redis ping PONG

agent@sandbox:/workspace$ curl https://google.com curl: (6) Could not resolve host: google.com

외부 접근 차단 확인

리소스 사용량 실시간 모니터링

$ docker compose stats NAME CPU % MEM USAGE / LIMIT ai-sandbox-sandbox-1 0.32% 245.1MiB / 8GiB ai-sandbox-vectordb-1 0.15% 187.3MiB / 2GiB ai-sandbox-redis-1 0.08% 12.4MiB / 512MiB

전체 환경 종료 및 정리

$ docker compose down $ docker compose down -v # 볼륨까지 삭제 (데이터 초기화)

Claude Code를 Docker 안에서 실행하기

Claude Code는 기본적으로 호스트 머신에서 실행되지만, Docker 컨테이너 안에서 실행하면 한 단계 더 격리된 환경을 만들 수 있다. 특히 Claude Code의 --dangerously-skip-permissions 모드를 사용할 때, 컨테이너 안에서 실행하면 실수로 호스트 시스템이 손상되는 것을 방지할 수 있다.

# Dockerfile.claude-code
FROM node:22-slim

시스템 패키지

RUN apt-get update && apt-get install -y —no-install-recommends
git
curl
python3
python3-pip
python3-venv
&& rm -rf /var/lib/apt/lists/*

Claude Code 설치

RUN npm install -g @anthropic-ai/claude-code

작업 디렉터리

WORKDIR /workspace

비root 사용자

RUN useradd -m -s /bin/bash developer
&& chown -R developer:developer /workspace

USER developer

CMD [“claude”]

실행할 때는 API 키를 환경변수로 전달한다:

# Claude Code를 Docker 안에서 실행
$ docker build -f Dockerfile.claude-code -t claude-sandbox .

$ docker run —rm -it
-e ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
-v $(pwd)/project:/workspace/project
—network none
claude-sandbox

Claude Code가 컨테이너 내부에서 실행됨

/workspace/project 안에서만 파일을 읽고 쓸 수 있음

네트워크는 차단되어 있지만, Claude API 호출이 필요하므로

실제로는 API 서버만 허용하는 네트워크 설정이 필요합니다

Anthropic API만 허용하는 네트워크 설정

$ docker network create claude-net

claude-net에서 Anthropic API만 허용하려면

프록시 컨테이너를 앞에 두는 방법이 현실적입니다

$ docker run —rm -it
-e ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
-v $(pwd)/project:/workspace/project
—network claude-net
claude-sandbox

솔직히 말하면 네트워크를 완전히 차단하면 Claude Code가 API 서버에 접근할 수 없어서 동작하지 않는다. 현실적인 방법은 claude-net 네트워크에 Squid 같은 프록시를 두고, api.anthropic.com만 화이트리스트로 허용하는 것이다. 이 부분은 환경에 따라 설정이 달라지므로, 우선은 일반 네트워크로 실행하되 볼륨 마운트 범위를 최소화하는 것부터 시작하는 것을 추천한다.

컨테이너 리소스 모니터링

AI agent가 코드를 실행하면 예상치 못한 리소스 소모가 발생할 수 있다. 무한 루프, 메모리 누수, 디스크 폭주 같은 상황을 빠르게 감지하려면 모니터링이 필수이다.

# 실시간 리소스 모니터링 (docker stats)
$ docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"

NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O ai-sandbox 34.52% 1.23GiB / 8GiB 15.38% 1.2kB / 0B 45MB / 12MB vectordb 2.31% 312.4MiB / 2GiB 15.26% 856B / 456B 23MB / 8MB redis 0.12% 18.7MiB / 512MiB 3.65% 234B / 123B 4MB / 1MB

특정 컨테이너만 모니터링

$ docker stats ai-sandbox —no-stream

—no-stream: 한 번만 출력하고 종료

컨테이너 내부에서 프로세스 확인

$ docker exec ai-sandbox ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND agent 1 0.0 0.0 4624 3712 pts/0 Ss 10:23 0:00 bash agent 45 98.2 2.1 345612 178432 pts/0 R 10:25 1:23 python train.py

CPU 98% 사용하는 프로세스 발견 → 필요시 강제 종료

$ docker exec ai-sandbox kill 45

컨테이너 자체를 즉시 중지 (비상 상황)

$ docker kill ai-sandbox

디스크 사용량 확인

$ docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 12 5 8.234GB 4.123GB (50%) Containers 3 3 245.1MB 0B (0%) Local Volumes 4 3 1.567GB 234MB (14%) Build Cache 23 0 2.345GB 2.345GB

사용하지 않는 리소스 정리

$ docker system prune -a —volumes WARNING! This will remove:

  • all stopped containers
  • all networks not used by at least one container
  • all anonymous volumes not used by at least one container
  • all images without at least one container associated to them
  • all build cache Total reclaimed space: 6.7GB

한 가지 팁을 주자면, docker compose에서 deploy.resources.limits를 설정해두면 컨테이너가 지정된 리소스 이상을 사용하지 못한다. 메모리 제한을 초과하면 커널이 OOM Killer로 프로세스를 종료한다. 이게 결국 agent가 무한정 리소스를 소모하는 것을 막는 하드 리밋 역할을 한다.

AI가 생성한 코드를 안전하게 실행하는 패턴

지금까지 설명한 Docker 설정을 종합해서, AI agent가 생성한 코드를 안전하게 실행하는 워크플로를 정리한다. 나는 이 패턴을 “일회용 컨테이너 패턴”이라고 부르고 있다.

패턴 1: 일회용 컨테이너로 코드 실행

#!/bin/bash
# run-sandbox.sh — AI가 생성한 코드를 안전하게 실행

SCRIPT_FILE=$1 TIMEOUT=${2:-30} # 기본 30초 타임아웃

if [ -z “$SCRIPT_FILE” ]; then echo “Usage: ./run-sandbox.sh <script_file> [timeout_seconds]” exit 1 fi

스크립트 파일을 임시 디렉터리에 복사

TEMP_DIR=$(mktemp -d) cp “$SCRIPT_FILE” “$TEMP_DIR/script.py”

일회용 컨테이너에서 실행

docker run —rm
—name “sandbox-$(date +%s)”
—network none
—cap-drop=ALL
—security-opt=no-new-privileges
—read-only
—tmpfs /tmp:size=256m,noexec
—memory=2g
—cpus=2
—pids-limit=100
-v “$TEMP_DIR:/workspace/run:ro”
-v “$TEMP_DIR/output:/workspace/output”
ai-sandbox:latest
timeout “$TIMEOUT” python /workspace/run/script.py

EXIT_CODE=$?

결과 확인

if [ $EXIT_CODE -eq 124 ]; then echo “TIMEOUT: 스크립트가 ${TIMEOUT}초 내에 완료되지 않았습니다.” elif [ $EXIT_CODE -ne 0 ]; then echo “ERROR: 종료 코드 $EXIT_CODE” else echo “SUCCESS” ls -la “$TEMP_DIR/output/” 2>/dev/null fi

임시 디렉터리 정리

rm -rf “$TEMP_DIR”

이 스크립트의 보안 레이어를 하나씩 분석하면:

패턴 2: Python 래퍼로 실행 결과 캡처

쉘 스크립트 대신 Python으로 래퍼를 만들면 실행 결과를 구조화된 형태로 받을 수 있다:

# sandbox_runner.py
import subprocess
import json
import tempfile
import os
from pathlib import Path
from dataclasses import dataclass, asdict

@dataclass class SandboxResult: success: bool stdout: str stderr: str exit_code: int timed_out: bool execution_time_ms: int

def run_in_sandbox( code: str, timeout: int = 30, memory_limit: str = “2g”, network: bool = False, ) -> SandboxResult: """AI가 생성한 Python 코드를 Docker 샌드박스에서 실행"""

with tempfile.TemporaryDirectory() as tmpdir:
    # 코드를 파일로 저장
    script_path = Path(tmpdir) / "script.py"
    script_path.write_text(code, encoding="utf-8")

    output_dir = Path(tmpdir) / "output"
    output_dir.mkdir()

    # Docker 실행 명령어 구성
    cmd = [
        "docker", "run", "--rm",
        "--network", "none" if not network else "bridge",
        "--cap-drop=ALL",
        "--security-opt=no-new-privileges",
        "--memory", memory_limit,
        "--cpus", "2",
        "--pids-limit", "100",
        "-v", f"{tmpdir}/script.py:/workspace/script.py:ro",
        "-v", f"{output_dir}:/workspace/output",
        "ai-sandbox:latest",
        "timeout", str(timeout),
        "python", "/workspace/script.py",
    ]

    import time
    start = time.monotonic()

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout + 10,  # Docker 오버헤드 고려
        )
        elapsed = int((time.monotonic() - start) * 1000)

        return SandboxResult(
            success=result.returncode == 0,
            stdout=result.stdout[:10000],   # 출력 크기 제한
            stderr=result.stderr[:5000],
            exit_code=result.returncode,
            timed_out=result.returncode == 124,
            execution_time_ms=elapsed,
        )
    except subprocess.TimeoutExpired:
        elapsed = int((time.monotonic() - start) * 1000)
        return SandboxResult(
            success=False,
            stdout="",
            stderr="Execution timed out",
            exit_code=-1,
            timed_out=True,
            execution_time_ms=elapsed,
        )

사용 예시

if name == “main”: ai_generated_code = """ import math result = [math.factorial(i) for i in range(20)] for i, val in enumerate(result): print(f”{i}! = {val}”) """

result = run_in_sandbox(ai_generated_code, timeout=10)
print(json.dumps(asdict(result), indent=2, ensure_ascii=False))</code></pre>

이 래퍼를 사용하면 AI agent가 생성한 코드의 실행 결과를 안전하게 받아볼 수 있다. 성공 여부, 표준 출력, 에러 메시지, 실행 시간까지 구조화된 형태로 반환된다.

실전에서 주의할 점

이 패턴을 실제로 운영하면서 알게 된 몇 가지 주의사항이다:

  • Docker 이미지 캐싱: 매번 새 컨테이너를 만들지만 이미지는 캐싱되므로 시작 시간이 1~2초 수준이다. 이미지를 미리 빌드해두면 체감 속도가 빠르다.
  • 파일 시스템 정리: --rm 플래그를 빠뜨리면 중단된 컨테이너가 디스크에 쌓인다. 주기적으로 docker container prune을 실행하자.
  • 로그 크기 제한: Agent가 무한 출력하는 코드를 생성할 수 있다. stdout의 크기를 코드 레벨에서 잘라내는 것이 안전하다.
  • 시크릿 주입: API 키 같은 비밀값을 컨테이너에 전달할 때는 --env보다 --env-file을 사용하자. 환경변수는 docker inspect로 볼 수 있기 때문에, 컨테이너가 남아있으면 노출될 수 있다.

AI agent를 활용하면서 가장 중요한 것은 결국 “통제 가능한 환경”을 만드는 것이다. Docker는 그 목적에 가장 적합한 도구이고, Apple Silicon Mac에서도 이제 충분히 성숙한 환경을 제공한다. 처음에는 설정이 번거롭게 느껴질 수 있지만, 한 번 구축해두면 어떤 코드든 걱정 없이 실행할 수 있다는 안도감이 상당하다. 자세한 내용은 Docker Desktop Mac 설치 가이드를 참고하자.


Share this post on:

Previous Post
API 키 보안 가이드 5단계 — .env 유출 방지부터 Mac Keychain까지
Next Post
Cursor VS Code 비교 — 5개월 vs 7개월 실사용 후 솔직한 결론