電通総研 テックブログ

電通総研が運営する技術ブログ

AWS Deployment SOPsを使ってみた

クロスイノベーション本部、新卒1年目の大岡叡です。
今回は、1月29日にプレビュー公開されたAWS Deployment SOPsを使ってみたので、その内容を報告します。

AWS Deployment SOPsを使ってみた結果、簡単なプロンプトを一度与えるだけで静的Webサイトをデプロイでき、CodePipelineのCI/CDについても簡単に構築することができました。この検証を通じて、非エンジニアがインフラ構築をして簡単なアプリケーションのデプロイを行う日も遠くないのではないかと思ったと同時に、フルスタックアプリケーションも今回のように簡単なプロンプトを一度与えるだけでデプロイできる時代が来るのではないかと、期待が膨らみました。

AWS MCP Serverとは

AWS Deployment SOPsの説明をするうえで前提となるAWS MCP Serverについて説明します。AWS MCP Serverは8つのツールを提供しており、AWS Knowledge ToolsAWS API ToolsAgent SOP Toolsの3つのカテゴリに分類されています。

AWS Knowledge Tools

  • aws___search_documentationAPIリファレンス・ベストプラクティス・サービスガイドを含む、すべてのAWSドキュメントを横断的に検索して関連情報を見つける。
  • aws___read_documentationAWSドキュメントページを取得し、AIアシスタントが利用しやすいようにMarkdown形式に変換する。
  • aws___recommendAWSドキュメントページに関連するトピックや、他のユーザーがよく一緒に閲覧しているコンテンツに基づいて、おすすめのドキュメントページを取得する。
  • aws___list_regions:リージョンID(例:ap-northeast-1)と名称(例:Asia Pacific (Tokyo))のペアの一覧を取得する。
  • aws___get_regional_availability:サービス、機能、SDK API、CloudFormationリソースについて、AWSリージョンごとの利用可否情報を確認する。

AWS API Tools

  • aws___call_awsAWS APIの呼び出しを実行する。
  • aws___suggest_aws_commands:関連するAWS APIの説明と構文ヘルプを取得する。AIモデルの学習データに含まれていない可能性がある、新しくリリースされたAPIにも対応できる。

Agent SOP Tools

  • aws___retrieve_agent_sop:Agent SOPsの検索または特定のSOPsの詳細情報の取得をする。

💡 Agent SOPsとは
Agent SOPsとは、Claude CodeのようなAIアシスタントAWS関連のタスクを行う際の標準作業手順書(Standard Operating Procedures)のことです。例えば、以下のようなSOPが用意されています。

SOP名 説明
create-secrets-using-best-practices ローテーション機能とKMSを備えたSecrets Managerによるシークレット作成
create-budget アラート機能付きのAWS Budgets作成
setup_cloudwatch_alarm_notifications SNS経由でのCloudWatchアラーム通知セットアップ
application-failure-troubleshooting CloudWatchログを分析してアプリケーション障害をデバッグ

AWS Deployment SOPsとは

AWS Deployment SOPsは、aws___retrieve_agent_sopのツールが取得するデプロイ用のSOPのことです。
以下の4つのSOPがDeployment SOPsです。

SOP名 説明
deploy-webapp アプリケーションがDeployment SOPsでサポートされているものか確認し、適切なDeployment SOPを選択する。
deploy-frontend-app AWS CDKのコードを生成し、アプリケーションをデプロイした後、デプロイしたWebアプリのURLを提供する。
setup-pipeline AWS CodePipelineを使用してパイプラインを作成し、GitHubに変更がプッシュされると自動的にアプリケーションの検証とデプロイを行う。
document-deployment デプロイに関するドキュメントを生成し、進捗を管理する。

Deployment SOPsがサポートしているアプリケーションタイプは以下のとおりです。

  • SPA:React, Vue, Angular, SvelteKit
  • SSG:Next.js (static export), Nuxt, Gatsby, Hugo, Jekyll, Docusaurus, Astro, Eleventy
  • Static websites

そして、Deployment SOPsは2つのユースケースAWSへのアプリケーションのデプロイ」と「CodePipelineのセットアップ」をサポートしています。今回はこの2つのユースケースを実際に試してみました。

検証の前提

  • 今回はコーディングエージェントとしてClaude Codeを使用しました。
  • Skills等のカスタマイズ系のものは使用しないようにしました。
  • AWS CLI(認証情報設定済み)やGit CLIなどの必要なツールはインストール済みです。
  • プロジェクトのルートで、以下のような.mcp.jsonを配置しました。
{
  "mcpServers": {
    "aws-mcp": {
      "command": "uvx",
      "args": [
        "mcp-proxy-for-aws@latest",
        "https://aws-mcp.us-east-1.api.aws/mcp"
      ]
    }
  }
}
  • あらかじめ以下のようなTODOアプリケーションをNext.jsの静的エクスポートで実装しました。これはプロジェクトのルートディレクトリにtodo-appというディレクトリ名で配置しました。

検証1:Next.jsアプリケーションのデプロイ

Claude Codeの挙動ベースで検証内容を報告します。結果としては、簡単なプロンプトを一度与えるだけでCloudFront + S3の構成を構築してデプロイを行い、CloudFrontのURLを共有してくれました。

検証内容

最初に「AWS MCPAWS Deployment SOPsを用いて、todo-appをAWSにデプロイしてください。」というプロンプトをClaude Codeに投げました(ちなみに、この検証で投げたプロンプトはこれだけです)。すると、AWS MCP Serverのaws___retrieve_agent_sopを用いてdeploy-webappのSOPを取得してきて、アプリケーションがDeployment SOPsでサポートされているタイプか分析してくれました。

アプリケーションのファイルの読み込みが完了した後、AWS MCP Serverのaws___retrieve_agent_sopを用いてdeploy-frontend-appのSOPを取得してきてくれました。そして、Next.jsの静的エクスポートはデプロイ可能ということで、デプロイ開始宣言をしてくれました。

続いて、DEPLOYMENT_PLAN.mdを生成してくれました。これ以降はこのプランに従って自律的に実装を進めてくれました。

---
sop_name: deploy-frontend-app, setup-pipeline
repo_name: aws-deployment-sops-test
app_name: TodoApp
app_type: Frontend Application (Next.js Static Export)
branch: main
created: 2026-02-01
last_updated: 2026-02-01
---

# Deployment Plan: TodoApp

Coding Agents should follow this Deployment Plan, and validate previous progress if picking up the Deployment in a new coding session.

**IMPORTANT**: Update this plan after EACH step completes. Mark the step `[x]` and update `last_updated` timestamp.

## Phase 1: Gather Context and Configure
- [x] Step 0: Inform User of Execution Flow
- [x] Step 1: Create Deployment Plan
- [] Step 2: Create Deploy Branch
- [] Step 3: Detect Build Configuration
- [] Step 4: Validate Prerequisites
- [] Step 5: Revisit Deployment Plan

## Phase 2: Build CDK Infrastructure
- [] Step 6: Initialize CDK Foundation
- [] Step 7: Generate CDK Stack
- [] Step 8: Create Deployment Script
- [] Step 9: Validate CDK Synth

## Phase 3: Deploy and Validate
- [] Step 10: Execute CDK Deployment
- [] Step 11: Validate CloudFormation Stack

## Phase 4: Update Documentation
- [] Step 12: Finalize Deployment Plan
- [] Step 13: Update README.md

そして、最終的にCloudFrontのURLを提供してくれました。このURLをクリックしたら実際にデプロイされたアプリケーションが正常に動作していることを確認できました。

実装してくれたCDKのコード

実際にClaude Codeが生成してくれたCDKのコードを以下に示します。CloudFront(Distribution, CloudFront Function)、S3(Webサイト用)、S3(CloudFrontアクセスログ用)、S3(S3アクセスログ用)などがセキュリティのベストプラクティスに基づいて実装されています。

import * as cdk from "aws-cdk-lib";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import { CloudFrontToS3 } from "@aws-solutions-constructs/aws-cloudfront-s3";
import { Construct } from "constructs";

export interface FrontendStackProps extends cdk.StackProps {
  environment: string;
  buildOutputPath: string;
}

export class FrontendStack extends cdk.Stack {
  public readonly distributionDomainName: string;
  public readonly bucketName: string;

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

    const { environment, buildOutputPath } = props;
    const isProd = environment === "prod";
    const removalPolicy = isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY;

    // CSP via CloudFront Function - permissive policy for 3rd-party CDNs/APIs
    const cspFunction = new cloudfront.Function(this, "CspFunction", {
      runtime: cloudfront.FunctionRuntime.JS_2_0,
      code: cloudfront.FunctionCode.fromInline(`
        function handler(event) {
          var response = event.response;
          response.headers['content-security-policy'] = {
            value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob:; style-src 'self' 'unsafe-inline' https:; font-src 'self' https: data:; img-src 'self' https: data: blob:; connect-src 'self' https: wss:; frame-src 'self' https:; media-src 'self' https: blob:; worker-src 'self' https: blob:; object-src 'self' https:; manifest-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
          };
          return response;
        }
      `),
      comment: "Adds Content-Security-Policy header",
    });

    // Extension rewrite function for Next.js static export (trailingSlash: false)
    // Rewrites /path to /path.html
    const extensionRewriteFunction = new cloudfront.Function(this, "ExtensionRewriteFunction", {
      runtime: cloudfront.FunctionRuntime.JS_2_0,
      code: cloudfront.FunctionCode.fromInline(`
        function handler(event) {
          var request = event.request;
          var uri = request.uri;
          if (!uri.includes('.') && uri !== '/') {
            request.uri = uri + '.html';
          }
          return request;
        }
      `),
      comment: "Rewrites /path to /path.html for Next.js static export",
    });

    const cloudfrontToS3 = new CloudFrontToS3(this, "CFToS3", {
      bucketProps: {
        removalPolicy,
        autoDeleteObjects: !isProd,
        versioned: false,
        enforceSSL: true,
      },
      loggingBucketProps: {
        removalPolicy,
        autoDeleteObjects: !isProd,
        lifecycleRules: [{ id: "DeleteOldLogs", enabled: true, expiration: isProd ? cdk.Duration.days(3650) : cdk.Duration.days(7) }],
        enforceSSL: true,
      },
      cloudFrontLoggingBucketProps: {
        removalPolicy,
        autoDeleteObjects: !isProd,
        lifecycleRules: [{ id: "DeleteOldLogs", enabled: true, expiration: isProd ? cdk.Duration.days(3650) : cdk.Duration.days(7) }],
        enforceSSL: true,
      },
      insertHttpSecurityHeaders: false,
      cloudFrontDistributionProps: {
        comment: `${id} - ${environment}`,
        defaultRootObject: "index.html",
        defaultBehavior: {
          responseHeadersPolicy: cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS,
          functionAssociations: [
            {
              function: cspFunction,
              eventType: cloudfront.FunctionEventType.VIEWER_RESPONSE,
            },
            {
              function: extensionRewriteFunction,
              eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
            },
          ],
        },
        priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
        enableIpv6: true,
        httpVersion: cloudfront.HttpVersion.HTTP2_AND_3,
        minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
      },
    });

    const websiteBucket = cloudfrontToS3.s3Bucket!;
    const distribution = cloudfrontToS3.cloudFrontWebDistribution;

    new s3deploy.BucketDeployment(this, "DeployWebsite", {
      sources: [s3deploy.Source.asset(buildOutputPath)],
      destinationBucket: websiteBucket,
      distribution,
      distributionPaths: ["/*"],
      prune: true,
      memoryLimit: 512,
    });

    this.distributionDomainName = distribution.distributionDomainName;
    this.bucketName = websiteBucket.bucketName;

    // Outputs
    new cdk.CfnOutput(this, "WebsiteURL", {
      value: `https://${distribution.distributionDomainName}`,
      description: "CloudFront distribution URL",
      exportName: `${id}-WebsiteURL`,
    });

    new cdk.CfnOutput(this, "BucketName", {
      value: websiteBucket.bucketName,
      description: "S3 bucket name",
      exportName: `${id}-BucketName`,
    });

    new cdk.CfnOutput(this, "DistributionId", {
      value: distribution.distributionId,
      description: "CloudFront distribution ID",
      exportName: `${id}-DistributionId`,
    });

    new cdk.CfnOutput(this, "DistributionDomainName", {
      value: distribution.distributionDomainName,
      description: "CloudFront domain name",
      exportName: `${id}-DistributionDomain`,
    });

    if (cloudfrontToS3.s3LoggingBucket) {
      new cdk.CfnOutput(this, "S3LogBucketName", {
        value: cloudfrontToS3.s3LoggingBucket.bucketName,
        description: "Bucket for S3 access logs",
        exportName: `${id}-S3LogBucket`,
      });
    }

    if (cloudfrontToS3.cloudFrontLoggingBucket) {
      new cdk.CfnOutput(this, "CloudFrontLogBucketName", {
        value: cloudfrontToS3.cloudFrontLoggingBucket.bucketName,
        description: "Bucket for CloudFront access logs",
        exportName: `${id}-CloudFrontLogBucket`,
      });
    }

    cdk.Tags.of(this).add("Stack", "Frontend");
    cdk.Tags.of(this).add("aws-mcp:deploy:sop", "deploy-frontend-app");
    cdk.Tags.of(cloudfrontToS3).add("Stack", "Frontend");
    cdk.Tags.of(cloudfrontToS3).add("aws-mcp:deploy:sop", "deploy-frontend-app");
  }
}

以上より、「AWS MCPAWS Deployment SOPsを用いて、todo-appをAWSにデプロイしてください。」と一言お願いをすれば、それなりの構成でインフラを構築してくれて、アプリケーションをデプロイできることを確認できました。これなら非エンジニアの方でも簡単かつ安全にアプリケーションをデプロイできるのではないでしょうか。

Deployment SOPsなしの場合

Deployment SOPsを使用せずにClaude Codeに「todo-appをAWSにデプロイしてください。」とお願いしたらどうなるか試してみました。ここでは非エンジニアがデプロイする想定で、条件などを一切与えずにお願いしました。こちらも検証1と同様、Claudeの指示に基本従う方針で進めました。

最初にdeploy方法の選択を迫られました。RecommendedのAWS Amplifyを選択しました。

デプロイを進めてくれて、最終的にAmplifyのURLを表示してくれました。

しかし、このURLにアクセスしたところ、以下のようなWebページでした。

Amplifyへのデプロイ手順はCloudFront + S3構成と比べて複雑ではないものの、単に「デプロイして」と依頼するだけでは、現状のClaude Codeでは自律的にタスクを完遂することが難しいようです。

検証2:CodePipelineのセットアップ

Claude Codeの挙動ベースに検証内容を報告します。CodePipelineが構築され、GitHubにプッシュするだけで変更が自動的にデプロイされる仕組みが整いました。

検証内容

最初に「AWS MCPAWS Deployment SOPsを用いて、CodePipelineをセットアップしてください。」というプロンプトをClaude Codeに投げました。すると、AWS MCP Serverのaws___retrieve_agent_sopを用いてsetup-pipelineのSOPを取得してきて、CodePipelineのセットアップを開始してくれました。

次に以下のような確認をされました。lintエラーの修正だけお願いしておきました。

検証1と同様にDEPLOYMENT_PLAN.mdを更新しながらCDKのコード実装など色々やってくれた後に、以下の認証をお願いされました。これは手順通り対応しました。

その後、パイプラインのデプロイなど色々やってくれてタスクが完了しました。

CodePipelineの動作を確認するために以下のようにh1の中身に変更を加えて、deploy-to-awsブランチにプッシュしたところ、CodePipelineがトリガーされてビルド→デプロイが実行され、Webページに変更内容が反映されました。

実装してくれたCDKのコード

実際にClaude Codeが生成してくれたCDKのコードを以下に示します。CodePipeline、CodeBuildプロジェクト、S3バケットアーティファクト用)が構築されます。

import * as cdk from "aws-cdk-lib";
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as pipelines from "aws-cdk-lib/pipelines";
import { Construct } from "constructs";
import { FrontendStack } from "./frontend-stack";

export interface PipelineStackProps extends cdk.StackProps {
  codeConnectionArn: string;
  repositoryName: string;
  branchName: string;
}

export class PipelineStack extends cdk.Stack {
  public readonly pipeline: pipelines.CodePipeline;

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

    const source = pipelines.CodePipelineSource.connection(
      props.repositoryName,
      props.branchName,
      {
        connectionArn: props.codeConnectionArn,
        triggerOnPush: true,
      }
    );

    const synth = new pipelines.ShellStep("Synth", {
      input: source,
      commands: [
        // Install dependencies
        "(cd todo-app && npm install)",
        "(cd infra && npm install)",

        // Quality checks
        "(cd todo-app && npm run lint)",

        // Secret scanning
        "npx -y @secretlint/quick-start '**/*'",

        // Build frontend
        "(cd todo-app && npm run build)",

        // CDK synth
        "cd infra",
        "npx tsc",
        `npx -y cdk synth --context codeConnectionArn="${props.codeConnectionArn}" --context repositoryName="${props.repositoryName}" --context branchName="${props.branchName}"`,
      ],
      primaryOutputDirectory: "infra/cdk.out"
    });

    this.pipeline = new pipelines.CodePipeline(this, "Pipeline", {
      pipelineName: "TodoAppPipeline",
      selfMutation: true,
      pipelineType: codepipeline.PipelineType.V2,
      synth,
      synthCodeBuildDefaults: {
        buildEnvironment: {
          computeType: codebuild.ComputeType.MEDIUM,
          buildImage: codebuild.LinuxBuildImage.STANDARD_7_0
        },
        partialBuildSpec: codebuild.BuildSpec.fromObject({
          version: "0.2",
          phases: {
            install: {
              "runtime-versions": {
                nodejs: "latest",
              },
            },
          },
        }),
      },
    });

    // Deploy stage for production
    const deployStage = new cdk.Stage(this, "Deploy", {
      env: { account: this.account, region: this.region },
    });
    new FrontendStack(deployStage, "TodoAppFrontend-prod", {
      stackName: "TodoAppFrontend-prod",
      environment: "prod",
      buildOutputPath: "../todo-app/out",
    });
    this.pipeline.addStage(deployStage);

    // Build pipeline to enable property access
    this.pipeline.buildPipeline();

    cdk.Tags.of(this).add("Stack", "Pipeline");
    cdk.Tags.of(this).add("aws-mcp:deploy:sop", "setup-pipeline");

    new cdk.CfnOutput(this, "PipelineName", {
      value: "TodoAppPipeline",
      description: "CodePipeline Name",
    });

    new cdk.CfnOutput(this, "PipelineArn", {
      value: this.pipeline.pipeline.pipelineArn,
      description: "CodePipeline ARN",
    });
  }
}

まとめ

AWS Deployment SOPsを用いて、静的エクスポートしたNext.jsアプリケーションをデプロイしたり、CodePipelineのセットアップを行いました。特に問題なくAIがすべての作業を完了してくれたことは驚きでした。近い将来、非エンジニアでもデプロイを行える日は遠くないと感じました。さらに、フルスタックアプリケーションも簡単なプロンプトを一度与えるだけで構築できる時代が来るのではないかと、期待が膨らみました。

最後までお読みいただきありがとうございました!

参考文献

私たちは一緒に働いてくれる仲間を募集しています!

電通総研 キャリア採用サイト 電通総研 新卒採用サイト

執筆:@ooka.toru
レビュー:@miyazawa.hibiki
Shodoで執筆されました