Amazon SageMaker AI の Code Editor をサンドボックスとして使う

Amazon SageMaker AI の Code Editor をサンドボックスとして使う

SageMaker Studio の Code Editor を使い、AWS 上に柔軟なサンドボックス開発環境を構築する方法を紹介します。CDK でのデプロイからカスタムイメージの設定、Claude Code 拡張機能の導入まで、実装手順をまとめました。
2026.06.22

はじめに

こんにちは、Kanaru です。コーディングエージェントを使った開発において、サンドボックス環境の構築は重要なテーマとなっています。

本記事では、SageMaker Studio の Code Editor を使用して、AWS 上に柔軟なサンドボックスを構築する方法を説明します。また、サンドボックス上で IDE と Claude Code によるコーディングを可能にする方法も紹介します。

想定読者

  • AWS 上にサンドボックスの開発環境を構築したい方
  • サンドボックス環境を柔軟に管理したい方
  • SageMaker Studio の Code Editor に拡張機能を導入したい方

本記事で紹介しないこと

  • SageMaker Studio 以外を使った開発環境の構築

Amazon SageMaker AI の概要

AWS には Amazon SageMaker AI という、機械学習に特化したマネージドの開発基盤があります。その中で、Web 上で開発から実行まで統合して行うことのできるインターフェースが SageMaker Studio です。今回は Studio に含まれる Code Editor 機能を使用し、サンドボックス環境で IDE を動かします。

概念図は以下の通りです。

sagemaker.png

ドメイン

SageMaker AI の設定単位です。ドメインの中にユーザープロファイルとスペースが含まれます。

ユーザープロファイル

SageMaker AI 上のユーザーです。ユーザーごとに使用できるアプリケーションやインスタンスファミリーを指定するなど、様々な設定が利用可能です。

HomeEFS

ユーザープロファイルごとにストレージ領域を割り当てることができます。この領域はドメインに紐づく EFS(HomeEFS) で管理されます。この領域はアクセスするスペースに自動でマウントされます。スペースの EBS は共通ではないため、スペース間でデータを共有するにはこのストレージを使用する必要があります。

アプリケーション

SageMaker AI 上で使用できるアプリです。Code Editor や JupyterLab などがあります。

スペース

アプリケーションが動作する環境です。コンピューティングのための EC2 インスタンスとストレージ用の EBS が含まれます。1つのスペースは1つのアプリケーションしか動かせません。つまり、複数のアプリケーションを使用する場合にはアプリケーションごとにスペースを作成する必要があります。

スペースにはプライベートスペース共有スペースがあります。
プライベートスペースはスペースを所有するユーザーしかアクセスできません。一方、共有スペースは複数のユーザーがアクセスできます。
今回使用する Code Editor は必ずプライベートスペースで動かす必要があります。

イメージ

スペース内で動作するコンテナのためのベースイメージです。カスタムイメージを使用する場合には、ECR 上のイメージを事前にアタッチし、実行ロールなどを設定しておく必要があります。

Studio

ユーザープロファイルごとに用意される Web インターフェースです。ここから様々なアプリケーションにアクセスできます。

デプロイする

CDK でデプロイします。現在(2026年6月)時点では SageMaker AI 周りの L2 Construct が存在しないため、L1 Construct で構築します。

ドメインの作成

次のコードでドメインを構築します。

import type * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecr_assets from "aws-cdk-lib/aws-ecr-assets";
import * as iam from "aws-cdk-lib/aws-iam";
import * as sagemaker from "aws-cdk-lib/aws-sagemaker";
import * as cdk from "aws-cdk-lib/core";
import * as custom_resources from "aws-cdk-lib/custom-resources";
import { Construct } from "constructs";

type SagemakerDomainProps = {
  vpc: ec2.IVpc;
  domainName?: string;
};

export class SagemakerDomain extends Construct {
  readonly domain: sagemaker.CfnDomain;
  readonly domainCleanup: custom_resources.AwsCustomResource;

  constructor(scope: Construct, id: string, props: SagemakerDomainProps) {
    super(scope, id);

    const defaultRole = new iam.Role(this, "DefaultRole", {
      assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSageMakerFullAccess"),
      ],
    });

    const domainName =
      props.domainName ||
      cdk.Names.uniqueResourceName(this, {
        maxLength: 40,
        separator: "-",
      });

    const domain = new sagemaker.CfnDomain(this, "SagemakerDomain", {
      authMode: "IAM",
      defaultUserSettings: {
        executionRole: defaultRole.roleArn,
        studioWebPortalSettings: {
          hiddenAppTypes: [
            "JupyterServer",
            "DetailedProfiler",
            "TensorBoard",
            "JupyterLab",
            "RStudioServerPro",
            "Canvas",
          ],
        },
      },
      domainName: domainName,
      subnetIds: props.vpc.privateSubnets.map((subnet) => subnet.subnetId),
      vpcId: props.vpc.vpcId,
    });

    domain.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);

    const cleanupEFS = new custom_resources.AwsCustomResource(this, "CleanupEFS", {
      onDelete: {
        service: "SageMaker",
        action: "deleteDomain",
        parameters: {
          DomainId: domain.attrDomainId,
          RetentionPolicy: {
            HomeEfsFileSystem: "Delete",
          },
        },
        physicalResourceId: custom_resources.PhysicalResourceId.of(
          `DeleteDomain-${domain.attrDomainId}`,
        ),
        ignoreErrorCodesMatching: "ResourceNotFound"
      },
      policy: custom_resources.AwsCustomResourcePolicy.fromStatements([
        new iam.PolicyStatement({
          actions: ["sagemaker:DeleteDomain"],
          resources: ["*"],
        }),
      ]),
    });
    cleanupEFS.node.addDependency(domain);

    this.domain = domain;
    this.domainCleanup = cleanupEFS;
  }
}

コードを解説します。
L1 Construct sagemaker.CfnDomain でドメインを作成しています。ここで、homeEfsFileSystemCreation を明示的に Disabled にしない限り、自動で HomeEFS が作成されます。しかし、ドメイン削除時はこの EFS の削除処理が自動で実行されないため、リソースが残り続け削除に失敗します。
一方、API 経由の削除であれば、RetentionPolicyHomeEfsFileSystem: "Delete" を指定することで作成した EFS を併せて削除してくれます。
よって、ドメインは CloudFormation ではなくカスタムリソースで削除することにします。この場合、CloudFormation とカスタムリソースでドメインを重複削除しようとしてしまうので、ドメインの方の Removal Polycy は RETAIN に設定します。

もし削除時に EFS を残したい場合は、依存関係となるドメインと VPC を RETAIN に設定してください。

ユーザープロファイルの作成

以下のコードでユーザープロファイルを作成します。

import * as sagemaker from "aws-cdk-lib/aws-sagemaker";
import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";

type SagemakerUserProfileProps = {
  domainId: string;
  userProfileName?: string;
  removalPolicy?: cdk.RemovalPolicy;
};

export class SagemakerUserProfile extends Construct {
  readonly name: string;

  constructor(scope: Construct, id: string, props: SagemakerUserProfileProps) {
    super(scope, id);

    const userProfileName = props.userProfileName ?? cdk.Names.uniqueResourceName(this, {
      separator: "-",
      maxLength: 63,
    }).toLocaleLowerCase();
    const userProfile = new sagemaker.CfnUserProfile(this, "SagemakerUserProfile", {
      domainId: props.domainId,
      userProfileName: userProfileName,
    });
    userProfile.applyRemovalPolicy(props.removalPolicy ?? cdk.RemovalPolicy.RETAIN);

    this.name = userProfileName;
  }
}

ただ L1 Construct を使っているだけなので特筆すべき点はありません。ユーザープロファイルの細かい設定に関しては、ドメイン側でデフォルト設定ができるので、それで制御する想定です。もしユーザープロファイルごとに設定したい場合は、プロパティを追記してください。

スペースの作成

以下のコードでスペースを作成します。

import * as sagemaker from "aws-cdk-lib/aws-sagemaker";
import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";

type SagemakerSpaceProps = {
  spaceName?: string;
  domainId: string;
  removalPolicy?: cdk.RemovalPolicy;
  ownerUserProfileName: string;
};

export class SagemakerSpace extends Construct {
  constructor(scope: Construct, id: string, props: SagemakerSpaceProps) {
    super(scope, id);

    const spaceName =
      props.spaceName ||
      cdk.Names.uniqueResourceName(this, {
        separator: "-",
        maxLength: 63,
      }).toLocaleLowerCase();

    new sagemaker.CfnSpace(this, "SagemakerSpace", {
      spaceName,
      domainId: props.domainId,
      ownershipSettings: {
        ownerUserProfileName: props.ownerUserProfileName,
      },
      spaceSharingSettings: {
        sharingType: "Private",
      },
      spaceSettings: {
        appType: "CodeEditor",
      },
    });
  }
}

こちらも L1 Construct をそのまま使っているだけです。Code Editor のスペースの共有設定は必ずプライベートである必要があるので注意してください。

カスタムイメージの使用

Code Editor にデフォルトの拡張機能を設定するための方法の1つに、カスタムイメージを使用する方法があります。今回はこの方法を使用して、Code Editor のデフォルトとして Claude Code の拡張機能をインストールしたいと思います。

イメージの用意(SageMaker のベースイメージを使う方法)

Dockerfile でイメージを作成します。

Dockerfile
FROM --platform=linux/amd64 public.ecr.aws/sagemaker/sagemaker-distribution:latest-cpu
ARG NB_USER="sagemaker-user"
ARG NB_UID=1000
ARG NB_GID=100
ENV MAMBA_USER=$NB_USER

USER root

RUN sagemaker-code-editor \
    --install-extension Anthropic.claude-code \
    --extensions-dir /opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions

USER $MAMBA_USER
ENTRYPOINT ["entrypoint-code-editor"]

ベースイメージである public.ecr.aws/sagemaker/sagemaker-distribution は、何も指定しなかった際にデフォルトで指定されるイメージです。今回は CPU のみのインスタンスタイプを使用する前提なのでタグが latest-cpu ですが、GPU を含むインスタンスタイプを使用する場合は latest-gpu のタグを指定してください。

sagemaker-code-editor コマンドで拡張機能のインストールができます。拡張機能の指定は、Open VSX のパスを見て、<名前空間>.<拡張機能名> の形式で指定します。

イメージの用意(自分で構築する方法)

上記のSageMaker AI のイメージには機械学習を扱う上での諸ツールが含まれるため 4GB ほどの容量があり、カスタムイメージの容量も膨らんでしまいます。Code Editor だけを使うのであれば不要なものも多いため、より小さいベースイメージでカスタムイメージを構築する方法を紹介します。

以下のような Dockerfile を使います。

Dockerfile
FROM --platform=linux/amd64 mambaorg/micromamba:latest
ARG NB_USER="mambauser"
ARG NB_UID=1000
ARG NB_GID=100

USER root

RUN micromamba install -y --name base -c conda-forge sagemaker-code-editor && \
    micromamba clean --all --yes

RUN micromamba run -n base sagemaker-code-editor \
    --install-extension Anthropic.claude-code \
    --extensions-dir /home/$NB_USER/.code-editor-server/extensions && \
    chown -R $NB_UID:$NB_GID /home/$NB_USER

USER $NB_UID

CMD ["micromamba", "run", "-n", "base", \
     "sagemaker-code-editor", \
     "--host", "0.0.0.0", \
     "--port", "8888", \
     "--without-connection-token", \
     "--base-path", "/CodeEditor/default"]

上記イメージでは micromamba をベースイメージとして採用し、sagemaker-code-editor をインストールしています。拡張機能のインストール方法は先ほどとほぼ同じコマンドですが、インストール先のパスが異なるため注意してください。

SageMaker AI Image の構築

ビルドしたイメージは ECR に保存した上で、SageMaker AI のイメージとして参照する必要があります。これらの処理を CDK の中で実行するためには次のようにします。

const imageAsset = new ecr_assets.DockerImageAsset(this, "CodeEditorImage", {
    directory: "path/to/dockerfile",
});

const imageRole = new iam.Role(this, "ImageRole", {
    assumedBy: new iam.ServicePrincipal("sagemaker.amazonaws.com"),
});

imageAsset?.repository.grantPull(imageRole);
imageAsset?.repository.grantPull(defaultRole);

const imageName = cdk.Names.uniqueResourceName(this, {
    separator: "-",
    maxLength: 63,
}).toLocaleLowerCase();
const sagemakerImage = new sagemaker.CfnImage(this, "SagemakerImage", {
        imageName,
        imageRoleArn: imageRole.roleArn,
        imageDisplayName: "dummyImageDisplayName",
    })
    : undefined;

const sagemakerImageVersion = new sagemaker.CfnImageVersion(this, "SagemakerImageVersion", {
        imageName,
        baseImage: imageAsset?.imageUri,
    })
    : undefined;
sagemakerImageVersion?.addDependency(sagemakerImage);

const imageRoleDefaultPolicy = imageRole.node.tryFindChild("DefaultPolicy");
if (imageRoleDefaultPolicy && sagemakerImageVersion) {
    sagemakerImageVersion.node.addDependency(imageRoleDefaultPolicy);
}

const appImageConfigName = `${domainName}-app-image-config`;
const appImageConfig = new sagemaker.CfnAppImageConfig(
    this,
    "AppImageConfig",
    {
        appImageConfigName,
        codeEditorAppImageConfig: {
            containerConfig: {},
        },
    },
);

const domain = new sagemaker.CfnDomain(this, "SagemakerDomain", {
    authMode: "IAM",
    defaultUserSettings: {
        executionRole: defaultRole.roleArn,
        codeEditorAppSettings: {
            customImages: [
                {
                    imageName: sagemakerImage.imageName,
                    appImageConfigName: appImageConfigName,
                    imageVersionNumber: sagemakerImageVersion.attrVersion,
                },
            ],
            defaultResourceSpec: {
            instanceType: "ml.t3.medium",
            sageMakerImageArn: sagemakerImage?.attrImageArn,
            sageMakerImageVersionArn:
                sagemakerImageVersion?.attrImageVersionArn,
            },
        },
        studioWebPortalSettings: {
            hiddenAppTypes: [
                "JupyterServer",
                "DetailedProfiler",
                "TensorBoard",
                "JupyterLab",
                "RStudioServerPro",
                "Canvas",
            ],
        },
    },
    domainName: domainName,
    subnetIds: props.vpc.privateSubnets.map((subnet) => subnet.subnetId),
    vpcId: props.vpc.vpcId,
});

domain.addDependency(sagemakerImageVersion);
domain.addDependency(appImageConfig);

CfnImageCfnImageVersionCfnAppImageConfig の3リソースを追加しました。また、CfnDomain のプロパティに codeEditorAppSettings を追加し、カスタムイメージの設定を追加しました。

おわりに

claude-code.png

SageMaker AI の Code Editor を使用することで、サンドボックスの開発環境を簡単に構成できました。カスタムイメージが使用できるということで、柔軟性も十分だと感じました。


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

関連記事