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
- User creates webapp via m3-forge UI
- Frontend calls tRPC
webapp.createprocedure - Backend stores metadata in PostgreSQL and calls m3-plugin-runner HTTP API
- M3 Plugin Runner clones code, builds Docker image, starts container
- Traefik automatically routes
{slug}.apps.marie.localto the container - User accesses webapp via subdomain URL
Quick Start
Prerequisites
- Python 3.11+
- Docker with Docker Compose
- Access to Docker socket (for container management)
Development
From monorepo root
pnpm runner:install # Install Python dependencies in virtualenv
pnpm runner:dev # Run development server with hot reloadDocker Deployment
From monorepo root
pnpm runner:build # Build Docker image
pnpm runner:up # Start services (runner + Traefik)
pnpm runner:logs # View logs
pnpm runner:down # Stop servicesConfiguration
Environment variables (can be set in .env file):
| Variable | Default | Description |
|---|---|---|
HOST | 0.0.0.0 | Server bind address |
PORT | 8080 | Server port |
DEBUG | false | Enable debug mode and hot reload |
APPS_DIR | /data/apps | Directory for app data storage |
TEMPLATES_DIR | ./templates | Path to Dockerfile templates |
DOMAIN | apps.marie.local | Base domain for webapp subdomains |
DOCKER_NETWORK | marie-network | Docker network for containers |
TRAEFIK_ENTRYPOINT | web | Traefik entrypoint name |
RUNNER_API_KEY | “ | API key for authentication (empty = disabled) |
DEFAULT_MEMORY_LIMIT | 2g | Default container memory limit |
DEFAULT_CPU_LIMIT | 1.0 | Default container CPU limit (cores) |
API Reference
Create Webapp
POST /apps
Content-Type: multipart/form-dataForm Fields:
id(required): Unique identifier (UUID)name(required): Display nameapp_type:gradio|streamlit|fastapi|static|docker(default:gradio)git_url: Git repository URL (required if nocodefile)git_branch: Git branch (default:main)port: Container port (default:7860)env: JSON-encoded environment variables (default:{})code: Uploaded zip file (required if nogit_url)
Response:
{
"id": "abc123",
"name": "My Webapp",
"status": "running",
"url": "https://abc123.apps.marie.local"
}List Webapps
GET /appsGet 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}/redeployGet Logs
GET /apps/{app_id}/logs?tail=100Health Check
GET /healthApp Types
Gradio
Python apps using Gradio .
Expected structure:
my-gradio-app/
├── app.py # Must contain Gradio interface
└── requirements.txt # Optional dependenciesExample:
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
- Check logs:
pnpm runner:logsorGET /apps/{id}/logs - Verify Docker network exists:
docker network ls | grep marie-network - Check port conflicts:
docker psto see running containers
Build fails
- Ensure
requirements.txthas valid dependencies - Check for syntax errors in app code
- For custom Dockerfiles, ensure they’re valid
Cannot access webapp URL
- Verify Traefik is running:
docker ps | grep traefik - Check DNS/hosts file for
*.apps.marie.local - Verify container is running:
GET /apps/{id}
Last updated on