Want to build and deploy your MCP server automatically with your agent? Start with the MCP Builder Quickstart. That flow uses Runlayer MCP + MCP Builder on top of Runlayer Deploy.
- Deploy custom MCP servers not already available as connectors
- Build internal or proprietary integrations
- Run services requiring specific dependencies or configurations
Prerequisites
Before deploying, ensure you have:- Docker installed and running locally
- Runlayer CLI installed (
uvx runlayer) - A Dockerfile and application code ready to deploy
Non-admin users can request deploy-based MCP servers through the connectors flow. The request follows the standard approval workflow. Direct deployment via the CLI requires admin permissions.
Quick Start
Initialize Deployment
Create a new deployment and generate configuration:This creates a deployment in the backend and generates a
runlayer.yaml template with your deployment ID. You can also pick a custom icon for the deployment during creation or update it later from the deployment settings dialog.Configure Service
Edit the generated
runlayer.yaml file:- Set your service port
- Choose CPU and memory resources
- Add environment variables
- Configure build settings for your Dockerfile
Runtimes
Docker
The Docker runtime builds your application locally using Docker and deploys it as a container. This gives you full control over dependencies and supports any language or framework. Build Configuration:dockerfile: Path to your Dockerfile (default:Dockerfile)context: Build context directory (default:.)platform: CPU architecture -x86orarm(default:arm, recommended for cost savings)args: Build arguments as key-value pairstarget: Target stage for multi-stage builds (optional)
Coming Soon
Managed Runtimes for Python and TypeScript—deploy with just your code and dependencies.
Configuration Reference
Completerunlayer.yaml structure:
Field Details
service.expose
Allow unauthenticated access to OAuth routes your deployed MCP server implements. Omit service.expose for non-OAuth servers.
Use this only for OAuth discovery and OAuth protocol endpoints that must be reachable before a client has a Runlayer Bearer token:
infrastructure
Valid CPU and memory combinations:
| CPU (vCPU) | Memory Options (MB) | Best For |
|---|---|---|
| 256 (.25 vCPU) | 512, 1024, 2048 | Minimal services |
| 512 (.5 vCPU) | 1024, 2048, 3072, 4096 | Light services |
| 1024 (1 vCPU) | 2048, 3072, 4096, 5120, 6144, 7168, 8192 | Standard services |
| 2048 (2 vCPU) | 4096-16384 (1024 MB increments) | Medium workloads |
| 4096 (4 vCPU) | 8192-30720 (1024 MB increments) | Heavy workloads |
See AWS Fargate pricing for cost estimates.
infrastructure.platform
CPU architecture for the container runtime: x86 or arm.
- In build mode: Defaults to
build.platform. If explicitly set, must matchbuild.platformor validation will fail. - In image mode: Defaults to
x86if not specified. Set this to match the architecture of your pre-built Docker image.
infrastructure.enable_db
Enable a managed database for persistent storage. When enabled, a DynamoDB table is provisioned with the following environment variables injected into your container:
DB_TABLE_NAME- The DynamoDB table nameDB_TABLE_REGION- The AWS region of the table
pk) and sort key (sk) schema. TTL is enabled via the ttl attribute (set to a Unix timestamp to auto-expire items).
Use any standard DynamoDB client to interact with the table—boto3 (Python), AWS SDK (Node.js), or any AWS SDK. All standard DynamoDB operations are supported: GetItem, PutItem, Query, Scan, BatchGetItem, BatchWriteItem, etc.
Example MCP tool with persistence (Python):
AWS cross-account roles (hosted only)
Useinfrastructure.aws.assume_roles when a hosted connector needs to call AWS APIs in your account without storing long-lived access keys in env. This applies only to Runlayer-hosted ECS deployments—not remote or local catalog connectors.
How it works
- You declare customer role ARNs in
infrastructure.aws.assume_roles. - Runlayer provisions a dedicated ECS task role per deployment in the Runlayer-hosted AWS account.
- The connector container runs with that task role and calls
sts:AssumeRoleon your roles usingExternalId = <deployment-id>. - Runlayer does not broker STS or store static AWS keys.
| Variable | Description |
|---|---|
RUNLAYER_DEPLOYMENT_ID | Deployment UUID — pass as ExternalId when assuming customer roles |
RUNLAYER_DEPLOYMENT_TASK_ROLE_ARN | This deployment’s ECS task role — the principal your trust policy must allow |
RUNLAYER_AWS_ROLE_<NAME> | Customer role ARN for map key <NAME> (uppercased, e.g. secrets → RUNLAYER_AWS_ROLE_SECRETS) |
| Step | Action |
|---|---|
| 1 | Create or choose a least-privilege IAM role in your AWS account |
| 2 | Attach a permissions policy granting only what the connector needs (S3, Secrets Manager, etc.) |
| 3 | Add a trust policy allowing only the Runlayer deployment task role ARN with sts:ExternalId = deployment UUID |
| 4 | Add the role ARN to infrastructure.aws.assume_roles.<name> in runlayer.yaml |
| 5 | Deploy and copy task_role_arn from CLI output, deployment page, or API |
| Source | Field |
|---|---|
runlayer deploy success output | task_role_arn |
| Deployment API / dashboard | task_role_arn |
| Running container | RUNLAYER_DEPLOYMENT_TASK_ROLE_ARN |
task_role_arn is per deployment and stable across redeploys. Use it as the sole Principal.AWS in your trust policy—not the Runlayer AWS account ID or a broad shared role.
Trust policy template
<RUNLAYER_DEPLOYMENT_TASK_ROLE_ARN>— from deploy output / API /RUNLAYER_DEPLOYMENT_TASK_ROLE_ARNenv var<deployment-uuid>— theidinrunlayer.yaml(same asRUNLAYER_DEPLOYMENT_ID)
- Trust policy — who can assume the role (the Runlayer deployment task role only)
- Permissions policy — what the connector can do after assume (your data-plane access)
AssumeRole errors are authoritative if your customer trust policy is not ready yet. Common issues: wrong principal (account root instead of task_role_arn), missing or incorrect ExternalId, typo in role ARN.
env
Environment variables are passed to your container at runtime. Values are automatically masked as *** in stored configurations and API responses for security.
CLI Variable Substitution:
The CLI supports shell-style environment variable substitution when loading runlayer.yaml, allowing you to reference local environment variables or .env files without hardcoding sensitive values.
Syntax:
${VAR}- Required variable (deployment fails if not set)${VAR:-default}- Use default value if variable is unset or empty${VAR-default}- Use default value only if variable is unset (not if empty)$$DEPLOYMENT_URL- Special backend variable - your deployment’s public URL (backend replaces at runtime)$$RUNLAYER_URL- Special backend variable - the Runlayer platform URL$$RUNLAYER_OAUTH_CALLBACK_URL- Special backend variable - OAuth callback URL for integrations
$$DEPLOYMENT_URL is a special variable that resolves to your deployment’s URL. OAuth servers commonly use it as their issuer/base URL..env in your current working directory.
Automatic Environment Variables:
The backend automatically injects the following variables into your container:
DEPLOYMENT_URL- Your deployment URL:https://your-platform.com/api/v1/proxy/hosted/{deployment_id}RUNLAYER_URL- The Runlayer platform URLRUNLAYER_OAUTH_CALLBACK_URL- OAuth callback URL:https://your-platform.com/oauth/callback
$$DEPLOYMENT_URL (double dollar sign) as a placeholder in your YAML configuration. The backend replaces this at deployment time with your deployment URL.
Example:
OAUTH_ISSUER:https://your-platform.com/api/v1/proxy/hosted/{deployment_id}
CLI Commands
CI/CD with organization API keys
For automated deploy pipelines, create an organization API key with the Deploy role in Settings → Organization API keys. Deploy-scoped organization keys can initialize, validate, deploy, pull, and destroy deployments without being tied to a user session. You can pass the key directly:Deploy organization keys are intended for CI/CD deployment workflows. They can manage deployment records and infrastructure, including
deploy destroy, but they cannot register deployed services as MCP connectors during deployment.deploy init
Initialize a new deployment and generate configuration file.
- Creates a deployment record in your Runlayer instance
- Generates a
runlayer.yamltemplate with your deployment ID - Prompts for deployment name (must be lowercase and URL-friendly)
--config,-c: Path to create config file (default:runlayer.yaml)--secret,-s: API key override (optional; preferuvx runlayer login)--host,-H: Runlayer instance URL (required)
deploy
Build and deploy your service.
- Loads environment variables from
.env(auto-discovered) or--env-file - Substitutes variables in your YAML configuration
- Validates your YAML configuration
- Builds Docker image locally using your Dockerfile
- Pushes image to managed registry
- Deploys infrastructure
- Monitors deployment with live log streaming
- Prints the deployment URL so you can open it directly
--config,-c: Path to config file (default:runlayer.yaml)--secret,-s: API key override (optional; preferuvx runlayer login)--host,-H: Runlayer instance URL (required)--env-file,-e: Path to .env file for variable substitution (optional, auto-discovers.envby default)
deploy validate
Validate configuration without deploying.
--config,-c: Path to config file (default:runlayer.yaml)--secret,-s: API key override (optional; preferuvx runlayer login)--host,-H: Runlayer instance URL (required)--env-file,-e: Path to .env file for variable substitution (optional, auto-discovers.envby default)
deploy pull
Fetch the current deployment configuration from the backend and save it as a local runlayer.yaml.
- Reads the deployment ID from the existing
runlayer.yaml(or--deployment-id) - Creates a timestamped backup of the current file (if present)
- Downloads the latest configuration and writes it locally
${VAR_NAME} placeholders; backend variables like $$DEPLOYMENT_URL are preserved as-is.
Options:
--config,-c: Path to save config file (default:runlayer.yaml)--deployment-id,-d: Deployment ID (overrides config file)--secret,-s: API key override (optional; preferuvx runlayer login)--host,-H: Runlayer instance URL (required)
deploy destroy
Teardown deployment and destroy infrastructure.
--config,-c: Path to config file containing deployment ID--deployment-id,-d: Deployment ID (overrides config file)--secret,-s: API key override (optional; preferuvx runlayer login)--host,-H: Runlayer instance URL (required)
Examples
Example: Multi-stage Node.js Build with legacy SSE transport
TypeScript MCP server with build arguments and optimized production build:Example: Environment Variable Substitution
Production MCP server with secrets loaded from.env file:
runlayer.yaml:
Timing matters: CLI substitution (
${VAR}) happens before sending to backend. Backend substitution ($$DEPLOYMENT_URL) happens at deployment time. This means validation errors will show CLI-substituted values, but $$DEPLOYMENT_URL remains as-is until the backend deploys your service.Example: OAuth MCP Server
OAuth-enabled MCP server with unauthenticated OAuth routes:Registering as an MCP Server
After your deployment succeeds, register it as an MCP server to make it available in Connectors:- Navigate to the deployment detail page in the Runlayer UI
- Click “Register as MCP Server” and update any information you need
- Click on “Create MCP”
- Your deployed service is now available as a connector in Runlayer
Identity Forward
When enabled on any HTTP-based MCP connector (Streaming HTTP or SSE), the Runlayer proxy injects the authenticated caller’s identity into upstream HTTP requests. Your server reads it to scope data per user, write meaningful audit logs, enforce per-user rate limits, or personalize responses. Identity Forward works on catalog connectors, manually added connectors, and deployed connectors alike — it is not limited to Runlayer Deploy. There are two independent toggles:| Toggle | Carries | When to use |
|---|---|---|
| Forward identity headers | Plain X-Runlayer-* headers. Read them directly — no SDK, no verification. | Upstreams you fully control on a trusted network path. |
| Forward signed identity token (preferred) | A short-lived Runlayer-signed JWT in X-Runlayer-Identity-Token, verifiable via JWKS. | Any upstream you want to cryptographically prove the call came through Runlayer. |
Headers sent
| Header | Description | Sent for |
|---|---|---|
X-Runlayer-Subject-Type | One of user, agent, obo | always |
X-Runlayer-Org-Id | Runlayer organization UUID | always |
X-Runlayer-User-Email | End-user email | user, obo |
X-Runlayer-User-Id | Runlayer user UUID | user, obo |
X-Runlayer-Agent-Id | Agent account UUID | agent, obo |
X-Runlayer-Agent-Name | Agent display name | agent, obo |
Subject types
| Subject type | When it occurs |
|---|---|
user | A human end-user calling directly through their MCP client (Cursor, Claude Desktop, etc.). |
agent | A standalone agent account (machine-to-machine) calling with its own credentials. No human in the loop. |
obo | An agent calling on behalf of a delegating user. Both agent identity and the delegator’s user identity are forwarded. |
Example requests
Human user:Signed identity token
When Forward signed identity token is enabled, Runlayer mints a short-lived JWT for every upstream request and sends it as:| Claim | Value |
|---|---|
alg (header) | EdDSA |
kid (header) | Stable fingerprint of the signing key. |
iss | Runlayer instance URL. |
aud | runlayer:identity-forward:<server-id> — per-upstream so a token minted for one server can’t be replayed against another. The <server-id> is the Runlayer server UUID surfaced in the admin UI for that server. |
iat / exp | Issued-at + expiry; TTL is 5 minutes. |
jti | Unique per request. |
sub | user_id when present, otherwise agent_id. |
subject_type | user, agent, or obo. |
organization_id | Runlayer organization UUID. |
user_email, user_id | Present for user and obo subjects. |
agent_id, agent_name | Present for agent and obo subjects. |
Verifying the signed token
Python (FastAPI + PyJWT):jose):
Enable
- Open the server’s configuration in Runlayer (admin only).
- Toggle Forward identity headers and/or Forward signed identity token on. The signed option is recommended; the plain headers are kept for backwards compatibility and trusted-network scenarios.
- Save. The standard server-update audit log entry records the change like any other server-config edit.
Reading the headers
Python (FastAPI / FastMCP):Limitations
- Transports:
streaming-httpandsseonly. Not supported onstdio(use environment variables / placeholders if you need identity for stdio). - Email-only personalization. Organization roles, groups, and custom claims are not forwarded.
- Runlayer self-MCP (
runlayer-mcp) does not receive identity headers or tokens; it already runs in-process with full subject context. - Signed tokens have a 5-minute TTL. Verify upstream and re-fetch the JWKS when an unknown
kidis presented (rotation rotateskidautomatically).
Troubleshooting
Docker build fails
Docker build fails
Solutions:
- Test build locally first:
docker build -t test . - Check Dockerfile syntax and instructions
- Verify build context includes all required files
- Check build arguments are passed correctly
Invalid CPU/memory combination
Invalid CPU/memory combination
Solution:Refer to the configuration reference table for valid combinations. Not all CPU/memory pairs are supported. For example, 256 CPU only supports 512, 1024, or 2048 MB memory.
Deployment stuck or timeout
Deployment stuck or timeout
Solutions:Check deployment logs in the web UI. Common issues:
- Container failing health checks (check your app responds on the configured port)
- Wrong port configured in YAML
- Missing required environment variables
- Application crashes at startup (check application logs)
Can't destroy deployment
Can't destroy deployment
Solution:If the deployment has connected MCP servers, use the Delete All option to remove the deployment and its servers together. If you lack permission to delete those servers, ask the connector owner or an administrator to do it.
Environment variables not working
Environment variables not working
Explanation:Values are automatically masked as
*** in stored configurations and API responses for security. This is expected behavior. Your actual values are securely passed to the container at runtime and are not visible in the UI or API.Variable substitution errors
Variable substitution errors
Common issues:
- “Required environment variable ‘VAR’ is not set”: Variable isn’t in your environment or
.envfile. Check spelling and ensure it’s exported. - Wrong default syntax: Use
${VAR:-default}(with colon) to handle empty strings, or${VAR-default}(no colon) to only use default if unset. $$DEPLOYMENT_URLgetting substituted by CLI: Use double dollar sign ($$DEPLOYMENT_URL), not single (${DEPLOYMENT_URL}). Single dollar triggers CLI substitution, double dollar preserves it for backend replacement.
Build works locally but fails in deployment
Build works locally but fails in deployment
Solutions:
- Verify platform architecture matches (
x86vsarm) - Check all dependencies are in Dockerfile, not just on your local machine
- Ensure build arguments are specified in YAML if required
- Review build logs for missing packages or permission errors
Upstream server doesn't see identity headers / token
Upstream server doesn't see identity headers / token
Checklist:
- The relevant Identity Forward toggle (headers and/or signed token) is on for this server (admin server config).
- Transport is
streaming-httporsse.stdiois not supported. - Caller has identity (logged-in user, OBO agent, or standalone agent account); anonymous calls forward only
Subject-TypeandOrg-Id. - You’re reading the headers case-insensitively (HTTP header names are not case-sensitive; httpx canonicalizes them on send).
- For the signed token: the verifier’s expected audience must be
runlayer:identity-forward:<server-id>for this specific server (copy the UUID from the Runlayer admin UI). A token minted for server A intentionally fails audience check on server B. Also confirm the JWKS URL is reachable from the upstream environment.
exec format error
exec format error
infrastructure.platform.Fix: Set infrastructure.platform to match your docker image architecture.- For x86/amd64 images:
platform: x86 - For ARM images:
platform: arm
Next Steps
MCP Builder Quickstart
Scaffold a custom MCP server fast with the MCP Builder prompt
OAuth for Deployed Servers
Dual-auth architecture and token storage for OAuth connectors
Connectors
View and manage your connectors
Security Best Practices
Secure your MCP deployments and integrations