「コンテナアプリをAWSにデプロイしたいけど、ECSやEKSは設定が複雑すぎる」「もっと手軽にコンテナを動かしたい」——こうした悩みを持つSESエンジニアに最適なのが、AWS App Runnerです。
結論から言えば、AWS App Runnerを使えばコンテナイメージまたはソースコードからわずか数ステップでWebアプリケーションをデプロイできます。本記事では、基本的な使い方からオートスケール設定、CI/CD連携まで実践的に解説します。
この記事を3秒でまとめると
- App RunnerはECS/EKSより圧倒的にシンプルなコンテナ実行サービス
- ECR連携で自動デプロイ、オートスケーリングも組み込み済み
- VPC Connectorで既存のRDS/ElastiCacheとも安全に接続可能

AWS App Runnerとは
AWS App Runnerは、2021年にリリースされたフルマネージドのコンテナ実行サービスです。コンテナイメージまたはソースコード(Python、Node.js、Java等)を指定するだけで、ロードバランシング・オートスケーリング・TLS終端が自動的に設定されます。
App Runner vs ECS vs EKS:どう使い分ける?
| 比較項目 | App Runner | ECS (Fargate) | EKS |
|---|---|---|---|
| 学習コスト | ★☆☆(低い) | ★★☆(中程度) | ★★★(高い) |
| カスタマイズ性 | ★☆☆(限定的) | ★★☆(中程度) | ★★★(高い) |
| 運用負荷 | ★☆☆(低い) | ★★☆(中程度) | ★★★(高い) |
| コスト効率(小規模) | ★★★(最良) | ★★☆ | ★☆☆ |
| コスト効率(大規模) | ★★☆ | ★★★(最良) | ★★★(最良) |
| 向いている用途 | WebAPI、マイクロサービス | 本番ワークロード | 大規模・マルチテナント |
App Runnerが最適なケース:
- プロトタイプやMVPの素早いデプロイ
- 小〜中規模のWebAPI・マイクロサービス
- インフラ管理を最小限にしたいチーム
- SES案件での開発環境・検証環境の構築
SES案件でのApp Runner需要
App Runnerの登場により、コンテナデプロイの民主化が進んでいます。
- スタートアップ案件: MVPを最短でリリースしたい
- マイクロサービス移行案件: 小さなサービスから段階的にコンテナ化
- 開発環境構築: PR単位の一時環境をApp Runnerで自動構築
- 内部ツール: 管理画面やダッシュボードの社内デプロイ
App Runnerの基本的な使い方
ECRからのデプロイ
まず、コンテナイメージをECRにプッシュし、App Runnerにデプロイする基本フローを見ていきましょう。
Dockerfile例(Node.js Express API):
# マルチステージビルド
FROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
FROM node:22-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# App Runnerはポート8080をデフォルトで使用
EXPOSE 8080
CMD ["node", "dist/server.js"]
ECRへのプッシュとApp Runnerの作成:
# ECRリポジトリ作成
aws ecr create-repository --repository-name my-api
# Dockerイメージのビルドとプッシュ
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=ap-northeast-1
ECR_URL="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"
aws ecr get-login-password --region $REGION | \
docker login --username AWS --password-stdin $ECR_URL
docker build -t my-api .
docker tag my-api:latest ${ECR_URL}/my-api:latest
docker push ${ECR_URL}/my-api:latest
# App Runnerサービス作成
aws apprunner create-service \
--service-name my-api \
--source-configuration '{
"AuthenticationConfiguration": {
"AccessRoleArn": "arn:aws:iam::'$ACCOUNT_ID':role/AppRunnerECRAccessRole"
},
"AutoDeploymentsEnabled": true,
"ImageRepository": {
"ImageIdentifier": "'$ECR_URL'/my-api:latest",
"ImageConfiguration": {
"Port": "8080",
"RuntimeEnvironmentVariables": {
"NODE_ENV": "production",
"DATABASE_URL": "postgresql://..."
}
},
"ImageRepositoryType": "ECR"
}
}' \
--instance-configuration '{
"Cpu": "1024",
"Memory": "2048",
"InstanceRoleArn": "arn:aws:iam::'$ACCOUNT_ID':role/AppRunnerInstanceRole"
}' \
--health-check-configuration '{
"Protocol": "HTTP",
"Path": "/health",
"Interval": 10,
"Timeout": 5,
"HealthyThreshold": 1,
"UnhealthyThreshold": 5
}'
ソースコードからのデプロイ
GitHubリポジトリを直接指定してデプロイすることもできます。
aws apprunner create-service \
--service-name my-api-from-source \
--source-configuration '{
"AuthenticationConfiguration": {
"ConnectionArn": "arn:aws:apprunner:'$REGION':'$ACCOUNT_ID':connection/github/..."
},
"AutoDeploymentsEnabled": true,
"CodeRepository": {
"RepositoryUrl": "https://github.com/your-org/your-repo",
"SourceCodeVersion": {
"Type": "BRANCH",
"Value": "main"
},
"CodeConfiguration": {
"ConfigurationSource": "API",
"CodeConfigurationValues": {
"Runtime": "NODEJS_18",
"BuildCommand": "npm ci && npm run build",
"StartCommand": "npm start",
"Port": "8080"
}
}
}
}'
オートスケーリングの設定
App Runnerのオートスケーリングは、同時リクエスト数に基づいて自動的にインスタンスを増減します。
# オートスケーリング設定の作成
aws apprunner create-auto-scaling-configuration \
--auto-scaling-configuration-name my-api-scaling \
--max-concurrency 100 \
--min-size 1 \
--max-size 10
# サービスに適用
aws apprunner update-service \
--service-arn $SERVICE_ARN \
--auto-scaling-configuration-arn $AUTO_SCALING_ARN
スケーリングの仕組み
| パラメータ | 説明 | デフォルト値 | 推奨設定 |
|---|---|---|---|
| MaxConcurrency | インスタンスあたりの最大同時リクエスト数 | 100 | 50〜200 |
| MinSize | 最小インスタンス数 | 1 | 1(コスト重視)/ 2(可用性重視) |
| MaxSize | 最大インスタンス数 | 25 | ワークロードに応じて設定 |
コールドスタート対策: App Runnerでは、MinSizeを1以上に設定しておくことで、常に少なくとも1つのインスタンスがウォーム状態を維持します。これにより、初回リクエストのレイテンシが大幅に改善されます。
VPC Connectorで内部リソースに接続する
App Runnerはデフォルトではパブリックなネットワークで動作しますが、VPC Connectorを使うことでプライベートサブネット内のRDSやElastiCacheに安全に接続できます。
# VPC Connector作成
aws apprunner create-vpc-connector \
--vpc-connector-name my-vpc-connector \
--subnets subnet-xxx subnet-yyy \
--security-groups sg-zzz
# サービスにVPC Connectorを関連付け
aws apprunner update-service \
--service-arn $SERVICE_ARN \
--network-configuration '{
"EgressConfiguration": {
"EgressType": "VPC",
"VpcConnectorArn": "'$VPC_CONNECTOR_ARN'"
}
}'
RDSとの接続パターン
// src/lib/database.ts
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
// App RunnerのVPC Connector経由でRDSに接続
ssl: process.env.NODE_ENV === 'production'
? { rejectUnauthorized: false }
: undefined,
});
export async function query(text: string, params?: any[]) {
const client = await pool.connect();
try {
const result = await client.query(text, params);
return result;
} finally {
client.release();
}
}
カスタムドメインとHTTPS
App Runnerでは、カスタムドメインの設定も簡単です。
# カスタムドメインの関連付け
aws apprunner associate-custom-domain \
--service-arn $SERVICE_ARN \
--domain-name api.example.com \
--enable-www-subdomain
# DNS設定の確認
aws apprunner describe-custom-domains \
--service-arn $SERVICE_ARN \
--query 'CustomDomains[].{Domain:DomainName,Status:Status,Records:CertificateValidationRecords}'
DNS設定後、ACM証明書が自動的に発行・管理されます。手動での証明書更新は不要です。
CI/CDパイプラインの構築
GitHub Actionsでの自動デプロイ
# .github/workflows/deploy.yml
name: Deploy to App Runner
on:
push:
branches: [main]
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: my-api
SERVICE_NAME: my-api
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
$ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Wait for App Runner deployment
run: |
# AutoDeployment有効の場合、ECRプッシュで自動デプロイ
echo "Waiting for App Runner auto-deployment..."
sleep 30
STATUS=""
for i in $(seq 1 30); do
STATUS=$(aws apprunner describe-service \
--service-arn ${{ secrets.APP_RUNNER_ARN }} \
--query 'Service.Status' --output text)
echo "Status: $STATUS (attempt $i)"
if [ "$STATUS" = "RUNNING" ]; then
echo "Deployment successful!"
break
fi
sleep 10
done
if [ "$STATUS" != "RUNNING" ]; then
echo "Deployment timeout"
exit 1
fi
CDKでのインフラ定義
// lib/app-runner-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as apprunner from '@aws-cdk/aws-apprunner-alpha';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
export class AppRunnerStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, 'Vpc', {
vpcId: 'vpc-xxx',
});
const repository = ecr.Repository.fromRepositoryName(
this, 'Repo', 'my-api'
);
const vpcConnector = new apprunner.VpcConnector(
this, 'VpcConnector', {
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
}
);
const service = new apprunner.Service(this, 'ApiService', {
serviceName: 'my-api',
source: apprunner.Source.fromEcr({
repository,
imageConfiguration: {
port: 8080,
environmentVariables: {
NODE_ENV: 'production',
},
},
tagOrDigest: 'latest',
}),
cpu: apprunner.Cpu.ONE_VCPU,
memory: apprunner.Memory.TWO_GB,
vpcConnector,
autoDeploymentsEnabled: true,
healthCheck: apprunner.HealthCheck.http({
path: '/health',
interval: cdk.Duration.seconds(10),
}),
});
new cdk.CfnOutput(this, 'ServiceUrl', {
value: service.serviceUrl,
});
}
}
Observabilityの設定
CloudWatch連携
App Runnerは自動的にCloudWatch Logsにログを送信します。メトリクスも標準で利用可能です。
# ログの確認
aws logs get-log-events \
--log-group-name "/aws/apprunner/my-api/${SERVICE_ID}/application" \
--log-stream-name "instance/..." \
--limit 50
# メトリクスの確認
aws cloudwatch get-metric-statistics \
--namespace "AWS/AppRunner" \
--metric-name "RequestCount" \
--dimensions Name=ServiceName,Value=my-api \
--start-time $(date -u -v-1H '+%Y-%m-%dT%H:%M:%SZ') \
--end-time $(date -u '+%Y-%m-%dT%H:%M:%SZ') \
--period 300 \
--statistics Sum
X-Rayトレーシング
// src/tracing.ts
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const provider = new NodeTracerProvider();
provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: 'https://xray.ap-northeast-1.amazonaws.com',
})
)
);
provider.register({
propagator: new AWSXRayPropagator(),
});
コスト最適化
App Runnerの料金体系
| リソース | 料金(東京リージョン) |
|---|---|
| vCPU(アクティブ時) | $0.081/vCPU-時間 |
| vCPU(一時停止時) | $0.008/GB-時間 |
| メモリ | $0.009/GB-時間 |
| 自動デプロイ | $1/月/サービス |
月額コスト試算(1 vCPU / 2GB メモリ):
- 常時稼働: 約$65/月
- 日中のみ稼働(12h/日): 約$35/月
- MinSize=0の完全従量制: トラフィックに応じて変動
コスト削減のテクニック
- MinSizeを0に設定: トラフィックがない時間帯のコストをゼロに
- 適切なCPU/メモリ設定: オーバープロビジョニングを避ける
- MaxConcurrencyの最適化: インスタンスあたりの処理効率を最大化
- 自動一時停止: 開発環境は使用時のみアクティブに
まとめ:App Runnerでコンテナデプロイをシンプルにしよう
AWS App Runnerは、コンテナアプリケーションのデプロイを極限まで簡素化するサービスです。ECS/EKSの複雑な設定なしに、本番品質のコンテナ実行環境を数分で構築できます。
SESエンジニアにとって、App Runnerの知識はコンテナ案件への参入障壁を大きく下げます。ECS/EKSの経験がなくても、App Runnerから始めてコンテナ技術のスキルを積み上げていけます。
AWSの基本はAWS入門ガイドを、ECSについてはAWS ECS Fargateガイドをご覧ください。CI/CD全般はAWS CodePipelineガイド、コスト最適化はAWS FinOpsガイドが参考になります。