| title | Build MCP containers |
|---|---|
| description | How to build MCP server containers without running them using the ToolHive CLI. |
This guide explains how to use the thv build
command to build MCP server containers from protocol schemes without running
them. This is useful for pre-building containers for Kubernetes deployments,
CI/CD pipelines, and container registry workflows.
The thv build command allows you to build containers from protocol schemes
(uvx://, npx://, go://) without immediately running them. This provides
several benefits:
- Pre-build containers for faster deployment in Kubernetes environments
- Separate build and run phases in CI/CD pipelines
- Custom image tagging for container registry workflows
- Dockerfile generation for inspection and customization
- Build validation before deployment
To build a container from a protocol scheme:
thv build <PROTOCOL_SCHEME>For example:
# Build a Python MCP server using uvx
thv build uvx://mcp-server-git
# Build a Node.js MCP server using npx with a pinned version
thv build npx://@upstash/context7-mcp@1.0.26
# Build a Go MCP server
thv build go://github.com/example/my-mcp-server@latest:::info[What's happening?]
When you run thv build, ToolHive:
- Detects the protocol scheme and extracts the package reference
- Generates a Dockerfile based on the appropriate template
- Builds a Docker image with the package installed
- Tags the image with an auto-generated name or your custom tag
- Displays the built image name for use with other tools
:::
Use the --tag (or -t) flag to specify a custom name and tag for the built
image:
thv build --tag my-custom-name:latest npx://@modelcontextprotocol/server-filesystemThis is particularly useful for:
- Container registries: Tag images for pushing to registries
- Kubernetes deployments: Use predictable image names in manifests
- Version management: Tag images with specific versions
Build and tag for pushing to a container registry:
# Build and tag for Docker Hub
thv build --tag myusername/mcp-git-server:v1.0.0 uvx://mcp-server-git
# Build and tag for GitHub Container Registry
thv build --tag ghcr.io/myorg/mcp-filesystem:latest npx://@modelcontextprotocol/server-filesystem
# Push to registry
docker push ghcr.io/myorg/mcp-filesystem:latestBuild images with predictable names for Kubernetes manifests:
# Build with a consistent tag
thv build --tag mcp-servers/git-server:stable uvx://mcp-server-gitUse the built image in your Kubernetes manifests:
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: git-server
namespace: production
spec:
image: mcp-servers/git-server:stable
# ... other spec fields ...Build multiple versions of the same server:
# Build different versions
thv build --tag mcp-git:v1.0.0 uvx://mcp-server-git@1.0.0
thv build --tag mcp-git:v1.1.0 uvx://mcp-server-git@1.1.0
thv build --tag mcp-git:latest uvx://mcp-server-git@latestThe thv build command supports the same protocol schemes as
thv run:
Build Python-based MCP servers using the uv package manager:
# Build with auto-generated name
thv build uvx://mcp-server-git
# Build with custom tag
thv build --tag my-git-server:latest uvx://mcp-server-git@1.2.0Build Node.js-based MCP servers using npm:
# Build with auto-generated name
thv build npx://@modelcontextprotocol/server-filesystem
# Build with custom tag
thv build --tag filesystem-server:v2.0 npx://@modelcontextprotocol/server-filesystem@2.0.0Build Go-based MCP servers:
# Build from remote Go module
thv build --tag grafana-mcp:latest go://github.com/grafana/mcp-grafana/cmd/mcp-grafana@latest
# Build from local Go project
thv build --tag my-local-server:dev go://./cmd/my-mcp-serverSome MCP servers require specific subcommands or arguments that must always be
present. You can bake these required arguments directly into the container image
at build time using the -- separator at the end of the thv build command:
thv build <PROTOCOL_SCHEME> -- <BUILD_ARGS>:::info[Build-time vs runtime arguments]
- Build-time arguments: Baked into the container image and always present. These are typically required subcommands or essential configuration flags.
- Runtime arguments: Passed when running the container and appended after build-time arguments. These are typically optional flags or dynamic configuration.
:::
Build-time arguments are embedded in the container's ENTRYPOINT and always
execute before any runtime arguments. For example, the LaunchDarkly MCP server
requires a start subcommand:
# Bake "start" subcommand into container
thv build --tag launchdarkly-mcp:latest npx://@launchdarkly/mcp-server -- start
# Runtime args still append after baked-in args
thv run launchdarkly-mcp:latest -- --verbose
# Executes: npx @launchdarkly/mcp-server start --verboseYou can include multiple build-time arguments as needed:
thv build uvx://my-package -- --transport stdio --log-level infoUse the --dry-run flag to generate the Dockerfile without building the image:
# Output Dockerfile to stdout
thv build --dry-run uvx://mcp-server-git
# Save Dockerfile to a file
thv build --dry-run --output Dockerfile.mcp-git uvx://mcp-server-gitThis is useful for:
- Inspecting the build process before building
- Customizing Dockerfiles for specific requirements
- Understanding dependencies and build steps
- Debugging build issues
# Generated by: thv build --dry-run uvx://mcp-server-git
FROM python:3.12-slim
# Install uv
RUN pip install uv
# Install the package
RUN uv tool install mcp-server-git
# Set the entrypoint
ENTRYPOINT ["uv", "tool", "run", "mcp-server-git"]The thv build command is especially useful for Kubernetes deployments where
you need to pre-build containers before deploying them.
-
Build the container with a specific tag:
thv build --tag ghcr.io/myorg/mcp-git:v1.0.0 uvx://mcp-server-git@1.0.0
-
Push to container registry:
docker push ghcr.io/myorg/mcp-git:v1.0.0
-
Deploy to Kubernetes using the pre-built image:
apiVersion: toolhive.stacklok.dev/v1alpha1 kind: MCPServer metadata: name: git-server namespace: production spec: image: ghcr.io/myorg/mcp-git:v1.0.0 transport: stdio
Integrate thv build into your CI/CD pipeline for automated container building:
# Example GitHub Actions workflow
name: Build and Deploy MCP Server
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ToolHive
uses: StacklokLabs/toolhive-actions/install@v0
with:
version: latest
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build MCP server
run: |
thv build --tag ghcr.io/${{ github.repository }}/mcp-server:${{ github.ref_name }} \
uvx://mcp-server-git@${{ github.ref_name }}
- name: Push to registry
run: |
docker push ghcr.io/${{ github.repository }}/mcp-server:${{ github.ref_name }}For more advanced CI/CD patterns including multi-architecture builds, supply chain security, and change detection, see the Advanced CI/CD patterns guide.
For corporate environments with custom certificate authorities:
# Use global CA certificate configuration
thv config set-ca-cert /path/to/corporate-ca.crt
thv build uvx://internal-mcp-server
# Override CA certificate for specific build
thv build --ca-cert /path/to/special-ca.crt uvx://special-serverEnterprise environments often use private package registries or mirrors instead of public registries like npm or PyPI. ToolHive supports configuring build environment variables that are injected into the Dockerfile during builds, allowing you to use custom registries for all protocol scheme builds.
Use the thv config set-build-env command to configure environment variables
that will be included in all builds:
thv config set-build-env <KEY> <VALUE>Common environment variables for package registries:
| Package manager | Environment variable | Example value |
|---|---|---|
| npm | NPM_CONFIG_REGISTRY |
https://npm.corp.example.com |
| Go | GOPROXY |
https://goproxy.corp.example.com |
| Go | GOPRIVATE |
github.com/mycompany/* |
| pip/uv | PIP_INDEX_URL |
https://pypi.corp.example.com/simple |
| pip/uv | PIP_TRUSTED_HOST |
pypi.corp.example.com |
Example configuration for an enterprise environment:
# Configure npm to use a corporate registry
thv config set-build-env NPM_CONFIG_REGISTRY https://npm.corp.example.com
# Configure Go proxy for private modules
thv config set-build-env GOPROXY https://goproxy.corp.example.com
thv config set-build-env GOPRIVATE "github.com/mycompany/*"
# Configure Python/uv to use a corporate PyPI mirror
thv config set-build-env PIP_INDEX_URL https://pypi.corp.example.com/simpleBuild MCP servers from local Go projects:
# Build from current directory
cd my-go-mcp-project
thv build --tag my-server:dev go://.
# Build from relative path
thv build --tag my-server:dev go://./cmd/server
# Build from absolute path
thv build --tag my-server:dev go:///path/to/my-project| Feature | thv build |
thv run |
|---|---|---|
| Purpose | Build containers only | Build and run containers |
| Output | Container image | Running MCP server |
| Use case | Pre-building, CI/CD | Development, testing |
| Kubernetes | Pre-build for deployment | Direct development |
| Custom tagging | ✅ --tag flag |
❌ Auto-generated names |
| Dockerfile generation | ✅ --dry-run flag |
❌ Not available |
- Use built containers with
thv runfor local development - Deploy pre-built containers to Kubernetes
- Set up CI/CD pipelines for automated building
- Learn about container registry workflows
Build fails with network errors
If builds fail with network connectivity issues:
-
Check internet connectivity for downloading packages
-
Configure CA certificates for corporate environments:
thv config set-ca-cert /path/to/corporate-ca.crt
-
Use proxy settings if required by your network
-
Verify package names and versions exist in the respective registries
Invalid image tag format
If you get image tag validation errors:
- Use valid Docker image tag format:
name:tagorregistry/name:tag - Avoid special characters except hyphens, underscores, and dots
- Use lowercase names for compatibility
- Check tag length limits (typically 128 characters)
Example valid tags:
thv build --tag my-server:latest uvx://package
thv build --tag ghcr.io/org/server:v1.0.0 npx://packagePackage not found errors
If the build fails because a package cannot be found:
-
Verify package exists in the respective registry:
-
Check version specifiers:
# Correct version formats thv build uvx://package@1.0.0 thv build npx://package@latest thv build go://github.com/user/repo@v1.0.0 -
For Go modules, ensure the path includes the correct import path
Custom registry not being used
If your custom package registry configuration isn't being applied:
-
Verify your build environment configuration:
thv config get-build-env
-
Check the environment variable names match what your package manager expects (for example,
NPM_CONFIG_REGISTRYfor npm,GOPROXYfor Go) -
Use
--dry-runto inspect the generated Dockerfile and verify your environment variables are being injected:thv build --dry-run uvx://my-package
-
Ensure network connectivity to your custom registry from inside the container build environment