# Deployment Guide This guide covers production deployment of the Gitea HTTP MCP Wrapper in various environments. ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Docker Deployment](#docker-deployment) 3. [Security Best Practices](#security-best-practices) 4. [Monitoring and Health Checks](#monitoring-and-health-checks) 5. [Reverse Proxy Configuration](#reverse-proxy-configuration) 6. [Cloud Deployment](#cloud-deployment) 7. [Kubernetes Deployment](#kubernetes-deployment) 8. [Troubleshooting](#troubleshooting) ## Prerequisites ### Required - Docker and Docker Compose (for Docker deployment) - Gitea instance with API access - Gitea API token with appropriate permissions - Network connectivity between wrapper and Gitea instance ### Recommended - HTTPS-capable reverse proxy (Nginx, Caddy, Traefik) - Secrets management solution (not `.env` files in production) - Monitoring and logging infrastructure - Firewall or VPN for network security ## Docker Deployment ### Quick Start 1. **Clone the repository:** ```bash git clone https://github.com/lmiranda/gitea-mcp-remote.git cd gitea-mcp-remote ``` 2. **Create configuration:** ```bash cp .env.docker.example .env nano .env # Edit with your values ``` Required configuration: ```bash GITEA_URL=https://gitea.example.com GITEA_TOKEN=your_gitea_api_token GITEA_OWNER=your_username_or_org GITEA_REPO=your_default_repo AUTH_TOKEN=your_bearer_token # Recommended ``` 3. **Start the service:** ```bash docker-compose up -d ``` 4. **Verify deployment:** ```bash curl http://localhost:8000/health ``` ### Production Configuration For production, use a more robust `docker-compose.yml`: ```yaml version: '3.8' services: gitea-mcp-wrapper: build: context: . dockerfile: Dockerfile image: gitea-mcp-wrapper:latest container_name: gitea-mcp-wrapper restart: always ports: - "127.0.0.1:8000:8000" # Bind to localhost only environment: - GITEA_URL=${GITEA_URL} - GITEA_TOKEN=${GITEA_TOKEN} - GITEA_OWNER=${GITEA_OWNER} - GITEA_REPO=${GITEA_REPO} - HTTP_HOST=0.0.0.0 - HTTP_PORT=8000 - AUTH_TOKEN=${AUTH_TOKEN} healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"] interval: 30s timeout: 3s retries: 3 start_period: 5s logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: - gitea-mcp-network networks: gitea-mcp-network: driver: bridge ``` ### Docker Build Options **Build the image:** ```bash docker build -t gitea-mcp-wrapper:latest . ``` **Build with specific Python version:** ```bash docker build --build-arg PYTHON_VERSION=3.11 -t gitea-mcp-wrapper:latest . ``` **Tag for registry:** ```bash docker tag gitea-mcp-wrapper:latest registry.example.com/gitea-mcp-wrapper:latest docker push registry.example.com/gitea-mcp-wrapper:latest ``` ## Security Best Practices ### 1. Use Authentication Always set `AUTH_TOKEN` in production: ```bash # Generate a secure token openssl rand -base64 32 # Add to .env AUTH_TOKEN= ``` ### 2. Use HTTPS Never expose the wrapper directly to the internet without HTTPS. Use a reverse proxy (see below). ### 3. Network Isolation - Bind to localhost only (`127.0.0.1`) if using a reverse proxy - Use Docker networks to isolate services - Consider VPN or private networking for access ### 4. Secrets Management Don't use `.env` files in production. Use Docker secrets, Kubernetes secrets, or a secrets manager: **Docker Secrets Example:** ```yaml services: gitea-mcp-wrapper: secrets: - gitea_token - auth_token environment: - GITEA_TOKEN_FILE=/run/secrets/gitea_token - AUTH_TOKEN_FILE=/run/secrets/auth_token secrets: gitea_token: external: true auth_token: external: true ``` ### 5. Regular Updates - Rotate Gitea API token regularly - Rotate AUTH_TOKEN regularly - Keep Docker base image updated - Update dependencies: `pip install --upgrade -r requirements.txt` ### 6. Minimal Permissions Grant the Gitea API token only the minimum required permissions: - Repository read/write - Issue management - Label management - Milestone management Avoid granting admin or organization-level permissions. ## Monitoring and Health Checks ### Health Check Endpoints The wrapper provides three health check endpoints: ```bash GET /health GET /healthz GET /ping ``` All return `{"status": "healthy"}` with HTTP 200 when the server is operational. ### Docker Health Checks Docker automatically monitors the health check and can restart if unhealthy: ```yaml healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 3s retries: 3 start_period: 5s ``` ### Monitoring Integration **Prometheus metrics:** (Not yet implemented, but can be added) **Log monitoring:** ```bash # View logs docker-compose logs -f gitea-mcp-wrapper # JSON structured logs docker logs gitea-mcp-wrapper --tail 100 ``` **Uptime monitoring:** Use tools like UptimeRobot, Pingdom, or Datadog to monitor `/health` endpoint. ## Reverse Proxy Configuration ### Nginx ```nginx server { listen 443 ssl http2; server_name mcp.example.com; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Pass through Authorization header proxy_set_header Authorization $http_authorization; proxy_pass_header Authorization; # WebSocket support (if needed in future) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Health check endpoint (optional, can bypass auth) location /health { proxy_pass http://127.0.0.1:8000/health; access_log off; } } ``` ### Caddy ```caddyfile mcp.example.com { reverse_proxy localhost:8000 { # Pass through Authorization header header_up Authorization {>Authorization} } # Optional: Rate limiting rate_limit { zone mcp_zone rate 100r/m } } ``` ### Traefik ```yaml # docker-compose.yml services: gitea-mcp-wrapper: labels: - "traefik.enable=true" - "traefik.http.routers.mcp.rule=Host(`mcp.example.com`)" - "traefik.http.routers.mcp.entrypoints=websecure" - "traefik.http.routers.mcp.tls.certresolver=letsencrypt" - "traefik.http.services.mcp.loadbalancer.server.port=8000" ``` ## Cloud Deployment ### AWS EC2 1. **Launch EC2 instance:** - Amazon Linux 2 or Ubuntu 22.04 - t3.micro or larger - Security group: Allow port 443 (HTTPS) 2. **Install Docker:** ```bash sudo yum update -y sudo yum install -y docker sudo service docker start sudo usermod -aG docker ec2-user ``` 3. **Deploy wrapper:** ```bash git clone https://github.com/lmiranda/gitea-mcp-remote.git cd gitea-mcp-remote cp .env.docker.example .env nano .env # Configure docker-compose up -d ``` 4. **Configure Nginx or ALB for HTTPS** ### AWS ECS (Fargate) 1. **Create task definition:** ```json { "family": "gitea-mcp-wrapper", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "containerDefinitions": [ { "name": "gitea-mcp-wrapper", "image": "your-ecr-repo/gitea-mcp-wrapper:latest", "portMappings": [ { "containerPort": 8000, "protocol": "tcp" } ], "environment": [ {"name": "GITEA_URL", "value": "https://gitea.example.com"}, {"name": "HTTP_HOST", "value": "0.0.0.0"}, {"name": "HTTP_PORT", "value": "8000"} ], "secrets": [ { "name": "GITEA_TOKEN", "valueFrom": "arn:aws:secretsmanager:region:account:secret:gitea-token" }, { "name": "AUTH_TOKEN", "valueFrom": "arn:aws:secretsmanager:region:account:secret:auth-token" } ], "healthCheck": { "command": ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 10 } } ] } ``` 2. **Create ECS service with ALB** ### Google Cloud Run 1. **Build and push image:** ```bash gcloud builds submit --tag gcr.io/PROJECT_ID/gitea-mcp-wrapper ``` 2. **Deploy:** ```bash gcloud run deploy gitea-mcp-wrapper \ --image gcr.io/PROJECT_ID/gitea-mcp-wrapper \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --set-env-vars GITEA_URL=https://gitea.example.com \ --set-secrets GITEA_TOKEN=gitea-token:latest,AUTH_TOKEN=auth-token:latest \ --port 8000 ``` ### Azure Container Instances ```bash az container create \ --resource-group myResourceGroup \ --name gitea-mcp-wrapper \ --image your-registry/gitea-mcp-wrapper:latest \ --ports 8000 \ --dns-name-label gitea-mcp \ --environment-variables \ GITEA_URL=https://gitea.example.com \ HTTP_HOST=0.0.0.0 \ HTTP_PORT=8000 \ --secure-environment-variables \ GITEA_TOKEN=your_token \ AUTH_TOKEN=your_auth_token ``` ## Kubernetes Deployment ### Deployment Manifest ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: gitea-mcp-wrapper namespace: default spec: replicas: 2 selector: matchLabels: app: gitea-mcp-wrapper template: metadata: labels: app: gitea-mcp-wrapper spec: containers: - name: gitea-mcp-wrapper image: your-registry/gitea-mcp-wrapper:latest ports: - containerPort: 8000 env: - name: GITEA_URL value: "https://gitea.example.com" - name: HTTP_HOST value: "0.0.0.0" - name: HTTP_PORT value: "8000" - name: GITEA_TOKEN valueFrom: secretKeyRef: name: gitea-secrets key: token - name: AUTH_TOKEN valueFrom: secretKeyRef: name: gitea-secrets key: auth-token livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 10 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m" --- apiVersion: v1 kind: Service metadata: name: gitea-mcp-wrapper namespace: default spec: selector: app: gitea-mcp-wrapper ports: - protocol: TCP port: 80 targetPort: 8000 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: gitea-mcp-wrapper namespace: default annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: tls: - hosts: - mcp.example.com secretName: mcp-tls rules: - host: mcp.example.com http: paths: - path: / pathType: Prefix backend: service: name: gitea-mcp-wrapper port: number: 80 ``` ### Secrets Management ```bash # Create secret kubectl create secret generic gitea-secrets \ --from-literal=token=your_gitea_token \ --from-literal=auth-token=your_auth_token \ --namespace=default ``` ## Troubleshooting ### Container Won't Start ```bash # Check logs docker-compose logs gitea-mcp-wrapper # Check container status docker-compose ps # Rebuild image docker-compose build --no-cache docker-compose up -d ``` ### Health Check Failing ```bash # Test health endpoint directly docker exec gitea-mcp-wrapper curl http://localhost:8000/health # Check if server is listening docker exec gitea-mcp-wrapper netstat -tlnp ``` ### Cannot Reach Gitea from Container ```bash # Test connectivity docker exec gitea-mcp-wrapper curl -v https://gitea.example.com # Check DNS resolution docker exec gitea-mcp-wrapper nslookup gitea.example.com # For docker-compose, ensure network allows egress ``` ### High Memory Usage ```bash # Check container stats docker stats gitea-mcp-wrapper # Adjust resource limits in docker-compose.yml deploy: resources: limits: memory: 256M ``` ### Authentication Failures ```bash # Verify AUTH_TOKEN is set docker exec gitea-mcp-wrapper printenv AUTH_TOKEN # Test with curl curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8000/tools/list # Check logs for auth failures docker-compose logs gitea-mcp-wrapper | grep -i auth ``` ## Scaling Considerations ### Horizontal Scaling The wrapper is stateless and can be scaled horizontally: ```yaml # docker-compose.yml services: gitea-mcp-wrapper: deploy: replicas: 3 ``` Or in Kubernetes: ```bash kubectl scale deployment gitea-mcp-wrapper --replicas=5 ``` ### Load Balancing Use a load balancer to distribute traffic: - Docker Swarm: Built-in load balancing - Kubernetes: Service with multiple pods - Cloud: AWS ALB, GCP Load Balancer, Azure Load Balancer ### Caching Consider caching responses to reduce Gitea API load: - Add Redis or Memcached - Cache tool list responses - Cache frequently accessed issues/labels ### Rate Limiting Implement rate limiting at reverse proxy level to prevent API abuse: **Nginx:** ```nginx limit_req_zone $binary_remote_addr zone=mcp:10m rate=10r/s; limit_req zone=mcp burst=20 nodelay; ``` **Caddy:** ```caddyfile rate_limit { rate 100r/m } ``` ## Backup and Disaster Recovery ### Configuration Backup ```bash # Backup .env file cp .env .env.backup.$(date +%Y%m%d) # Backup docker-compose.yml cp docker-compose.yml docker-compose.yml.backup.$(date +%Y%m%d) ``` ### Image Backup ```bash # Save Docker image docker save gitea-mcp-wrapper:latest | gzip > gitea-mcp-wrapper-backup.tar.gz # Load Docker image docker load < gitea-mcp-wrapper-backup.tar.gz ``` ### Recovery Plan 1. Restore configuration files 2. Rebuild or load Docker image 3. Start services: `docker-compose up -d` 4. Verify health: `curl http://localhost:8000/health` 5. Test authentication and tool access ## Production Checklist - [ ] HTTPS configured via reverse proxy - [ ] `AUTH_TOKEN` set and secure - [ ] Secrets stored in secrets manager (not `.env`) - [ ] Health checks configured - [ ] Monitoring and alerting set up - [ ] Logs aggregated and retained - [ ] Firewall rules configured - [ ] Rate limiting enabled - [ ] Resource limits set - [ ] Backup strategy in place - [ ] Disaster recovery plan documented - [ ] Security updates scheduled - [ ] Token rotation process defined --- **For questions or issues, please open an issue on the [GitHub repository](https://github.com/lmiranda/gitea-mcp-remote/issues).**