mcesptool/PRODUCTION_DEPLOYMENT.md
Ryan Malloy 64c1505a00 Add QEMU ESP32 emulation support
Integrate Espressif's QEMU fork for virtual ESP device management:

- QemuManager component with 5 MCP tools (start/stop/list/status/flash)
- Config auto-detects QEMU binaries from ~/.espressif/tools/
- Supports esp32, esp32s2, esp32s3, esp32c3 chip emulation
- Virtual serial over TCP (socket://localhost:PORT) transparent to esptool
- Scan integration: QEMU instances appear in esp_scan_ports results
- Blank flash images initialized to 0xFF (erased NOR flash state)
- 38 unit tests covering lifecycle, port allocation, flash writes
2026-01-28 15:35:22 -07:00

970 lines
30 KiB
Markdown

# 🚀 Production Deployment Guide
## Overview
This guide covers deploying the FastMCP ESPTool server in production environments, including containerization, scaling, monitoring, and enterprise integration patterns.
## 🏭 Production Architecture
### Deployment Topology
```
┌─────────────────────────────────────────────────────────────┐
│ Production Environment │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Load │ │ Reverse │ │ SSL/TLS │ │
│ │ Balancer │───▶│ Proxy │───▶│ Termination │ │
│ │ (HAProxy) │ │ (Caddy) │ │ (Cert) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────┤
│ │ MCP ESPTool Server Cluster │
│ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ │ Server │ │ Server │ │ Server │ │
│ │ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │
│ │ │ │ │ │ │ │ │
│ │ │ Port: 8080 │ │ Port: 8081 │ │ Port: 8082 │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │
│ └─────────────────────────────────────────────────────────┤
│ │ │
│ ┌─────────────────────────────────────────────────────────┤
│ │ Shared Services │
│ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ │ Redis │ │ PostgreSQL │ │ Monitoring │ │
│ │ │ Cache │ │ Database │ │ (Grafana) │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │
│ └─────────────────────────────────────────────────────────┤
│ │ │
│ ┌─────────────────────────────────────────────────────────┤
│ │ Hardware Interface │
│ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ │ ESP Device │ │ ESP Device │ │ ESP Device │ │
│ │ │ Station 1 │ │ Station 2 │ │ Station N │ │
│ │ │ │ │ │ │ │ │
│ │ │/dev/ttyUSB0 │ │/dev/ttyUSB1 │ │/dev/ttyUSBN │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │
│ └─────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────┘
```
## 🐳 Container Production Setup
### Production Dockerfile
```dockerfile
# Multi-stage production Dockerfile
FROM python:3.11-slim-bookworm AS base
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
build-essential \
cmake \
ninja-build \
libusb-1.0-0-dev \
udev \
&& rm -rf /var/lib/apt/lists/*
# Install uv for fast package management
RUN pip install uv
# Create non-root user for security
RUN groupadd -r esptool && useradd -r -g esptool -d /app -s /bin/bash esptool
RUN mkdir -p /app && chown esptool:esptool /app
# Production stage
FROM base AS production
USER esptool
WORKDIR /app
# Copy project files
COPY --chown=esptool:esptool pyproject.toml ./
COPY --chown=esptool:esptool src/ ./src/
# Install production dependencies
RUN uv venv .venv && \
. .venv/bin/activate && \
uv pip install -e ".[production]" && \
uv pip install esptool esp-idf-tools
# Set up ESP-IDF (production minimal)
RUN git clone --depth 1 --branch v5.1 \
https://github.com/espressif/esp-idf.git /opt/esp-idf && \
cd /opt/esp-idf && \
./install.sh --targets esp32,esp32s3,esp32c3
# Environment setup
ENV ESP_IDF_PATH=/opt/esp-idf
ENV PATH="/opt/esp-idf/tools:/app/.venv/bin:$PATH"
ENV PYTHONPATH=/app/src
# Create directories for data persistence
RUN mkdir -p /app/data /app/logs /app/config
# Health check script
COPY --chown=esptool:esptool scripts/health-check.py ./health-check.py
RUN chmod +x health-check.py
# Security: Remove package management tools in production
USER root
RUN apt-get remove -y git curl build-essential cmake && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
USER esptool
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD python health-check.py || exit 1
# Expose port
EXPOSE 8080
# Production startup
CMD ["/app/.venv/bin/python", "-m", "mcp_esptool_server.server", "--production"]
```
### Production Docker Compose
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
# Load balancer
haproxy:
image: haproxy:2.8-alpine
ports:
- "80:80"
- "443:443"
- "8404:8404" # Stats
volumes:
- ./config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- ./certs:/etc/ssl/certs:ro
networks:
- frontend
- backend
restart: unless-stopped
depends_on:
- esptool-server-1
- esptool-server-2
- esptool-server-3
# MCP ESPTool Server Instances
esptool-server-1: &esptool-server
build:
context: .
dockerfile: Dockerfile
target: production
image: mcp-esptool-server:${VERSION:-latest}
environment:
- SERVER_ID=1
- SERVER_PORT=8080
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- DATABASE_URL=postgresql://esptool:${DB_PASSWORD}@postgres:5432/esptool
- REDIS_URL=redis://redis:6379/0
- ESP_DEVICE_BASE_PATH=/dev/serial
- PRODUCTION_MODE=true
- MAX_CONCURRENT_OPERATIONS=10
- ENABLE_METRICS=true
- METRICS_PORT=9090
volumes:
- ./data/server-1:/app/data
- ./logs:/app/logs
- ./config:/app/config:ro
- /dev/serial:/dev/serial:rw
networks:
- backend
restart: unless-stopped
depends_on:
- postgres
- redis
labels:
- "com.docker.compose.service=esptool-server"
esptool-server-2:
<<: *esptool-server
environment:
- SERVER_ID=2
- SERVER_PORT=8080
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- DATABASE_URL=postgresql://esptool:${DB_PASSWORD}@postgres:5432/esptool
- REDIS_URL=redis://redis:6379/0
- ESP_DEVICE_BASE_PATH=/dev/serial
- PRODUCTION_MODE=true
volumes:
- ./data/server-2:/app/data
- ./logs:/app/logs
- ./config:/app/config:ro
- /dev/serial:/dev/serial:rw
esptool-server-3:
<<: *esptool-server
environment:
- SERVER_ID=3
- SERVER_PORT=8080
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- DATABASE_URL=postgresql://esptool:${DB_PASSWORD}@postgres:5432/esptool
- REDIS_URL=redis://redis:6379/0
- ESP_DEVICE_BASE_PATH=/dev/serial
- PRODUCTION_MODE=true
volumes:
- ./data/server-3:/app/data
- ./logs:/app/logs
- ./config:/app/config:ro
- /dev/serial:/dev/serial:rw
# Database
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: esptool
POSTGRES_USER: esptool
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
networks:
- backend
restart: unless-stopped
# Cache and session store
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 1gb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- backend
restart: unless-stopped
# Monitoring and metrics
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
networks:
- backend
- monitoring
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
volumes:
- grafana-data:/var/lib/grafana
- ./config/grafana:/etc/grafana/provisioning:ro
networks:
- monitoring
restart: unless-stopped
depends_on:
- prometheus
# Log aggregation
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
volumes:
- loki-data:/tmp/loki
- ./config/loki.yml:/etc/loki/local-config.yaml:ro
networks:
- monitoring
restart: unless-stopped
volumes:
postgres-data:
redis-data:
prometheus-data:
grafana-data:
loki-data:
networks:
frontend:
driver: bridge
backend:
driver: bridge
monitoring:
driver: bridge
```
## ⚙️ Configuration Management
### Production Configuration
```python
# src/mcp_esptool_server/config/production.py
import os
from dataclasses import dataclass, field
from typing import List, Dict, Optional
from pathlib import Path
@dataclass
class ProductionConfig:
"""Production environment configuration"""
# Server settings
server_id: str = field(default_factory=lambda: os.getenv('SERVER_ID', '1'))
server_port: int = int(os.getenv('SERVER_PORT', '8080'))
max_workers: int = int(os.getenv('MAX_WORKERS', '4'))
max_concurrent_operations: int = int(os.getenv('MAX_CONCURRENT_OPERATIONS', '10'))
# Database configuration
database_url: str = os.getenv('DATABASE_URL', 'postgresql://localhost:5432/esptool')
redis_url: str = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
# Security settings
enable_auth: bool = os.getenv('ENABLE_AUTH', 'true').lower() == 'true'
jwt_secret: str = os.getenv('JWT_SECRET', 'change-me-in-production')
api_key_required: bool = os.getenv('API_KEY_REQUIRED', 'true').lower() == 'true'
# ESP device settings
esp_device_base_path: str = os.getenv('ESP_DEVICE_BASE_PATH', '/dev/serial')
max_device_connections: int = int(os.getenv('MAX_DEVICE_CONNECTIONS', '20'))
device_timeout: int = int(os.getenv('DEVICE_TIMEOUT', '30'))
# Logging and monitoring
log_level: str = os.getenv('LOG_LEVEL', 'INFO')
enable_metrics: bool = os.getenv('ENABLE_METRICS', 'true').lower() == 'true'
metrics_port: int = int(os.getenv('METRICS_PORT', '9090'))
# Performance tuning
enable_caching: bool = os.getenv('ENABLE_CACHING', 'true').lower() == 'true'
cache_ttl: int = int(os.getenv('CACHE_TTL', '300'))
# Factory programming settings
enable_factory_mode: bool = os.getenv('ENABLE_FACTORY_MODE', 'false').lower() == 'true'
factory_batch_size: int = int(os.getenv('FACTORY_BATCH_SIZE', '100'))
# Data retention
log_retention_days: int = int(os.getenv('LOG_RETENTION_DAYS', '30'))
metrics_retention_days: int = int(os.getenv('METRICS_RETENTION_DAYS', '90'))
def __post_init__(self):
"""Validate configuration after initialization"""
if self.enable_auth and self.jwt_secret == 'change-me-in-production':
raise ValueError("JWT_SECRET must be set in production with authentication enabled")
if not Path(self.esp_device_base_path).exists():
raise ValueError(f"ESP device base path does not exist: {self.esp_device_base_path}")
```
### Environment Configuration
```bash
# .env.production
# Core server settings
SERVER_ID=1
SERVER_PORT=8080
MAX_WORKERS=4
MAX_CONCURRENT_OPERATIONS=10
# Database and cache
DATABASE_URL=postgresql://esptool:secure_password@postgres:5432/esptool
REDIS_URL=redis://redis:6379/0
# Security (CHANGE THESE IN PRODUCTION)
ENABLE_AUTH=true
JWT_SECRET=your-256-bit-secret-key-here
API_KEY_REQUIRED=true
# ESP device configuration
ESP_DEVICE_BASE_PATH=/dev/serial
MAX_DEVICE_CONNECTIONS=20
DEVICE_TIMEOUT=30
# Logging and monitoring
LOG_LEVEL=INFO
ENABLE_METRICS=true
METRICS_PORT=9090
# Performance
ENABLE_CACHING=true
CACHE_TTL=300
# Factory programming
ENABLE_FACTORY_MODE=true
FACTORY_BATCH_SIZE=50
# Data retention
LOG_RETENTION_DAYS=30
METRICS_RETENTION_DAYS=90
# External services
GRAFANA_PASSWORD=secure_grafana_password
DB_PASSWORD=secure_db_password
```
## 🔐 Security Configuration
### Authentication and Authorization
```python
# src/mcp_esptool_server/security/auth.py
import jwt
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from fastapi import HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
class ProductionAuth:
"""Production authentication and authorization"""
def __init__(self, config: ProductionConfig):
self.config = config
self.security = HTTPBearer()
self.valid_api_keys: set = self._load_api_keys()
def _load_api_keys(self) -> set:
"""Load valid API keys from configuration"""
# In production, load from secure key management system
api_keys_file = Path(self.config.config_dir) / "api_keys.txt"
if api_keys_file.exists():
return set(api_keys_file.read_text().strip().split('\n'))
return set()
def create_access_token(self, data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
"""Create JWT access token"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.config.jwt_secret, algorithm="HS256")
return encoded_jwt
def verify_token(self, credentials: HTTPAuthorizationCredentials) -> Dict[str, Any]:
"""Verify JWT token"""
try:
payload = jwt.decode(
credentials.credentials,
self.config.jwt_secret,
algorithms=["HS256"]
)
return payload
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials"
)
def verify_api_key(self, api_key: str) -> bool:
"""Verify API key"""
return api_key in self.valid_api_keys
# Security middleware
class SecurityMiddleware:
"""Production security middleware"""
def __init__(self, app: FastMCP, auth: ProductionAuth):
self.app = app
self.auth = auth
self._setup_security_headers()
def _setup_security_headers(self):
"""Configure security headers"""
@self.app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
# Security headers
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = "default-src 'self'"
return response
```
## 📊 Monitoring and Observability
### Metrics Collection
```python
# src/mcp_esptool_server/monitoring/metrics.py
from prometheus_client import Counter, Histogram, Gauge, start_http_server
from typing import Dict, Any
import time
class ProductionMetrics:
"""Production metrics collection"""
def __init__(self, config: ProductionConfig):
self.config = config
# Operation metrics
self.operations_total = Counter(
'esptool_operations_total',
'Total number of ESPTool operations',
['operation_type', 'status', 'server_id']
)
self.operation_duration = Histogram(
'esptool_operation_duration_seconds',
'Duration of ESPTool operations',
['operation_type', 'server_id']
)
# Device metrics
self.connected_devices = Gauge(
'esptool_connected_devices',
'Number of connected ESP devices',
['server_id']
)
self.device_operations = Counter(
'esptool_device_operations_total',
'Total device operations by port',
['port', 'operation', 'status', 'server_id']
)
# System metrics
self.active_connections = Gauge(
'esptool_active_connections',
'Number of active MCP connections',
['server_id']
)
self.memory_usage = Gauge(
'esptool_memory_usage_bytes',
'Memory usage in bytes',
['server_id']
)
# Error metrics
self.errors_total = Counter(
'esptool_errors_total',
'Total number of errors',
['error_type', 'component', 'server_id']
)
if config.enable_metrics:
start_http_server(config.metrics_port)
def record_operation(self, operation_type: str, duration: float, status: str):
"""Record operation metrics"""
self.operations_total.labels(
operation_type=operation_type,
status=status,
server_id=self.config.server_id
).inc()
self.operation_duration.labels(
operation_type=operation_type,
server_id=self.config.server_id
).observe(duration)
def record_device_operation(self, port: str, operation: str, status: str):
"""Record device-specific operation"""
self.device_operations.labels(
port=port,
operation=operation,
status=status,
server_id=self.config.server_id
).inc()
def update_connected_devices(self, count: int):
"""Update connected devices count"""
self.connected_devices.labels(server_id=self.config.server_id).set(count)
def record_error(self, error_type: str, component: str):
"""Record error occurrence"""
self.errors_total.labels(
error_type=error_type,
component=component,
server_id=self.config.server_id
).inc()
```
### Logging Configuration
```python
# src/mcp_esptool_server/logging/production.py
import logging
import logging.config
from pathlib import Path
import json
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(name)s %(levelname)s %(message)s %(pathname)s %(lineno)d"
},
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
}
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "json",
"filename": "/app/logs/esptool-server.log",
"maxBytes": 10485760, # 10MB
"backupCount": 5
},
"error_file": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "json",
"filename": "/app/logs/esptool-errors.log",
"maxBytes": 10485760,
"backupCount": 5
}
},
"loggers": {
"mcp_esptool_server": {
"level": "DEBUG",
"handlers": ["console", "file", "error_file"],
"propagate": False
},
"esptool": {
"level": "INFO",
"handlers": ["file"],
"propagate": False
}
},
"root": {
"level": "INFO",
"handlers": ["console"]
}
}
def setup_production_logging(config: ProductionConfig):
"""Set up production logging configuration"""
# Ensure log directory exists
log_dir = Path("/app/logs")
log_dir.mkdir(exist_ok=True)
# Update logging level from configuration
LOGGING_CONFIG["handlers"]["console"]["level"] = config.log_level
LOGGING_CONFIG["loggers"]["mcp_esptool_server"]["level"] = config.log_level
# Configure logging
logging.config.dictConfig(LOGGING_CONFIG)
# Set up structured logging context
logger = logging.getLogger("mcp_esptool_server")
logger.info("Production logging initialized", extra={
"server_id": config.server_id,
"log_level": config.log_level,
"environment": "production"
})
```
## 🚀 Deployment Automation
### Kubernetes Deployment
```yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-esptool-server
labels:
app: mcp-esptool-server
spec:
replicas: 3
selector:
matchLabels:
app: mcp-esptool-server
template:
metadata:
labels:
app: mcp-esptool-server
spec:
containers:
- name: mcp-esptool-server
image: mcp-esptool-server:latest
ports:
- containerPort: 8080
- containerPort: 9090 # Metrics
env:
- name: SERVER_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: esptool-secrets
key: database-url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: esptool-secrets
key: redis-url
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: esptool-secrets
key: jwt-secret
volumeMounts:
- name: device-access
mountPath: /dev/serial
- name: config
mountPath: /app/config
readOnly: true
- name: data
mountPath: /app/data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: device-access
hostPath:
path: /dev/serial
- name: config
configMap:
name: esptool-config
- name: data
persistentVolumeClaim:
claimName: esptool-data
---
apiVersion: v1
kind: Service
metadata:
name: mcp-esptool-service
spec:
selector:
app: mcp-esptool-server
ports:
- name: http
port: 80
targetPort: 8080
- name: metrics
port: 9090
targetPort: 9090
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mcp-esptool-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- esptool.yourdomain.com
secretName: esptool-tls
rules:
- host: esptool.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mcp-esptool-service
port:
number: 80
```
### CI/CD Pipeline
```yaml
# .github/workflows/production-deploy.yml
name: Production Deployment
on:
push:
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install uv
uv venv
uv pip install -e ".[dev,testing]"
- name: Run tests
run: |
uv run pytest tests/ --cov=src --cov-fail-under=85
- name: Security scan
run: |
uv run bandit -r src/
uv run safety check
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
target: production
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Configure kubectl
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Deploy to production
run: |
kubectl set image deployment/mcp-esptool-server \
mcp-esptool-server=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
kubectl rollout status deployment/mcp-esptool-server --timeout=300s
- name: Verify deployment
run: |
kubectl get pods -l app=mcp-esptool-server
kubectl logs -l app=mcp-esptool-server --tail=50
```
## 📋 Production Checklist
### Pre-Deployment
- [ ] **Security Configuration**
- [ ] Change default passwords and secrets
- [ ] Configure JWT secrets
- [ ] Set up API key management
- [ ] Configure TLS certificates
- [ ] Review firewall rules
- [ ] **Infrastructure Setup**
- [ ] Database cluster configured
- [ ] Redis cache configured
- [ ] Load balancer configured
- [ ] Monitoring stack deployed
- [ ] Log aggregation configured
- [ ] **Hardware Access**
- [ ] ESP device permissions configured
- [ ] USB device access verified
- [ ] Device path mapping documented
- [ ] Hardware redundancy planned
### Post-Deployment
- [ ] **Monitoring Setup**
- [ ] Grafana dashboards configured
- [ ] Alerting rules configured
- [ ] Log analysis configured
- [ ] Performance baselines established
- [ ] **Operational Procedures**
- [ ] Backup procedures documented
- [ ] Disaster recovery tested
- [ ] Scaling procedures documented
- [ ] Maintenance windows scheduled
- [ ] **Team Training**
- [ ] Operations team trained
- [ ] Troubleshooting guides created
- [ ] Escalation procedures documented
- [ ] User documentation published
This production deployment guide ensures enterprise-grade reliability, security, and scalability for the FastMCP ESPTool server in production environments.
`★ Insight ─────────────────────────────────────`
**Enterprise-Grade Deployment**: This production guide demonstrates how to scale the FastMCP ESPTool server from development to enterprise production environments with proper security, monitoring, and operational procedures.
**Hardware Integration Challenges**: The unique aspect of this deployment is managing physical ESP device access in containerized environments, requiring careful consideration of USB device mapping and permissions across different deployment platforms.
**Observability Excellence**: The comprehensive monitoring, logging, and metrics collection ensures production visibility and enables proactive problem resolution, critical for embedded development workflows where hardware issues can be difficult to diagnose.
`─────────────────────────────────────────────────`
<function_calls>
<invoke name="TodoWrite">
<parameter name="todos">[{"content": "Create project initialization and setup documentation", "status": "completed", "activeForm": "Creating project initialization and setup documentation"}, {"content": "Design concrete implementation examples", "status": "completed", "activeForm": "Designing concrete implementation examples"}, {"content": "Create production deployment guide", "status": "completed", "activeForm": "Creating production deployment guide"}]