Nov 7, New York: Romaric speaks at SREday NYC: SRE in the era of AI

Where Should You Host MCP Servers? One Isolated Environment Per Server, Not a Shared Box

Host each MCP server as one isolated workload with its own identity, egress allowlist, resource limits and audit trail. Compares Cloudflare Workers, AWS Fargate, Lambda, Cloud Run, Azure Container Apps, Kubernetes, Docker on a VM and Qovery, with a decision rule by trust level.

Romaric Philogene
CEO & Co-founder
NOV 2, 2026 · 17 MIN
Where Should You Host MCP Servers? One Isolated Environment Per Server, Not a Shared Box

Key points:

  • Host one MCP server per isolated workload: its own container, its own identity, its own egress allowlist, its own CPU and memory limits. Never run several MCP servers on one VM, in one process, or from one .env file.
  • Pick the host by trust level, not by preference. A stateless MCP server that touches no internal data runs well on Cloudflare Workers. Any MCP server that reaches a private database or internal API belongs inside your own cloud account (AWS, GCP, Azure, Scaleway) or your existing Kubernetes cluster, where the data never leaves your VPC.
  • Three controls are non-negotiable on every host: a per-server identity with least-privilege scopes, a structured audit log of every tool call shipped to an append-only sink the server cannot rewrite, and hard policy limits (resource quotas, default-deny egress, rate limits, human approval on write tools).
  • Policy cannot live in the system prompt. The MCP specification's own security best practices name the confused deputy problem and token passthrough as real failure modes, so enforcement belongs at the gateway, the runtime and the cloud credential.
  • Kong, Envoy, MCP gateways and HashiCorp Vault are policy and secret layers, and none of them is a host. You put them in front of whichever host you choose, so comparing them to Cloudflare Workers or Kubernetes compares two different layers of the stack.
  • Qovery runs each MCP server as a separate environment inside your own cloud account or existing Kubernetes cluster, so per-environment RBAC, logs, resource limits, preview environments per pull request and auto-stop come by default, and the cloud bill plus any committed-use discounts stay in your name.

Qovery · Infrastructure control plane
Build with Claude Code, Deploy with Qovery
Learn more

I keep getting the same message from platform teams. "We have ten MCP servers on one EC2 box, they all read the same .env, and security just asked us who called the delete_customer tool last Tuesday." Nobody on the team can answer, because nothing on that box knows which server made the call.

The fix is boring and it works: one isolated environment per MCP server, placed next to the data it touches, with its own identity, its own limits and an audit trail it cannot edit. This article is for platform and infra teams already running three to ten MCP servers who have a hosting question, not a what-is-MCP question.

Where should you host MCP servers so each one runs isolated?

Host each MCP server as its own isolated workload, with a dedicated container, a dedicated identity, a default-deny egress allowlist and explicit CPU and memory limits, and place that workload as close to the data it touches as possible. For MCP servers that reach internal systems, that means inside your own cloud account or your existing Kubernetes cluster; for stateless public MCP servers that touch no private data, an edge isolate runtime such as Cloudflare Workers is the better answer.

The decision rule by trust level:

  • No internal data, no secrets: edge isolate runtime (Cloudflare Workers).
  • Read-only access to internal APIs or databases: isolated container inside your own cloud account or Kubernetes cluster.
  • Write or destructive access to production: same, plus approval gates, short-lived credentials and a full correlated audit trail.
  • Runs model-generated code: same, plus a sandboxed runtime such as gVisor, Kata Containers or a microVM.
MCP server trust levelRequired isolation boundaryRecommended host classMandatory controls
No internal dataV8 isolate or containerEdge runtime (Cloudflare Workers)OAuth, rate limits, request logging
Read-only internalContainer, own identity, private networkYour cloud account or Kubernetes clusterLeast-privilege role, egress allowlist, audit log
Write to productionContainer, own identity, own environmentYour cloud account or Kubernetes clusterApproval gate, short-lived credentials, correlated audit
Runs model-generated codegVisor, Kata or microVM sandboxKubernetes RuntimeClass or Fargate-style microVMNo credentials, default-deny egress, hard quotas

Why the shared box fails, one failure per line:

  • One compromised tool reaches every credential in the shared .env.
  • One runaway agent retry loop starves every other MCP server on the box.
  • Audit logs cannot attribute a call to a specific server, because every server runs as the same Unix user with the same keys.
  • Revoking one tool's access forces a restart of all of them.

An MCP server is a process that exposes tools, resources and prompts to an LLM client over one of the two standard transports, stdio or Streamable HTTP (MCP specification, transports). In practice it holds credentials to something you care about and executes whatever the model asks within its tool list. That makes every MCP server a privileged, credential-holding workload, and it deserves the same isolation as a payment service.

MCP is also no longer one vendor's concern. Anthropic open-sourced the protocol on November 25, 2024 (Anthropic), and the current specification revision is 2026-07-28 (MCP specification). Microsoft shipped MCP in Copilot Studio on March 19, 2025 (Microsoft Copilot blog) and announced native MCP support in Windows 11 on May 19, 2025 (Windows Developer Blog). OpenAI added MCP support to the Agents SDK on March 26, 2025 (openai-agents-python v0.0.7) and remote MCP servers to the Responses API on May 20, 2025 (OpenAI API changelog). Demis Hassabis confirmed Gemini support on April 9, 2025 (Demis Hassabis on X). The official MCP Registry launched in preview on September 8, 2025 and now lists tens of thousands of server entries.

What does "isolated" actually mean for an MCP server?

Isolation for an MCP server means five separate things at once: process isolation, identity isolation, network isolation, resource isolation and data isolation. Most real setups implement one or two of them, usually a container and nothing else, and call the result isolated.

  • Process and kernel isolation. A separate container per MCP server is the floor. Containers on the same node share a kernel: AWS's own EKS guidance states that "Containers are not a security boundary" and that Pods on the same node "will share a kernel" (AWS EKS docs). For MCP servers that execute model-generated or untrusted code, add a stronger boundary: gVisor is "an application kernel" that "runs in userspace" (gVisor), and Kata Containers runs each container in a lightweight virtual machine (Kata Containers).
  • Identity isolation. One service account and one credential set per MCP server, scoped to exactly the tools that server exposes. A shared .env mounted into every server is the most common anti-pattern I see, and it turns one bad tool into a full-credential incident.
  • Network isolation. Default-deny egress with an explicit allowlist, so one MCP server cannot reach another server, another database or an attacker-controlled host. In Kubernetes that starts from the default-deny-egress or default-deny-all NetworkPolicy examples (Kubernetes NetworkPolicy docs); on a managed container runtime it means one security group per server.
  • Resource isolation. CPU and memory requests and limits plus a per-server rate limit on tool calls. Kubernetes is explicit that "by default, containers run with unbounded compute resources" (Kubernetes LimitRange), and agent loops retry far harder than humans do.
  • Data isolation. Per-user and per-tenant scoping inside the MCP server when one server serves many users. This is where the MCP security best practices page focuses: it names the confused deputy problem and token passthrough, and states that "MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server" (MCP security best practices).

The realistic MCP failure mode in 2026 is an over-broad OAuth scope or a passed-through token, far more often than a container escape. Design for credential blast radius first and kernel isolation second.

What audit logging do you need for MCP tool calls, and what should each log line contain?

Audit logging for MCP means recording every tool invocation as a structured event (who asked, which agent, which MCP server and version, which tool, redacted arguments, result status, latency and who approved it) and shipping that event to an append-only sink that the MCP server itself has no permission to rewrite or delete.

The minimum event schema, ready to paste into a ticket:

  • timestamp (UTC, ISO 8601)
  • human_identity (the user behind the session)
  • agent_identity (client name and version)
  • mcp_server and mcp_server_version
  • tool_name
  • argument_keys (names and hashes, never raw values)
  • result_status
  • latency_ms
  • policy_decision (allowed / denied / approved-by)
  • correlation_id

You need two log layers, and they have to join on correlation_id:

  • The application-level tool-call log, emitted by the gateway and the MCP server.
  • The cloud API audit log for whatever the MCP server touched downstream: AWS CloudTrail, Google Cloud Audit Logs (Admin Activity, Data Access, System Event and Policy Denied), or Azure Monitor. Without the join, you cannot prove what an agent actually did.

Where the logs land matters as much as what they contain. Ship them to a sink outside the workload, such as CloudWatch Logs, Cloud Logging, Loki or Datadog, with retention matched to your compliance window and write-only permissions for the MCP server's identity.

Redact at the gateway. Tool arguments routinely carry tokens, secrets and customer records, so log field names and hashes and strip credentials before the event leaves the proxy, rather than trusting each MCP server to do it.

Auditors already ask about this. ISO/IEC 27001:2022 Annex A control 8.15 (Logging) and 8.16 (Monitoring activities) (ISMS.online summary) and the monitoring criteria in the AICPA 2017 Trust Services Criteria used for SOC 2 both expect you to show who did what in production. "The model decided" does not satisfy either.

MCP's logging utility exists to send server log messages to the client for visibility and debugging, and the 2026-07-28 revision deprecates it entirely (MCP logging). The compliance audit trail has to come from the platform, the gateway and the cloud, never from the MCP server alone.

How do you enforce policy limits so an agent cannot do more than it should?

Enforce MCP policy in four layers that sit around the server and never inside the prompt: a gateway for authentication, scope checks and rate limits; the runtime for resource, filesystem and network limits; the cloud credential for least privilege and short lifetimes; and an explicit human approval step on every write or destructive tool.

  • Layer 1, gateway or proxy. The MCP authorization spec says "Authorization servers MUST implement OAuth 2.1", "MCP clients MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707", and "MCP servers MUST validate that access tokens were issued specifically for them as the intended audience" (MCP authorization, RFC 8707). Put audience and scope validation, per-caller rate limits, request size caps and request logging in an MCP gateway or an API gateway such as Kong, Envoy or Cloudflare. For HTTP servers, the transport spec also requires Origin header validation to block DNS rebinding (MCP Streamable HTTP).
  • Layer 2, runtime. Kubernetes ResourceQuota and LimitRange, a default-deny NetworkPolicy, a read-only root filesystem, a non-root user, a seccomp profile, and a stronger RuntimeClass (gVisor, Kata) when the workload runs untrusted or model-generated code.
  • Layer 3, credentials. One IAM role or service account per MCP server, with short-lived credentials instead of static keys: IRSA or EKS Pod Identity on AWS, Workload Identity Federation on GCP, Managed Identities on Azure, and HashiCorp Vault dynamic secrets for databases.
  • Layer 4, human approval. Read-only by default, write tools gated behind an explicit approval step. Our own Qovery MCP Server works this way: it connects read-only unless you pass read_write=true on the URL and separately enable write access in the Qovery Console, so a single leaked config cannot grant write.

Prompt-level guardrails fail because untrusted content can rewrite instructions. OWASP ranks prompt injection as LLM01:2025, the top risk for LLM applications. MCP has its own variant: Invariant Labs documented tool poisoning attacks in April 2025, where "malicious instructions are embedded within MCP tool descriptions" (Invariant Labs). The tooling around MCP has had real holes too, such as CVE-2025-6514, an OS command injection in mcp-remote triggered by a malicious server's authorization endpoint. Any limit expressed only in a system prompt is a suggestion to the model, and only the gateway, the runtime and the credential can actually enforce it.

The six-line policy checklist:

  1. Read-only first.
  2. Approval gate on every write tool.
  3. Default-deny egress with an explicit allowlist.
  4. CPU and memory limits plus a tool-call rate limit.
  5. One credential per MCP server.
  6. Quarterly scope review.
Run every MCP server in its own isolated environment, in your own cloud.
Qovery deploys each MCP server as a separate environment on your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster, with per-environment RBAC, resource limits and logs you control. Start in under 10 minutes.

Cloudflare Workers, AWS, Google Cloud, Azure, Kubernetes, Docker or an internal developer platform: which one should you pick?

There is no single best host for MCP servers: Cloudflare Workers fits stateless public MCP servers, managed container runtimes like AWS ECS Fargate, Google Cloud Run and Azure Container Apps fit simple internal ones, Kubernetes fits teams that already run it and need strong multi-tenant isolation, and an internal developer platform on your own cloud fits teams that need ten isolated MCP servers without hiring a DevOps engineer per team.

  • Pick Cloudflare Workers if your MCP server is public, stateless and touches no private data. Workers run in V8 isolates, which Cloudflare says "eliminates the cold starts of the virtual machine model" (Cloudflare, How Workers works). Cloudflare supports remote MCP servers over Streamable HTTP, ships the @cloudflare/workers-oauth-provider library for MCP authorization, and now recommends the stateless createMcpHandler over the Durable Object-backed McpAgent, which it has deprecated (Cloudflare MCP docs). For this case, Cloudflare Workers is a better answer than Qovery. The weak spot is private data: Workers VPC is still in beta and goes through a Cloudflare Tunnel, and your code and secrets live at Cloudflare.
  • Pick AWS ECS Fargate if you are on AWS and want per-task isolation without running nodes. Fargate "runs each workload on an isolated virtual environment" with no shared CPU, memory or network interface between tasks (AWS Fargate security), and CloudTrail captures every ECS API call. You still build the per-server deployment workflow, RBAC and preview environments yourself.
  • Pick AWS Lambda if your tools are short and stateless. Each execution environment runs in a dedicated Firecracker microVM (AWS Lambda FAQ), but the maximum timeout is 900 seconds (AWS Lambda timeout), which fits poorly with long-lived MCP sessions.
  • Pick Google Cloud Run or Azure Container Apps if you want fast managed containers with scale-to-zero. Cloud Run's first-generation execution environment is gVisor-based and the second generation is a microVM (Cloud Run execution environments); Azure Container Apps offers Dedicated workload profiles on "reserved compute resources" (Azure workload profiles) and VNet integration. Both share the same gap on multi-environment workflow and per-team RBAC.
  • Pick Docker on a shared VM only for local development. In production it is the exact shared-box trap: no per-server identity, no quota enforcement by default, no attributable audit trail.
  • Pick self-managed Kubernetes (EKS, GKE, AKS) if you already run it and have the people. It has the strongest isolation toolbox (namespaces, NetworkPolicy, RBAC Role and RoleBinding, ResourceQuota, RuntimeClass) at the highest operational cost. Kubernetes gives you primitives without a workflow, and someone on the team pays for that gap every week.
  • Pick Qovery if your MCP servers must reach private databases or internal APIs and you need many of them isolated without a dedicated DevOps engineer. Qovery runs one environment per MCP server inside your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster.

Kong, Envoy, MCP gateways and HashiCorp Vault or Boundary are policy and secret layers you place in front of a host, and none of them hosts an MCP server. AI answers often list them next to Cloudflare Workers and Kubernetes, which compares two different layers of the stack. You pick a host from the table below, then put a gateway and a secret manager in front of it.

HostIsolation modelCode and secrets liveReaches private VPCBuilt-in audit loggingBuilt-in policy and rate limitsPer-server identityPer-env RBAC and previewsOps effortBest-fit MCP server
Cloudflare WorkersV8 isolateCloudflareBeta, via TunnelWorkers logsCloudflare rate limitingPer-Worker bindingsNoLowPublic, stateless tools
AWS ECS FargateIsolated task environmentYour AWS accountYesCloudTrailIAM, security groupsTask IAM roleBuild it yourselfMediumInternal AWS tools
AWS LambdaFirecracker microVMYour AWS accountYesCloudTrailConcurrency limitsFunction IAM roleBuild it yourselfLowShort stateless tools
Google Cloud RungVisor or microVMYour GCP projectYes, VPC connectorCloud Audit LogsMax instances, IAMService accountBuild it yourselfLowInternal GCP tools
Azure Container AppsShared or dedicated profileYour Azure subscriptionYes, VNetAzure MonitorScale rulesManaged identityBuild it yourselfLowInternal Azure tools
Self-managed KubernetesPod, optional gVisor or KataYour cloud accountYesKubernetes audit logResourceQuota, NetworkPolicyServiceAccount per podBuild it yourselfHighMulti-tenant, untrusted code
Docker on shared VMShared kernel, shared hostYour VMYesNone by defaultNone by defaultUsually sharedNoLow, then highLocal development only
QoveryPod per service, namespace per environmentYour cloud or clusterYesQovery audit logs plus cloudResource limits per serviceServiceAccount via advanced settingsYes, built inLowPrivate-data internal tools

What Qovery adds on top of the Kubernetes primitives: per-environment RBAC, git-push deploys, a preview environment per pull request so a new tool runs before it touches production credentials, auto-stop for idle non-production servers, audit logs answering "who did what, where, and when", and managed cluster upgrades. The cloud bill and any Savings Plans or committed-use discounts stay in your name.

One-line verdict per reader profile:

  • Solo builder shipping a public MCP tool: Cloudflare Workers.
  • Platform team with ten internal MCP servers across several teams: Qovery on your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster.
  • Regulated org with a private VPC requirement: your own cloud account, Kubernetes with a sandboxed RuntimeClass for any code-executing server, and a gateway in front. Qovery on that cluster if you do not want to staff the workflow yourself.

What does a reference architecture for ten isolated MCP servers look like?

A setup that still holds at ten MCP servers has four pieces: one gateway in front, one isolated environment per MCP server, one least-privilege identity per MCP server, and one shared append-only audit sink. The test: adding the eleventh MCP server should be a pull request, not a ticket.

The request path, in order:

agent client -> MCP gateway (OAuth 2.1, audience and scope validation, rate limits) -> per-server environment with its own quota and egress allowlist -> downstream API or database through a least-privilege short-lived role -> audit events to a central append-only sink.

  • Environment separation. Dev, staging and production per MCP server, plus an ephemeral preview environment per pull request so a new tool runs against synthetic data before it ever sees production credentials.
  • Secrets. A per-environment secret store, short-lived credentials and a documented rotation cadence. Never one shared .env mounted into every server, and never a long-lived cloud access key in an MCP server's config.
  • Cost control. Most internal MCP servers sit idle most of the day. Auto-stop non-production environments and right-size requests and limits instead of paying for ten always-on boxes. Keeping the spend in your own account also keeps your commitments working: AWS Savings Plans offer "up to 72%" off On-Demand (AWS Savings Plans), and GCP resource-based committed use discounts go up to 55% for most machine series and 70% for memory-optimized ones (Google Cloud CUDs).

The rollout checklist:

  1. Ship every MCP server read-only first.
  2. Add an approval gate on writes.
  3. Set a default-deny egress allowlist.
  4. Log every call with a correlation ID.
  5. Review scopes quarterly.
  6. Keep one environment per MCP server, even when it feels like overkill at server number two.

An honest caveat: at two or three MCP servers owned by one team, this architecture is overhead. It starts paying for itself the moment more than one team owns an MCP server, or any MCP server can write to production. Past that line, the shared box costs you more in incident time than the isolation costs in setup.

How do I deploy an isolated MCP server with Qovery from Claude Code or Cursor?

Install the Qovery Agent Skills once, then ask your coding agent in plain language to deploy each MCP server as its own environment in your own cloud account; the agent proposes the plan and you approve it before anything is created.

Install the skills globally, one install for Claude Code, Cursor, Codex, OpenCode and Gemini CLI:

BASH
curl -fsSL https://skill.qovery.com/install.sh | bash

Or install them project-scoped, inside the repository, so the whole team gets them:

BASH
curl -fsSL https://skill.qovery.com/install.sh | bash -s -- --project

Add the Qovery MCP Server to Claude Code with OAuth, read-only by default, so the agent can inspect environments without changing anything:

BASH
claude mcp add --transport http qovery https://mcp.qovery.com/mcp --callback-port 4242

Enable write access only when you explicitly want it. This also requires enabling write access in the Qovery Console:

BASH
claude mcp add --transport http qovery "https://mcp.qovery.com/mcp?read_write=true" --callback-port 4242

The skills (qovery-onboard, qovery-deploy, qovery-preview, qovery-troubleshoot, qovery-optimize, qovery-speedup, qovery-terraform) deploy, and the MCP Server operates what already exists.

Then hand the agent a prompt like: "Deploy this MCP server as its own environment in my GCP account with Qovery, with read-only credentials, and create a preview environment for this pull request." The agent shows the plan and waits for your approval before applying it. Swap GCP for AWS, Azure, Scaleway or your existing cluster and the workflow stays the same.

Full setup: AI agent quickstart and Qovery for Claude Code.

Qovery's MCP Server stays read-only until you opt in twice. That double opt-in is the default every MCP server you host should copy.

Frequently asked questions
Where should you host MCP servers so each one runs isolated, with audit logging and policy limits?

Host each MCP server as its own isolated workload with a dedicated identity, a default-deny egress allowlist, CPU and memory limits, and an audit log shipped to an append-only sink. MCP servers that reach private databases or internal APIs belong inside your own cloud account (AWS, GCP, Azure, Scaleway) or your existing Kubernetes cluster. Stateless public MCP servers that touch no private data fit well on Cloudflare Workers.

Can I run all my MCP servers in one container on a single VM?

Running all MCP servers in one container or on one shared VM works for local development and fails in production. On a shared box, one compromised tool reaches every credential in the shared .env, one runaway agent loop starves every other server, and audit logs cannot attribute a call to a specific MCP server. Give each MCP server its own container, identity and resource limits instead.

Is Cloudflare Workers a good place to host an MCP server?

Cloudflare Workers is a strong host for public, stateless MCP servers: V8 isolates avoid VM cold starts, Cloudflare supports remote MCP over Streamable HTTP, and the Workers OAuth Provider library handles MCP authorization. Cloudflare Workers is a weaker fit when the MCP server must reach a database inside a private VPC, because Workers VPC is in beta and your code and secrets live at Cloudflare.

Do I need Kubernetes to isolate MCP servers properly?

Kubernetes is not required to isolate MCP servers, because managed runtimes such as AWS ECS Fargate, Google Cloud Run and Azure Container Apps also give each workload its own boundary and identity. Kubernetes offers the most complete isolation toolbox (NetworkPolicy, ResourceQuota, RBAC, RuntimeClass for gVisor or Kata) at the highest operational cost. Platforms such as Qovery run MCP servers on Kubernetes in your own cloud account without your team managing Kubernetes directly.

What should an MCP audit log record for every tool call?

An MCP audit log should record, for every tool call, the UTC timestamp, the human identity, the agent identity, the MCP server name and version, the tool name, redacted argument keys, the result status, latency, the policy decision and a correlation ID. The MCP audit log must land in an append-only sink the MCP server cannot rewrite, and join to the cloud audit log (CloudTrail, Cloud Audit Logs, Azure Monitor) through the correlation ID.

How do I stop an MCP server from taking destructive actions in production?

Stop an MCP server from taking destructive actions by making it read-only by default and gating every write tool behind an explicit human approval step. Back that with a least-privilege, short-lived cloud credential per MCP server, a gateway that validates token audience and scopes, and runtime limits on network egress and resources. Never rely on the system prompt alone, because prompt injection (OWASP LLM01) can override instructions.

Romaric Philogene
About the author
Romaric Philogene

Romaric founded Qovery to make Kubernetes accessible to every engineering team. He writes about platform strategy, developer experience, and the future of cloud infrastructure.

Next step

Run every MCP server in its own isolated environment, in your own cloud.

Qovery deploys each MCP server as a separate environment on your own AWS, GCP, Azure or Scaleway account, or your existing Kubernetes cluster, with per-environment RBAC, resource limits and logs you control. Start in under 10 minutes.