Deploying My First MCP with Docker and Cloud Run

Docker in the cloud

Abstract

Last week I shared how a holiday weekend experiment turned into my first Model Context Protocol (MCP). I ended with a working MCP running locally over HTTP. But I wanted to see how far I could take it. This week’s post covers the next phase: containerizing the MCP with Docker, slimming down the image, and deploying it to Google Cloud Run. Along the way, I learned a lot about FastMCP quirks, image optimization, testing with Cursor, and what it felt like to see a small weekend project live in the cloud.


From Local Breakthrough to Docker Challenge

Getting HTTP running locally felt like a big step forward. The natural “what’s next?” was obvious: could I package this into a Docker container and make it portable?

A Quick Note on Docker

For anyone less familiar: Docker is a tool that lets me package software into a “container”, essentially a lightweight, portable box that includes everything the program needs to run. Containers are smaller and more flexible than full virtual machines. This feature makes them interesting for developers because they can be built once and run almost anywhere, from a laptop to the cloud.

It is worth noting that Docker is not the only way to package and share MCPs. MCPs can also be distributed as simple local processes (using STDIO), hosted directly over HTTP without the need for containers, or even bundled into platform-specific installers or packages. For me, however, Docker felt like a natural next step. Our company is already considering containerization as part of our SaaS application strategy. So this experiment doubled as practice for future production needs.

That part, however, was not as straightforward as I hoped. FastMCP kept defaulting back to STDIO inside the container, even though HTTP worked fine locally. It took several rebuilds and trial-and-error tweaks before I finally got the container to respect the HTTP configuration.

Trimming the Fat

Once the MCP was running inside Docker, the next challenge was image size. My first build used Debian with Python and weighed in at around 384 MB. Debian is a popular Linux operating system distribution that serves as the base for many server environments. But Debian was heavier than necessary for a small project like this.

I rebuilt the image using Python with Alpine Linux. The size dropped to around 100 MB. Alpine is a lightweight Linux distribution commonly used for containers because it strips down to only the essentials. This provides a much more reasonable footprint. This is a good reminder that optimizing early paid off later when scaling.

Seeing It Run Across Machines

The first real “aha” moment came when I ran the container on one machine and accessed it successfully from another on the same local network. That was the point where the MCP stopped being a toy running on my laptop and started feeling like an actual service.

Deploying to Cloud Run

I decided to try Google Cloud Run next. It was free to experiment with, relatively easy to set up, and had parallels to AWS App Runner. After some configuration and some basic hardening of the container build, trimming unnecessary components, and preparing it for a more production-like environment, I had my MCP running in the cloud, accessible from anywhere.

One of the big wins was what Cloud Run provided out of the box compared to trying to host a server myself. Without changing anything in the build, I automatically got HTTPS support, load balancing, and automatic scaling based on demand. That meant I could focus on the MCP itself instead of worrying about configuring SSL certificates or writing scaling policies.

Throughout the process, I used Cursor to test the MCP and ensure that its LLM agent could utilize the provided tools. This required some configuration changes in how the MCP was accessed, particularly when switching from STDIO to HTTP and then to a cloud-hosted container. Testing also sparked a few improvements. For example, I added a simple health check tool inside the MCP so the agent could confirm the service was live before attempting requests.

That was a significant milestone: a project that began with a messy API website was now a containerized, cloud-hosted MCP that Cursor could actively interact with.

Looking Ahead

This was not the end of the road. I had ideas for making the container smarter and more efficient, such as:

  • Using SQLite instead of JSON for faster lookups.
  • Adding vector embeddings to improve how documentation was retrieved.
  • Experimenting with deployment on AWS App Runner and ECS/Fargate.
  • Eventually, dipping into Kubernetes when I was ready for that adventure.

Closing

What started as a holiday weekend experiment turned into a containerized, cloud-hosted MCP. The journey from local development to Docker to Cloud Run was full of lessons. About FastMCP defaults, image optimization, testing with Cursor, and the satisfaction of seeing something small come alive on the internet.

For my SaaS project, this learning is directly relevant. MCPs could become the backbone of how our platform integrates with external tools and APIs. Just as importantly, the experiment demonstrated that Docker containers were not only useful for MCPs but could also be a viable way to distribute microservices. In some cases, they offered advantages over AWS Lambda functions by reducing cold-start delays and removing limitations around long-running processes. AWS App Runner containers, however, come with a steady-state cost just to be deployed, which is different than how AWS Lambda Functions work. We do not have to make any transition immediately, but the ability to package and deploy services flexibly means we can experiment faster and scale smarter when necessary.

Matt Pitts, Sr Architect

Tags:

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *