Skip to Content
ServicesM3 Plugin Runner Service

M3 Plugin Runner Service

Container lifecycle management service for M3 Forge plugins.

Overview

M3 Plugin Runner is a Python/FastAPI service that manages Docker containers for user-deployed plugins. It provides a “push code, it runs” experience similar to Hugging Face Spaces or Lightning AI.

Key Features

  • Multi-App Support: Gradio, Streamlit, FastAPI, static HTML, custom Docker
  • Git Integration: Clone and deploy directly from Git repositories
  • Zip Upload: Deploy from uploaded zip files
  • Dynamic Routing: Traefik integration for automatic subdomain routing
  • Container Lifecycle: Create, start, stop, redeploy, delete
  • Log Streaming: Access container logs via API
  • Health Checks: Monitor runner and Docker daemon health

Architecture

The frontend never talks to the runner directly. Lifecycle operations flow through the Node.js backend, while the rendered webapp is embedded back into the Studio UI via iframe session access.

Communication Flow

  1. User creates webapp via m3-forge UI
  2. Frontend calls tRPC webapp.create procedure
  3. Backend stores metadata in PostgreSQL and calls m3-plugin-runner HTTP API
  4. M3 Plugin Runner clones code, builds Docker image, starts container
  5. Traefik automatically routes {slug}.apps.marie.local to the container
  6. User accesses webapp via subdomain URL

Quick Start

Prerequisites

  • Python 3.11+
  • Docker with Docker Compose
  • Access to Docker socket (for container management)

Development

pnpm runner:install # Install Python dependencies in virtualenv pnpm runner:dev # Run development server with hot reload

Docker Deployment

pnpm runner:build # Build Docker image pnpm runner:up # Start services (runner + Traefik) pnpm runner:logs # View logs pnpm runner:down # Stop services

Studio Runner Registration

Create a pending app runner registration in Studio, copy the one-time token, and start the runner with MARIE_STUDIO_URL plus MARIE_RUNNER_REGISTRATION_TOKEN.

For managed deployments, the runner control URL is generated from deployment metadata:

DeploymentControl URL source
Local Python processhttp://localhost:<PORT> when MARIE_STUDIO_URL is local
Standalone runner ComposeCompose injects MARIE_RUNNER_CONTROL_HOST=localhost and the published runner port
M3 Forge all-in-one ComposeCompose injects the Docker service host m3-plugin-runner:8080
Kubernetes/HelmHelm injects the runner Service name, namespace, and service port
External hostSet explicit MARIE_RUNNER_CONTROL_URL

PUBLIC_BASE_URL and PUBLIC_ROUTE_PREFIX are different. They describe the browser-facing app route, such as http://localhost:8880/apps/my-app. The control URL is only for Studio API to call the runner API.

Configuration

Environment variables (can be set in .env file):

VariableDefaultDescription
HOST0.0.0.0Server bind address
PORT8080Server port
DEBUGfalseEnable debug mode and hot reload
APPS_DIR/data/appsDirectory for app data storage
TEMPLATES_DIR./templatesPath to Dockerfile templates
DOMAINapps.marie.localBase domain for webapp subdomains
DOCKER_NETWORKmarie-networkDocker network for containers
TRAEFIK_ENTRYPOINTwebTraefik entrypoint name
PUBLIC_BASE_URLhttp://localhost:8880Public base URL for deployed webapps
PUBLIC_ROUTE_PREFIX/appsPublic route prefix under PUBLIC_BASE_URL
MARIE_STUDIO_URLStudio API URL used for registration and heartbeat
MARIE_RUNNER_REGISTRATION_TOKENOne-time registration token copied from Studio
MARIE_RUNNER_CONTROL_URLExplicit control API URL for standalone/external installs
MARIE_RUNNER_CONTROL_HOSTRuntime-fabric, Compose, or Helm generated control host
MARIE_RUNNER_CONTROL_PORTPORTRuntime-fabric, Compose, or Helm generated control port
MARIE_RUNNER_SERVICE_NAMECompose or Kubernetes Service name used to derive the control URL
MARIE_RUNNER_SERVICE_NAMESPACEKubernetes namespace used with MARIE_RUNNER_SERVICE_NAME
RUNNER_API_KEYAPI key for authentication (empty = disabled)
DEFAULT_MEMORY_LIMIT2gDefault container memory limit
DEFAULT_CPU_LIMIT1.0Default container CPU limit (cores)

API Reference

Create Webapp

POST /apps Content-Type: multipart/form-data

Form Fields:

  • id (required): Unique identifier (UUID)
  • name (required): Display name
  • app_type: gradio | streamlit | fastapi | static | docker (default: gradio)
  • git_url: Git repository URL (required if no code file)
  • git_branch: Git branch (default: main)
  • port: Container port (default: 7860)
  • env: JSON-encoded environment variables (default: {})
  • code: Uploaded zip file (required if no git_url)

Response:

{ "id": "abc123", "name": "My Webapp", "status": "running", "url": "https://abc123.apps.marie.local" }

List Webapps

GET /apps

Get Webapp Status

GET /apps/{app_id}

Delete Webapp

DELETE /apps/{app_id}

Start / Stop / Redeploy

POST /apps/{app_id}/start POST /apps/{app_id}/stop POST /apps/{app_id}/redeploy

Get Logs

GET /apps/{app_id}/logs?tail=100

Health Check

GET /health

App Types

Python apps using Gradio .

Expected structure:

my-gradio-app/ ├── app.py # Must contain Gradio interface └── requirements.txt # Optional dependencies

Example:

import gradio as gr def greet(name): return f"Hello, {name}!" demo = gr.Interface(fn=greet, inputs="text", outputs="text") demo.launch()

Troubleshooting

If RUNNER_API_KEY is set, all requests need X-API-Key header. Set RUNNER_API_KEY= (empty) to disable auth for development.

Container fails to start

  1. Check logs: pnpm runner:logs or GET /apps/{id}/logs
  2. Verify Docker network exists: docker network ls | grep marie-network
  3. Check port conflicts: docker ps to see running containers

Build fails

  1. Ensure requirements.txt has valid dependencies
  2. Check for syntax errors in app code
  3. For custom Dockerfiles, ensure they’re valid

Cannot access webapp URL

  1. Verify Traefik is running: docker ps | grep traefik
  2. Check DNS/hosts file for *.apps.marie.local
  3. Verify container is running: GET /apps/{id}
Last updated on