Architecture¶
This document explains how FastMCP Runner works internally. Understanding the architecture helps when debugging issues or evaluating whether it fits your use case.
Design constraints¶
FastMCP Runner was built around a specific constraint: running MCP applications on platforms that restrict container execution. Hugging Face Spaces, for example, allows you to run approved base images but doesn't provide Docker socket access. You cannot run docker pull or docker run inside these environments.
The solution is to interact with container registries at the OCI layer rather than through Docker. FastMCP Runner uses crane, a tool from Google's go-containerregistry project, to authenticate to registries, fetch image manifests, and export filesystem layers—all without requiring a container runtime.
Startup sequence¶
When FastMCP Runner starts, the entrypoint script executes a linear sequence of operations. Each step must succeed for the next to proceed; failures terminate the container with an error message.
1. Input validation¶
The first operation validates that required environment variables are present and correctly formatted. IMAGE must be a syntactically valid OCI reference. PORT must be a number between 1 and 65535. Invalid input produces a clear error message and exits immediately.
This validation catches configuration errors before any network operations occur, making debugging faster.
2. Registry authentication¶
If REGISTRY_USER and REGISTRY_PASSWORD are provided, the runner authenticates to the registry extracted from the IMAGE variable. For ghcr.io/org/repo:tag, the registry is ghcr.io.
Authentication uses crane auth login, which stores credentials in a Docker-compatible config file. These credentials are cleared later in the startup sequence—see the Security documentation for details.
Anonymous access is attempted if no credentials are provided. This works for public images but will fail for private repositories.
3. Image introspection¶
Before pulling the full image, the runner fetches only the image configuration blob using crane config. This JSON document contains:
Env: Environment variables to setWorkingDir: Directory to run commands fromEntrypoint: The executable and initial argumentsCmd: Additional arguments appended to the entrypoint
The configuration is typically a few kilobytes, much smaller than the full image. This allows FastMCP Runner to understand how to run the application before committing to a potentially large download.
4. Launcher generation¶
A Python script (parse-oci-config) transforms the image configuration into an executable shell script. This launcher script:
- Exports environment variables from the original image
- Exports passthrough variables (
MCP_ENV_*with prefix stripped) - Applies runner overrides (
PORT,MCP_TRANSPORT, etc.) - Changes to the correct working directory
- Execs the application's entrypoint with its arguments
The launcher is written to /tmp/launcher.sh and made executable. When LOG_LEVEL=debug, the generated script is logged so you can see exactly what will run.
Conflicting entrypoint handling: If the image's entrypoint is /entrypoint.sh, entrypoint.sh, /start.sh, or /init.sh, the runner uses the CMD instead. This prevents infinite recursion where the generated launcher would call FastMCP Runner's own entrypoint script.
5. Filesystem extraction¶
The runner exports the image to a tar stream using crane export and extracts it to a staging directory. Not all files are copied to the runtime filesystem—FastMCP Runner explicitly allows only application-related paths:
Allowed paths: - /app — Application code - /data — Persistent data - /home — User home directories - /opt — Optional application packages - /srv — Service data - /var/lib, /var/data — Application state - Python site-packages directories - Node.js node_modules directories
Excluded paths: - /bin, /sbin — System binaries - /lib, /lib64 — System libraries - /usr/bin, /usr/sbin — System utilities - /etc — System configuration - /boot, /root — System directories
This allowlist prevents a malicious or misconfigured image from overwriting the runner's own binaries, libraries, or configuration. The pulled image can only affect application-level directories.
6. Privilege drop¶
The runner starts as root because it needs to create directories and set file ownership. Once the filesystem is prepared, it drops to UID 1000 (a non-root user created during image build) using su-exec.
su-exec is similar to gosu—it execs the target command directly without forking, so the application runs as PID 1 with proper signal handling.
7. Application execution¶
The final step execs the generated launcher script. From this point, FastMCP Runner's entrypoint is replaced by your application. The application receives signals directly and can shut down cleanly.
Component overview¶
FastMCP Runner is packaged as a single container image containing:
| Component | Purpose |
|---|---|
crane | OCI registry client for authentication and image operations |
su-exec | Privilege dropping without forking |
python3 | Runs the OCI config parser and applications using Python |
wget | Used by the health check |
/entrypoint.sh | Main orchestration script |
/usr/local/bin/parse-oci-config | Python script that generates the launcher |
/usr/local/lib/log.sh | POSIX shell logging library |
The base image is Chainguard wolfi-base, a minimal, hardened Linux distribution designed for container workloads. It includes only essential system packages, reducing attack surface.
Execution environment¶
Your application runs with these characteristics:
- User: UID 1000 (non-root)
- Working directory: As specified in your image's
WorkingDir, defaulting to/app - PID: 1 (receives signals directly)
- Filesystem: Read-write access to
/app,/data,/tmp, and home directory - Network: Full network access (no restrictions imposed by the runner)
Limitations¶
FastMCP Runner has inherent limitations from its architecture:
No Docker socket access. Your application cannot run Docker commands. If your MCP server needs to spawn containers, FastMCP Runner is not suitable.
No persistent changes to system directories. Changes your application makes to /usr, /etc, or other system paths during runtime are not persisted and may not work at all depending on mount configurations.
Single application per container. The runner executes one entrypoint. If your image contains multiple services, you'll need a process manager inside your image or separate deployments.
Startup latency. Pulling and extracting the image adds startup time compared to running your image directly. For large images, this can be significant. Consider optimizing your application image size if startup time matters.