App Runner availability notice
AWS App Runner stopped accepting new customers on April 30, 2026. Existing customers can continue to use the service and create new services, but AWS is not adding features and recommends Amazon ECS Express Mode for new containerized workloads. This guide is for existing App Runner customers who still need to deploy or update an MCP server. New deployments should use the ECS Express Mode migration path described below.
App Runner takes a container image from Amazon ECR, runs it behind an HTTPS endpoint, and scales instances with request concurrency. For an MCP server, that means one public URL with the MCP endpoint at /mcp and your upstream API credentials injected at runtime.
This guide uses the image-based path because it works for TypeScript, Python, Java, and PHP without depending on App Runner's managed runtimes, several of which have already reached end of support.
Prerequisites
- A generated bundle for your chosen runtime.
- Docker installed and running locally.
- AWS CLI v2 configured with credentials for the target account and region.
- Permission to create ECR repositories, IAM roles, and App Runner services.
aws sts get-caller-identity
docker version
1. Bind the server to App Runner's port
App Runner routes traffic to the port in the service configuration, which is 8080 unless you change it. The generated servers default to localhost, so each runtime needs to listen on 0.0.0.0 and use that port. Reading PORT when it is present keeps the same image usable on other container platforms.
For TypeScript, update src/server.ts and make npm start use the HTTP transport.
// Before
port: { type: 'string', default: '3000' },
serve({ fetch: app.fetch, port, hostname: '127.0.0.1' }, () => {
// After
port: { type: 'string', default: process.env.PORT ?? '8080' },
serve({ fetch: app.fetch, port, hostname: '0.0.0.0' }, () => {
"scripts": {
"build": "tsc",
"start": "node dist/server.js --http",
"dev": "tsx watch src/server.ts"
}
Python already accepts --host and --port, so no source changes are needed. Java and PHP can use the Dockerfiles from the Cloud Run guide; they already bind 0.0.0.0 and read $PORT.
2. Build a container image
TypeScript and Python can start from the same bundle root. The TypeScript image builds the project, then installs production dependencies in the runtime stage.
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm install --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 8080
CMD ["node", "dist/server.js", "--http"]
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["sh", "-c", "python server.py --transport streamable-http --host 0.0.0.0 --port ${PORT:-8080}"]
Build for linux/amd64. App Runner runs x86_64 compute, and an Apple Silicon machine produces ARM images by default.
.git
node_modules
dist
venv
__pycache__
docker build --platform linux/amd64 -t my-mcp-server .
3. Push the image to Amazon ECR
Create a private repository, authenticate Docker to ECR, tag the image, and push it.
export AWS_REGION=us-east-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export ECR_REGISTRY=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
aws ecr create-repository \
--repository-name my-mcp-server \
--region $AWS_REGION
aws ecr get-login-password --region $AWS_REGION \
| docker login --username AWS --password-stdin $ECR_REGISTRY
docker tag my-mcp-server:latest $ECR_REGISTRY/my-mcp-server:latest
docker push $ECR_REGISTRY/my-mcp-server:latest
4. Create the IAM roles
App Runner needs one role to pull the private image and, if you use Secrets Manager, an instance role to read the secret at runtime.
aws iam create-role \
--role-name AppRunnerECRAccessRole \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"build.apprunner.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy \
--role-name AppRunnerECRAccessRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess
aws iam create-role \
--role-name AppRunnerInstanceRole \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"tasks.apprunner.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
Attach a narrowly scoped policy to the instance role that allows secretsmanager:GetSecretValue on the secret used by this service. Store the upstream token in Secrets Manager before creating the service.
Least privilege: do not attach a broad Secrets Manager policy. Scope the resource to the exact secret ARN, and add kms:Decrypt only if the secret uses a customer-managed KMS key.
5. Create the App Runner service
Define the service in a JSON file so the configuration is reviewable and repeatable. Replace the account ID and secret ARN placeholders before running the command.
{
"ServiceName": "my-mcp-server",
"SourceConfiguration": {
"AutoDeploymentsEnabled": true,
"AuthenticationConfiguration": {
"AccessRoleArn": "arn:aws:iam::ACCOUNT_ID:role/AppRunnerECRAccessRole"
},
"ImageRepository": {
"ImageIdentifier": "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/my-mcp-server:latest",
"ImageRepositoryType": "ECR",
"ImageConfiguration": {
"Port": "8080",
"RuntimeEnvironmentSecrets": {
"MCP_AUTH_API_TOKEN": "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:mcp-api-token-AbCdEf"
}
}
}
},
"InstanceConfiguration": {
"Cpu": "1 vCPU",
"Memory": "2 GB",
"InstanceRoleArn": "arn:aws:iam::ACCOUNT_ID:role/AppRunnerInstanceRole"
},
"HealthCheckConfiguration": {
"Protocol": "TCP",
"Interval": 10,
"Timeout": 5,
"HealthyThreshold": 1,
"UnhealthyThreshold": 5
}
}
aws apprunner create-service \
--cli-input-json file://service.json \
--region $AWS_REGION
The variable name must match the generated bundle README, for example MCP_AUTH_API_TOKEN. If your API has several schemes, add one entry per variable. A TCP health check is used because /mcp does not answer a plain GET / with a normal health page.
You can create the same service in the console: choose Container registry, select the ECR image, choose the ECR access role, set the port to 8080, add the environment secret, and set the health check protocol to TCP.
6. Verify the service
Wait until the service status is RUNNING, then read the default domain from the service description.
aws apprunner list-services --region $AWS_REGION
aws apprunner describe-service \
--service-arn YOUR_SERVICE_ARN \
--region $AWS_REGION \
--query 'Service.ServiceUrl' \
--output text
The MCP endpoint is the returned URL plus /mcp:
https://abc123.us-east-1.awsapprunner.com/mcp
Connect an MCP client that supports Streamable HTTP and call a read-only tool. App Runner's Logs tab shows container startup errors, missing secrets, and request failures.
Optional: deploy from source
Existing customers can also connect a GitHub or Bitbucket repository and let App Runner build it. For TypeScript on Node.js 22, add apprunner.yaml at the bundle root after applying the port and host changes above.
version: 1.0
runtime: nodejs22
build:
commands:
build:
- npm install
- npm run build
run:
command: npm start
network:
port: 8080
secrets:
- name: MCP_AUTH_API_TOKEN
value-from: "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:mcp-api-token-AbCdEf"
Source-based App Runner services need an instance role with the same secret permissions. Python uses the python311 managed runtime, but verify the current supported runtime list before relying on it. Container images avoid managed runtime end-of-support churn entirely.
Operational notes
- No scale to zero. App Runner keeps at least one instance provisioned, so you pay for memory even when idle. Pause the service when it does not need to be available, and check current App Runner pricing.
- Tune concurrency. Auto scaling is driven by concurrent requests per instance. Long-lived MCP streams occupy concurrency, so lower the max concurrency and cap the maximum instance count to keep costs predictable.
- Public by default. The default *.awsapprunner.com URL is publicly reachable. Upstream credentials are protected, but the MCP endpoint itself is not authenticated. Add an authorizer or put it behind an identity-aware proxy before sharing it.
- Secrets need a role. Environment secrets are resolved through the instance role. If the service starts but tools fail with missing credentials, check the role's GetSecretValue permission and KMS access first.
- Plan the migration. App Runner continues to run existing services, but new workloads belong on ECS Express Mode. ECS Express Mode takes a container image plus two IAM roles and provisions an ECS service, load balancer, and autoscaling. The image you build here can be reused, so keep the Dockerfile and ECR repository.
Next steps
If you are choosing a platform rather than maintaining an existing App Runner service, compare this setup with Google Cloud Run, Render, Vercel, and Cloudflare Workers. For AWS, start new containerized deployments on ECS Express Mode and use this guide only for services that already live on App Runner. Compare all seven options in Where to Deploy Your MCP Server.
Need a bundle to containerize? Start with From OpenAPI to a Running MCP Server.