Cloud Platform Deployment (Python)
Time to read: 15 minutes
Deploy your Python MCP server to NitroCloud or to AWS, Google Cloud, Azure and others.
The Fastest Way: NitroCloud
Why choose NitroCloud?
Skip the setup complexity. NitroCloud is purpose-built for MCP servers and detects NitroStack Python projects automatically:
- One-step deployment: connect your code and it builds and runs the container for you
- Auto-scaling: scales with traffic
- Built-in monitoring: metrics, logs and alerts
- Automatic SSL: free HTTPS certificates
- Environment variables: set secrets in the dashboard, never in your code
- Free tier: no credit card required
What NitroCloud Needs From Your Project
NitroCloud recognizes a Python NitroStack app when nitrostack appears in pyproject.toml (or requirements.txt) or your code imports it. Make sure your project has:
| Requirement | Why |
|---|---|
pyproject.toml at the project root (or one folder deep) | Dependencies are installed with uv. A uv.lock is used when present |
nitrostack listed as a dependency | Lets NitroCloud pick the Python build |
main.py as the entry point | The container runs python main.py |
MCP_TRANSPORT_TYPE=http support | The container sets HOST=0.0.0.0, PORT=3000 and MCP_TRANSPORT_TYPE=http for you |
No Dockerfile is needed. NitroCloud supplies its own.
Quick Start
# 1. Check the project locally
nitrostack-py validate
# 2. Make sure it runs over HTTP
MCP_TRANSPORT_TYPE=http nitrostack-py start
# 3. Push to GitHub
git add . && git commit -m "Ready to deploy" && git push
Then in the NitroCloud dashboard:
- Sign in and choose Create NitroApp
- Connect GitHub and pick your repository and branch
- Select the project folder (the one holding
pyproject.toml) - Add environment variables, for example
API_KEY,JWT_SECRETand any OAuth settings - Click Deploy
Your MCP server is live at its NitroCloud URL. Point your MCP client at https://<your-app-url>/mcp.
nitrostack-pydoes not currently shiploginordeploycommands. Deployment to NitroCloud happens from the dashboard, not the CLI.
Updating Your Server
Push to the connected branch and redeploy from the dashboard. To change configuration, edit the environment variables and redeploy. Never commit .env.
Verify the Deployment
curl https://<your-app-url>/mcp/health
Self-Hosting Options
If you prefer to manage your own infrastructure, here are the popular alternatives. Every example below runs the same container from the Docker guide:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir nitrostack && pip install --no-cache-dir -r requirements.txt
ENV HOST=0.0.0.0 MCP_TRANSPORT_TYPE=http PORT=3000
EXPOSE 3000
CMD ["nitrostack-py", "start", "--port", "3000"]
Common rules for every platform:
- Bind
HOST=0.0.0.0and the platformPORT - Set
MCP_TRANSPORT_TYPE=http(usedualonly if you also need stdio in the same process, which is unusual in the cloud) - Health check:
GET /mcp/health - MCP endpoint:
POST/GET/DELETE /mcp, with no trailing slash - Keep
API_KEY,JWT_SECRETand OAuth settings in the platform secret store
What You'll Learn
- AWS deployment
- Google Cloud deployment
- Azure deployment
- Platform comparison
- Secrets, logging and CI/CD
AWS Deployment
AWS ECS (Elastic Container Service)
1. Create an ECR repository
aws ecr create-repository --repository-name my-mcp-server
2. Build and push
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
docker build -t my-mcp-server .
docker tag my-mcp-server:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/my-mcp-server:latest
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/my-mcp-server:latest
3. Task definition
{
"family": "mcp-server",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "mcp-server",
"image": "<account-id>.dkr.ecr.us-east-1.amazonaws.com/my-mcp-server:latest",
"portMappings": [{ "containerPort": 3000 }],
"environment": [
{ "name": "HOST", "value": "0.0.0.0" },
{ "name": "MCP_TRANSPORT_TYPE", "value": "http" },
{ "name": "PORT", "value": "3000" }
],
"secrets": [
{
"name": "JWT_SECRET",
"valueFrom": "arn:aws:secretsmanager:us-east-1:<account-id>:secret:mcp/jwt-secret"
}
]
}
]
}
4. Create the service
aws ecs create-service \
--cluster mcp-cluster \
--service-name mcp-server \
--task-definition mcp-server \
--desired-count 2 \
--launch-type FARGATE
Point the load balancer health check at /mcp/health.
AWS Lambda (Serverless)
Lambda works for stateless HTTP use, but MCP sessions are stateful, so prefer ECS or Cloud Run for long-lived sessions. If you still want Lambda:
nitrostack-py pack
pip install dist/*.whl -t package/
Use a container image deployment with the Lambda Web Adapter so the same nitrostack-py start process serves requests:
FROM public.ecr.aws/docker/library/python:3.12-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir nitrostack -r requirements.txt
ENV HOST=0.0.0.0 PORT=8080 MCP_TRANSPORT_TYPE=http
CMD ["nitrostack-py", "start", "--port", "8080"]
Google Cloud Platform
Cloud Run
# Enable APIs
gcloud services enable run.googleapis.com cloudbuild.googleapis.com
# Build with Cloud Build
gcloud builds submit --tag gcr.io/PROJECT_ID/my-mcp-server
# Deploy
gcloud run deploy my-mcp-server \
--image gcr.io/PROJECT_ID/my-mcp-server \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars HOST=0.0.0.0,MCP_TRANSPORT_TYPE=http \
--set-secrets JWT_SECRET=jwt-secret:latest
Cloud Run injects PORT, and nitrostack-py start reads it from the environment. Remove --allow-unauthenticated and use Cloud Run IAM if the server is private.
GKE (Kubernetes)
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
replicas: 2
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
containers:
- name: mcp-server
image: gcr.io/PROJECT_ID/my-mcp-server:latest
ports:
- containerPort: 3000
env:
- name: HOST
value: "0.0.0.0"
- name: MCP_TRANSPORT_TYPE
value: "http"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: mcp-secrets
key: jwt-secret
readinessProbe:
httpGet:
path: /mcp/health
port: 3000
---
apiVersion: v1
kind: Service
metadata:
name: mcp-server
spec:
type: LoadBalancer
selector:
app: mcp-server
ports:
- port: 80
targetPort: 3000
kubectl apply -f deployment.yaml
Microsoft Azure
Azure Container Instances
# Create resource group
az group create --name mcp-rg --location eastus
# Create container
az container create \
--resource-group mcp-rg \
--name my-mcp-server \
--image <registry>/my-mcp-server:latest \
--ports 3000 \
--environment-variables HOST=0.0.0.0 MCP_TRANSPORT_TYPE=http PORT=3000 \
--secure-environment-variables JWT_SECRET=$JWT_SECRET
Azure Kubernetes Service (AKS)
# Create AKS cluster
az aks create --resource-group mcp-rg --name mcp-aks --node-count 2
# Get credentials
az aks get-credentials --resource-group mcp-rg --name mcp-aks
# Deploy (reuse the manifest from the GKE section, with your registry image)
kubectl apply -f deployment.yaml
DigitalOcean
App Platform
# .do/app.yaml
name: my-mcp-server
services:
- name: web
github:
repo: your-org/your-repo
branch: main
dockerfile_path: Dockerfile
http_port: 3000
envs:
- key: HOST
value: 0.0.0.0
- key: MCP_TRANSPORT_TYPE
value: http
- key: JWT_SECRET
value: ${JWT_SECRET}
type: SECRET
health_check:
http_path: /mcp/health
Heroku
# Create app
heroku create my-mcp-server
heroku stack:set container
# heroku.yml
# build:
# docker:
# web: Dockerfile
# Config
heroku config:set MCP_TRANSPORT_TYPE=http HOST=0.0.0.0 JWT_SECRET=...
# Deploy
git push heroku main
# Scale
heroku ps:scale web=1
Heroku sets PORT for you.
Platform Comparison
| Platform | Best For | Cost | Scaling |
|---|---|---|---|
| NitroCloud | MCP servers, fastest setup | $ | Automatic |
| AWS ECS | Enterprise | $$$ | Excellent |
| AWS Lambda | Stateless, event-driven | $ | Automatic |
| Google Cloud Run | Serverless containers | $$ | Automatic |
| GKE/AKS | Large scale | $$$ | Manual |
| DigitalOcean | Simplicity | $$ | Manual |
| Heroku | Quick deploy | $$ | Easy |
Environment Configuration
Secrets Management
AWS Secrets Manager:
import json
import boto3
def get_secret(secret_name: str) -> dict:
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response["SecretString"])
Google Cloud Secret Manager:
from google.cloud import secretmanager
def get_secret(project_id: str, name: str) -> str:
client = secretmanager.SecretManagerServiceClient()
path = f"projects/{project_id}/secrets/{name}/versions/latest"
return client.access_secret_version(name=path).payload.data.decode("utf-8")
Prefer injecting secrets as environment variables through the platform (ECS secrets, Cloud Run --set-secrets, Kubernetes secretKeyRef) so your code only reads os.environ.
Monitoring & Logging
CloudWatch (AWS)
On ECS, use the awslogs log driver and your container output goes to CloudWatch with no code changes:
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/aws/ecs/mcp-server",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "production"
}
}
Cloud Logging (GCP)
Cloud Run collects container stdout and stderr automatically. For structured logs:
import json
import sys
def log_entry(message: str, severity: str = "INFO") -> None:
print(json.dumps({"severity": severity, "message": message}), file=sys.stderr)
Best Practices
- Use managed services: less operational overhead
- Implement health checks: point them at
/mcp/health - Auto-scaling: handle traffic spikes, and set
MCP_MAX_SESSIONSper instance - Multi-region: high availability
- Secrets management: never hardcode credentials or ship
.env - Monitoring & alerts: know when things break
- CI/CD pipelines: automate deployments
- Cost optimization: right-size CPU and memory
CI/CD Examples
GitHub Actions (AWS)
name: Deploy to AWS ECS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Test and validate
run: |
pip install nitrostack -r requirements.txt pytest
pytest
nitrostack-py validate
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
run: |
docker build -t mcp-server .
docker tag mcp-server:latest ${{ steps.ecr.outputs.registry }}/mcp-server:latest
docker push ${{ steps.ecr.outputs.registry }}/mcp-server:latest
- name: Update ECS service
run: |
aws ecs update-service \
--cluster mcp-cluster \
--service mcp-server \
--force-new-deployment
If you deploy with NitroCloud, the CI job only needs to run tests and nitrostack-py validate. Pushing to the connected branch handles the rest.
Next Steps
Master deployment:
- Docker Guide - Container basics
- Production Checklist - Pre-flight checks
- Best Practices - Production best practices