Cloudflare Sandbox로 AI 코드 실행 환경 만들기

Cloudflare Sandbox로 AI 코드 실행 환경 만들기

AI 코딩 도구를 만들다 보면 결국 “생성한 코드를 어디서 실행할 것인가?”라는 문제를 만나게 됩니다. 단순히 코드를 문자열로 돌려주는 것만으로는 부족하거든요. 테스트도 돌려봐야 하고, 파일도 만들어야 하고, 때로는 개발 서버를 띄운 뒤 브라우저에서 확인할 수 있는 미리보기 URL도 필요합니다.

그렇다고 사용자의 코드를 내 서버에서 그대로 실행하기에는 부담이 큽니다. 코드는 파일 시스템을 건드릴 수도 있고, 오래 실행될 수도 있고, 의도치 않게 서버 자원을 많이 쓸 수도 있으니까요. 그래서 AI 에이전트나 코드 실행 서비스를 만들 때는 격리된 실행 환경이 필요합니다.

Cloudflare Sandbox는 이 문제를 Cloudflare Workers와 컨테이너 위에서 풀어주는 도구입니다. 이번 글에서는 Cloudflare Sandbox SDK가 어떤 구조로 동작하는지, 명령어 실행과 파일 처리를 어떻게 하는지, 그리고 샌드박스 안에서 실행한 웹 서버를 외부 URL로 어떻게 노출하는지 살펴보겠습니다.

Cloudflare Sandbox란?

Cloudflare Sandbox는 격리된 컨테이너 환경에서 코드를 실행하고, 그 환경을 Workers 코드에서 제어할 수 있게 해주는 SDK입니다. 이름만 보면 CodeSandbox 같은 웹 IDE를 떠올리기 쉬운데요. Cloudflare Sandbox는 브라우저 편집기라기보다는 AI나 자동화 시스템이 코드를 안전하게 실행하기 위한 서버 측 실행 환경에 가깝습니다.

주요 기능은 다음과 같습니다.

  • 명령어 실행python, node, bun, git 같은 명령을 샌드박스 안에서 실행
  • 파일 시스템 접근 — 파일을 쓰고 읽고, 생성된 결과물을 다시 가져오기
  • 프로세스 관리 — 개발 서버처럼 오래 도는 프로세스를 시작하고 로그를 확인
  • 포트 노출 — 샌드박스 안의 웹 서버를 외부 URL로 공개
  • 컨테이너 기반 격리 — 실행 환경을 컨테이너로 분리

즉 사용자가 입력한 코드나 AI가 생성한 코드를 내 애플리케이션 서버에서 직접 실행하지 않고, 별도의 샌드박스 컨테이너 안에서 실행하도록 만드는 구조입니다.

어떻게 동작하나?

Cloudflare Sandbox SDK는 Cloudflare Workers, Durable Objects, Cloudflare Containers를 함께 사용합니다. 공식 문서의 구조를 단순화하면 이런 흐름입니다.

사용자 요청
  -> Cloudflare Worker
      -> Sandbox Durable Object
          -> Sandbox Container
              -> 명령 실행, 파일 처리, 프로세스 실행

Worker는 외부 요청을 받는 진입점입니다. 여기서 getSandbox()로 특정 샌드박스를 가져오고, 그 샌드박스에 명령 실행이나 파일 작업을 요청합니다.

Durable Object는 샌드박스의 생명주기와 상태를 관리합니다. 같은 이름의 샌드박스를 다시 가져오면 같은 실행 환경을 재사용할 수 있고, 컨테이너 런타임과 통신하는 중간 계층 역할도 합니다.

실제 명령어와 프로세스는 컨테이너 안에서 실행됩니다. 그래서 Workers만으로는 실행하기 어려운 Python 스크립트, CLI 도구, 빌드 명령, 테스트 실행 같은 작업을 처리할 수 있습니다.

기본 코드

Sandbox SDK를 Worker에서 쓰려면 @cloudflare/sandbox 패키지를 사용합니다. 새 프로젝트라면 먼저 패키지를 추가합니다.

bun add @cloudflare/sandbox

Worker 코드에서는 getSandbox()로 샌드박스 인스턴스를 가져옵니다. 아래 예제는 /run으로 들어오면 Python 코드를 실행하고, /file로 들어오면 파일을 쓰고 다시 읽습니다.

src/index.ts
import { getSandbox, proxyToSandbox, type Sandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

type Env = {
  Sandbox: DurableObjectNamespace<Sandbox>;
};

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const proxyResponse = await proxyToSandbox(request, env);
    if (proxyResponse) return proxyResponse;

    const url = new URL(request.url);
    const sandbox = getSandbox(env.Sandbox, "my-sandbox");

    if (url.pathname === "/run") {
      const result = await sandbox.exec('python3 -c "print(2 + 2)"');

      return Response.json({
        success: result.success,
        output: result.stdout,
      });
    }

    if (url.pathname === "/file") {
      await sandbox.writeFile("/workspace/hello.txt", "Hello, Sandbox!");
      const file = await sandbox.readFile("/workspace/hello.txt");

      return Response.json({
        content: file.content,
      });
    }

    return new Response("Try /run or /file");
  },
};

여기서 눈여겨볼 부분은 proxyToSandbox()입니다. 샌드박스 안에서 실행한 서비스를 미리보기 URL로 노출하려면 Worker가 해당 요청을 샌드박스로 프록시할 수 있어야 합니다. 그래서 요청 초반에 proxyToSandbox()를 먼저 호출하고, 해당 요청이 샌드박스 프록시 대상이면 그대로 응답을 반환합니다.

설정 파일

샌드박스는 컨테이너 위에서 돌아가므로 Wrangler 설정에도 컨테이너 이미지 정보를 넣어야 합니다. 공식 예제에서는 wrangler.toml에 다음과 같이 컨테이너 이미지를 지정합니다.

wrangler.toml
[containers]
image = "./Dockerfile"
max_instances = 1

실제 프로젝트에서는 여기에 Worker 이름, 메인 파일, 호환성 날짜, Durable Object 바인딩 같은 설정도 함께 들어갑니다. WranglerCloudflare Containers를 이미 써보셨다면 익숙한 구조일 거예요.

컨테이너 이미지는 일반적인 Dockerfile로 정의합니다. 샌드박스 안에서 어떤 언어와 도구를 지원할지에 따라 이미지 구성이 달라집니다. Python 코드 실행이 목적이라면 Python 런타임을, Node.js 프로젝트 빌드가 목적이라면 Node.js와 패키지 매니저를 넣는 식입니다.

명령어 실행

가장 기본적인 사용법은 exec()입니다. 짧게 끝나는 명령어를 실행하고 표준 출력, 표준 오류, 성공 여부를 받아올 수 있습니다.

const result = await sandbox.exec("python3 --version");

if (!result.success) {
  console.error(result.stderr);
}

console.log(result.stdout);

AI 에이전트에서는 이 패턴이 자주 등장합니다. 예를 들어 모델이 테스트 코드를 수정했다면 샌드박스 안에서 bun testpytest를 실행하고, 실패 로그를 다시 모델에게 넘겨 다음 수정을 요청할 수 있습니다. 사용자 로컬 머신이나 애플리케이션 서버를 건드리지 않고도 이런 반복 루프를 만들 수 있다는 점이 핵심입니다.

파일 작업도 같은 방식으로 이어집니다. 모델이 만든 코드를 /workspace 아래에 쓰고, 실행 결과나 생성된 파일을 다시 읽어올 수 있습니다.

await sandbox.writeFile(
  "/workspace/main.py",
  `
print("Hello from Cloudflare Sandbox")
`,
);

const result = await sandbox.exec("python3 /workspace/main.py");
console.log(result.stdout);

오래 도는 프로세스 실행

빌드 명령처럼 금방 끝나는 작업은 exec()로 충분하지만, 개발 서버처럼 계속 떠 있어야 하는 작업도 있습니다. 이럴 때는 startProcess()를 사용합니다.

const process = await sandbox.startProcess("python3 -m http.server 8080");

console.log(process.id);
console.log(process.pid);

이렇게 시작한 프로세스는 샌드박스 안에서 계속 실행됩니다. 이후 로그를 스트리밍하거나, 필요 없어졌을 때 종료하는 식으로 관리할 수 있습니다.

AI 기반 코드 실행 서비스에서는 이 기능이 꽤 중요합니다. 단순한 계산 결과만 보여주는 것이 아니라, 사용자가 만든 웹 앱을 실제로 띄워서 확인하게 만들 수 있기 때문입니다.

포트를 외부 URL로 노출하기

Cloudflare Sandbox가 흥미로운 이유는 실행한 서버를 바로 외부 URL로 공개할 수 있다는 점입니다. 예를 들어 샌드박스 안에서 8080번 포트로 웹 서버를 띄운 뒤, tunnels.get()으로 터널 URL을 만들 수 있습니다.

await sandbox.startProcess("python3 -m http.server 8080");

const tunnel = await sandbox.tunnels.get(8080);

console.log(tunnel.url);

이 경우 결과는 이런 식의 임시 URL입니다.

https://random-words-here.trycloudflare.com

이 URL은 Cloudflare Tunnel의 빠른 터널(Quick Tunnel)을 사용합니다. 계정이나 DNS 설정 없이 바로 공개 URL을 받을 수 있어서 데모나 미리보기에 잘 맞습니다. 다만 컨테이너가 재시작되면 새 URL이 배정되므로 영구 주소로 생각하면 안 됩니다.

더 안정적인 주소가 필요하다면 이름을 붙인 터널(named tunnel)을 만들 수 있습니다.

const tunnel = await sandbox.tunnels.get(8080, {
  name: "my-app-preview",
});

console.log(tunnel.url);

이 방식은 Cloudflare에 연결된 도메인이 있을 때 https://my-app-preview.example.com 같은 안정적인 호스트명을 사용할 수 있게 해줍니다. 공식 문서 기준으로 named tunnel은 Cloudflare 계정, API 토큰, Cloudflare zone 설정이 필요합니다.

한 가지 주의할 점은 터널로 노출할 서비스가 샌드박스 안에서 0.0.0.0:<port>로 리스닝해야 한다는 것입니다. localhost에만 묶여 있으면 컨테이너 내부 프록시가 접근하지 못할 수 있어요.

Docker도 쓸 수 있을까?

Cloudflare Sandbox는 컨테이너 안에서 Docker를 쓰는 패턴도 지원합니다. 예를 들어 샌드박스 안에 Dockerfile을 쓰고, 이미지를 빌드한 뒤 실행할 수 있습니다.

await sandbox.writeFile(
  "/workspace/Dockerfile",
  `
FROM alpine:latest
RUN apk add --no-cache curl
CMD ["echo", "Hello from Docker!"]
`,
);

const build = await sandbox.exec(
  "docker build --network=host -t my-image /workspace",
);

if (!build.success) {
  console.error(build.stderr);
}

const run = await sandbox.exec("docker run --network=host --rm my-image");
console.log(run.stdout);

이 기능은 테스트 환경을 Docker 기준으로 맞춰야 할 때 유용합니다. 다만 Docker까지 열어두면 실행 환경이 훨씬 강력해지는 만큼, 어떤 명령을 허용할지와 실행 시간을 어떻게 제한할지 더 신중하게 설계해야 합니다.

언제 쓰면 좋을까?

Cloudflare Sandbox는 일반적인 웹 API 서버를 만들기 위한 첫 번째 선택지는 아닙니다. 그런 용도라면 Workers, Pages, Containers 같은 제품을 직접 쓰는 편이 더 단순합니다.

대신 다음과 같은 상황에서 빛을 발합니다.

우선 AI 에이전트가 작성한 코드를 실제로 실행해야 할 때 좋습니다. 코드를 생성하고, 파일에 쓰고, 테스트를 돌리고, 실패 로그를 다시 모델에게 전달하는 루프를 만들 수 있습니다.

또한 사용자별 임시 개발 환경을 만들어야 할 때도 잘 맞습니다. 각 사용자나 세션마다 샌드박스 이름을 다르게 잡으면 실행 환경을 분리할 수 있고, 필요할 때 미리보기 URL까지 제공할 수 있습니다.

그리고 교육용 코드 실행기나 문서 예제 실행 환경에도 어울립니다. 독자가 작성한 코드를 샌드박스 안에서 돌리고 결과만 보여주면, 운영 서버를 직접 노출하지 않고도 인터랙티브한 경험을 만들 수 있습니다.

반면 단순한 HTTP API, 정적 사이트, 이미 컨테이너화된 장기 실행 서비스라면 Sandbox보다 다른 Cloudflare 제품이 더 자연스러울 수 있습니다. Cloudflare Workers는 가벼운 API에 적합하고, Cloudflare Containers는 컨테이너 앱을 직접 운영하는 데 맞습니다. Sandbox는 이 둘을 바탕으로 “코드를 실행하고 조작하는 API”를 얹은 도구라고 보면 이해하기 쉽습니다.

보안 관점에서 볼 점

샌드박스라는 이름이 붙어 있어도, 아무 코드나 아무 제한 없이 실행해도 된다는 뜻은 아닙니다. 격리된 컨테이너를 사용하더라도 명령 실행, 파일 접근, 네트워크 접근, Docker 실행은 모두 강한 권한입니다.

실제 서비스로 만들 때는 몇 가지 원칙을 세우는 게 좋습니다. 실행 시간을 제한하고, 동시에 실행할 수 있는 샌드박스 수를 제한하고, 사용자별 작업 디렉토리를 분리해야 합니다. 또한 민감한 API 키나 운영 데이터가 샌드박스 안으로 들어가지 않도록 환경 변수를 최소화해야 합니다.

외부 URL을 만들 때도 주의가 필요합니다. 빠른 터널은 주소를 아는 사람이 접속할 수 있으므로, 민감한 관리자 페이지나 인증 없는 내부 도구를 그대로 노출하면 안 됩니다. 안정적인 주소가 필요하고 접근 제어가 중요하다면 named tunnel과 Cloudflare Access를 함께 검토하는 편이 안전합니다.

마치며

Cloudflare Sandbox는 Workers에서 컨테이너 실행 환경을 제어할 수 있게 해주는 도구입니다. 명령어를 실행하고, 파일을 다루고, 백그라운드 프로세스를 띄우고, 실행 중인 웹 서버를 공개 URL로 노출할 수 있습니다.

핵심은 “코드를 실행하는 기능”과 “그 실행 결과를 웹으로 보여주는 기능”을 한 흐름으로 묶을 수 있다는 점입니다. 그래서 AI 코딩 에이전트, 코드 인터프리터, 테스트 실행기, 미리보기 환경을 만들 때 특히 잘 맞습니다.

더 자세한 내용은 Cloudflare Sandbox 공식 문서Sandbox SDK 저장소를 참고하세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord