用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/lebiraja/skills4agents --skill agent-module-docker-ml-deployment命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | agent-module-docker-ml-deployment |
| description | Module: Docker + ML Service Deployment Standard |
agent.module.docker-ml-deployment1.0.0productionUse this module when deploying any ML-backed service using Docker Compose or a similar container orchestration layer, with a REST API serving model inference.
Apply when:
Do not apply when:
Multi-Service Docker Compose with ML Artifact Volume Mountfrontend (static/nginx), backend (ML API), cache (Redis), model-trainer (one-off job)..pkl, .pt, .onnx, .safetensors) are mounted as read-only volumes into the inference container — never baked into the image.profile (--profile training) so it is never started by accident on docker compose up.condition: service_healthy to prevent startup races.max-size: 10m, max-file: 5)..env files; no secrets are hardcoded or committed.┌─────────────────────────────────────────────────┐
│ Cloud VM / Server │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ frontend │──▶│ backend │─▶│ cache │ │
│ │ (nginx) │ │ (ML API) │ │ (Redis) │ │
│ │ :80/443 │ │ :8000 │ │ :6379 │ │
│ └──────────┘ └──────┬───────┘ └──────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ ML Models │ │
│ │ /app/models/│ │
│ │ (vol. mount)│ │
│ └─────────────┘ │
└─────────────────────────────────────────────────┘
Service roles:
| Service | Image Base | Port | Purpose |
|---|---|---|---|
backend | python:3.11-slim (two-stage) | 8000 | ML inference REST API |
frontend | node:20 → nginx:alpine (two-stage) | 80 / 443 | Static UI + API proxy |
cache | redis:7-alpine | 6379 (internal) | Prediction result cache |
model-trainer | Same as backend | — | One-off training job (profile-gated) |
backend/, frontend/, docker-compose.yml, backend/.env.example.backend/.env from .env.example:
cp backend/.env.example backend/.env
SECRET_KEY:
python3 -c "import secrets; print(secrets.token_hex(32))"
ENVIRONMENT=production, LOG_LEVEL=INFO, and CORS_ORIGINS with your actual domain in backend/.env.backend/.env is listed in .gitignore:
grep "\.env" .gitignore
If missing: echo "backend/.env" >> .gitignore.env contract for the project and commit .env.example with all keys present (values blank or placeholder).Minimum .env contract (adapt per project):
# Application
ENVIRONMENT=production
LOG_LEVEL=INFO
SECRET_KEY=<generate with secrets.token_hex(32)>
# ML model paths (inside container — match volume mount target)
MODEL_PATH=/app/models/<primary_model>.pkl
SCALER_PATH=/app/models/scaler.pkl
PREPROCESSOR_PATH=/app/models/preprocessor.pkl
MODEL_VERSION=1.0.0
# Cache
REDIS_URL=redis://cache:6379/0
CACHE_ENABLED=true
CACHE_TTL=300
# CORS — always include production domain
CORS_ORIGINS=["https://yourdomain.com"]
# Rate limiting
API_RATE_LIMIT=100/hour
Exit criteria:
backend/.env exists and is not tracked by git..env.example.SECRET_KEY is non-default and >= 32 bytes.backend/models/:
ls -lh backend/models/
# Option A — Docker (recommended for reproducibility)
docker compose --profile training run --rm model-trainer
# Option B — local Python
cd backend && pip install -r requirements.txt && python train_model.py
ls backend/models/*.pkl # or *.pt, *.onnx, etc.
VERSION="v1.0.0"
DATE=$(date +%Y%m%d)
for f in backend/models/<primary>.pkl backend/models/scaler.pkl backend/models/preprocessor.pkl; do
cp "$f" "${f%.pkl}_${VERSION}_${DATE}.pkl"
done
Model file naming convention:
<model_name>_v<MAJOR>.<MINOR>.<PATCH>_<YYYYMMDD>.pkl
# Example: gradient_boosting_model_v1.2.0_20260411.pkl
Exit criteria:
backend/models/.docker compose build --no-cache
docker compose up -d
docker compose ps
curl http://localhost:8000/api/v1/health
# Expected: {"status": "healthy", "model_loaded": true, "version": "1.0.0"}
curl -X POST http://localhost:8000/api/v1/predict \
-H "Content-Type: application/json" \
-d '<minimal_valid_payload_for_your_model>'
curl -s -o /dev/null -w "%{http_code}" http://localhost:80
# Expected: 200
docker compose exec cache redis-cli ping
# Expected: PONG
Key Docker commands reference:
# Rebuild after code changes
docker compose build --no-cache && docker compose up -d
# Follow logs
docker compose logs -f backend
docker compose logs -f frontend
# Enter a container for debugging
docker compose exec backend bash
# Stop and clean up
docker compose down
docker compose down -v # also wipes cache volumes
Exit criteria:
docker compose ps.model_loaded: true.Enforce two-stage builds for both backend and frontend.
Backend two-stage pattern:
# Stage 1: install Python dependencies
FROM python:3.11-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: production image
FROM python:3.11-slim AS production
# Install runtime system libs (e.g., OpenBLAS/LAPACK for numpy/scikit-learn)
RUN apt-get update && apt-get install -y --no-install-recommends \
libopenblas-dev liblapack-dev && \
rm -rf /var/lib/apt/lists/*
# Copy installed packages from builder
COPY --from=builder /root/.local /root/.local
# Copy application code (no model artifacts — they come via volume mount)
COPY app/ /app/app/
WORKDIR /app
# Run as non-root
RUN useradd -m appuser && chown -R appuser /app
USER appuser
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Frontend two-stage pattern:
# Stage 1: build React/Vue/etc. static assets
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: serve with nginx
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
nginx.conf API proxy pattern:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend container
location /api/ {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
}
}
Exit criteria:
Instance sizing (EC2 reference):
| Use Case | Type | vCPU | RAM | Est. Cost |
|---|---|---|---|---|
| Dev / Test | t3.small | 2 | 2 GB | ~$15/mo |
| Production | t3.medium | 2 | 4 GB | ~$30/mo |
| High Traffic | t3.large | 2 | 8 GB | ~$60/mo |
Security group / firewall rules:
| Port | Protocol | Source | Purpose |
|---|---|---|---|
| 22 | TCP | Your IP only | SSH |
| 80 | TCP | 0.0.0.0/0 | HTTP |
| 443 | TCP | 0.0.0.0/0 | HTTPS |
| 8000 | TCP | 0.0.0.0/0 | Direct API (optional, dev only) |
Docker installation (Ubuntu):
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
usermod -aG docker && newgrp docker
Exit criteria:
docker compose ps.model_loaded: true from the cloud VM.Add GitHub repository secrets:
| Secret | Value | Source |
|---|---|---|
EC2_SSH_KEY | Full .pem key content (including headers) | cat your-key.pem |
EC2_HOST | Public IP or domain of server | Cloud console |
EC2_USERNAME | OS user (e.g., ubuntu) | VM configuration |
Create .github/workflows/deploy.yml:
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script:
Exit criteria:
main triggers automatic deployment.sudo apt-get install -y certbot python3-certbot-nginx
docker compose stop frontend
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
docker compose start frontend
frontend/nginx.conf:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# ... existing location blocks
}
docker-compose.yml:
frontend:
volumes:
- /etc/letsencrypt:/etc/letsencrypt:ro
ports:
- "80:80"
- "443:443"
CORS_ORIGINS in backend/.env:
CORS_ORIGINS=["https://yourdomain.com","https://www.yourdomain.com"]
sudo certbot renew --dry-run
sudo certbot renew --post-hook "docker compose -f ~/app/docker-compose.yml restart frontend"
sudo systemctl status certbot.timer
Exit criteria:
https://yourdomain.com returns HTTP 200.This is the most critical operational procedure. Follow exactly.
Standard model update workflow:
1. Train new model (Docker or local)
2. Record metrics — confirm improvement or acceptability
3. Backup current production artifacts with version suffix
4. Replace model files in backend/models/
5. Commit to git (or upload to S3)
6. Push to main → CI/CD deploys
7. Verify health endpoint after deploy
8. Monitor error logs for 15 minutes
Step-by-step:
# Step 1: Train
docker compose --profile training run --rm model-trainer
# Note: record printed metrics (RMSE, R², accuracy, etc.)
# Step 2: Backup current production model
VERSION="v1.0.0"
DATE=$(date +%Y%m%d)
cd backend/models
cp <primary_model>.pkl <primary_model>_${VERSION}_${DATE}.pkl
cp scaler.pkl scaler_${VERSION}_${DATE}.pkl
cp preprocessor.pkl preprocessor_${VERSION}_${DATE}.pkl
# Step 3: Commit new model
cd /path/to/repo
git add backend/models/<primary_model>.pkl backend/models/scaler.pkl backend/models/preprocessor.pkl
git commit -m "model: update to v1.1.0 — R2=0.951, RMSE=2.12"
git push origin main
# CI/CD deploys automatically
# Step 4: Verify post-deploy
curl https://yourdomain.com/api/v1/health
curl -X POST https://yourdomain.com/api/v1/predict \
-H "Content-Type: application/json" \
-d '<minimal_valid_payload>'
Rollback procedure (if deploy fails):
# On the server
cd ~/app/backend/models
cp <primary_model>_v1.0.0_<date>.pkl <primary_model>.pkl
cp scaler_v1.0.0_<date>.pkl scaler.pkl
cp preprocessor_v1.0.0_<date>.pkl preprocessor.pkl
# Restart backend to reload model
docker compose restart backend
# Verify
curl http://localhost:8000/api/v1/health
Model storage strategy by size:
| Model Size | Storage Strategy |
|---|---|
| < 50 MB | Commit to git alongside code |
| 50 MB – 2 GB | Store in S3; download on deploy via CI/CD step |
| > 2 GB | Git LFS (git lfs track "*.pkl") or S3 + streaming load |
Exit criteria:
/api/v1/health response.| Decision Area | Preferred Option | Alternative | Selection Rule |
|---|---|---|---|
| Model artifact delivery | Volume mount (read-only) | Baked into Docker image | Volume mount: model updates don't require image rebuild. Bake in only if immutability is required. |
| Model storage (< 50 MB) | Commit to git | S3 | Git is simpler; use S3 only when files exceed GitHub LFS limits or build times become unacceptable. |
| Scaling strategy | Multiple Uvicorn workers (same container) | Multiple container replicas | Workers for I/O-bound routes; replicas only when CPU/memory is the bottleneck. |
| Cache strategy | Redis with TTL per prediction input hash | In-memory dict | Redis survives container restarts; use in-memory dict only for stateless/ephemeral deployments. |
| CI/CD trigger | Push to main branch | Manual SSH deploy | Automated on push reduces operator error; use manual only for regulated environments with change gates. |
| SSL termination | Certbot + nginx (in container) | Cloud load balancer TLS | LB TLS preferred for production HA; nginx TLS acceptable for single-VM deployments. |
| Backend API framework | FastAPI | Flask, Django REST | FastAPI for async ML workloads and OpenAPI auto-docs; Flask if team preference or existing codebase. |
| Zero-downtime model reload | Restart with volume swap | Hot-reload admin endpoint | Volume swap + restart is simpler and safe; hot-reload endpoint adds surface area but avoids brief downtime. |
GET /api/v1/health returns {"status": "healthy", "model_loaded": true} after every deployment.<title> tag or a known HTML landmark.PONG from redis-cli ping.healthy in docker compose ps.backend/.env and confirm compose fails with a readable error (not a null-key panic).docker compose stop cache) and confirm API continues serving (degraded, no cache) without crashing.restart: unless-stopped).grep -r "SECRET_KEY" .git returns no results (secret never committed).docker compose exec backend whoami returns non-root user (e.g., appuser).docker compose config | grep 6379 confirms Redis port is NOT mapped to 0.0.0.0 (only internal).curl http://yourdomain.com/api/v1/health redirects to HTTPS (301).<= 200 ms at p95<= 500 ms at p95 for tabular ML models; <= 2 s for deep learning models>= 99.5% monthly for single-VM production deploymentsdocker compose restart backend: <= 30 s for models < 500 MB<= 5 minutes<= 10 minutes>= 80% under steady-state traffic<= 256 MB for prediction cache (enforce with maxmemory policy)>= 80% triggers prune (docker system prune -f)Risk: Model artifact files missing or corrupted at startup.
@app.on_event("startup")) loads and validates all model files; health endpoint immediately reports model_loaded: false; container restarts on health check failure.Risk: .env file committed to git, leaking secrets.
.env in .gitignore; pre-commit hook or CI secret scan (e.g., gitleaks, truffleHog); audit git log --all -- backend/.env periodically.Risk: Deploying an untested model that degrades prediction quality.
Risk: Disk full on VM halts containers.
docker system prune -f via cron; df -h / alert at 80%; EBS volume sized with 50% headroom for model backups and logs.Risk: CI/CD deploys broken code and health check not caught.
curl -f http://localhost:8000/api/v1/health || exit 1 at end of deploy script; CI job fails and sends alert; rollback is manual but documented.Risk: Redis exposed publicly and accessed by external actors.
127.0.0.1 (or not mapped at all); security group blocks 6379/6380 from internet; confirm with docker compose config | grep "6379".Risk: Model version mismatch between scaler/preprocessor and primary model.
MODEL_VERSION env var ties to git tag; rollback restores all three files together.Risk: SSL certificate expires and site goes down.
certbot.timer systemd service enabled; certbot renew --post-hook restarts nginx; expiry alert via uptime monitor (UptimeRobot, BetterUptime, Pingdom).backend/.env created from .env.example with real SECRET_KEY (not default).backend/.env confirmed absent from git history.CORS_ORIGINS set to include the production domain.backend/models/.docker compose build --no-cache).docker compose ps.model_loaded: true.PONG confirmed.main triggers deploy).docker compose logs backend
# Check for: missing model file, missing .env, Redis not ready
ls backend/models/<primary>.pkl
ls backend/.env
docker compose logs cache
# Fix:
docker compose down && docker compose up -d --build
model_loaded: false from health endpointdocker compose exec backend ls -la /app/models/
docker compose config | grep -A5 volumes
docker compose logs backend | grep -i "error\|model\|pkl\|onnx"
docker compose logs backend | grep -A10 "500\|ERROR\|Exception"
# Common causes: input shape mismatch, scaler/model version mismatch, memory exhaustion
docker stats --no-stream
docker compose exec cache redis-cli ping
grep REDIS_URL backend/.env
# REDIS_URL must use Docker service name: redis://cache:6379/0
docker compose restart cache
df -h /
docker system prune -f
docker compose exec backend find /app/logs -name "*.log.*" -delete
du -sh backend/models/* backend/data/* 2>/dev/null | sort -rh | head -20
# Check GitHub Actions logs: Repository → Actions → (failed run)
# Common causes:
# 1. EC2_SSH_KEY missing header/footer lines
# 2. Security group blocks port 22 from GitHub Actions IPs
# 3. Disk full on VM: docker system prune -f on EC2, re-run
sudo certbot renew --dry-run
sudo systemctl status certbot.timer
# If timer is inactive: sudo systemctl enable --now certbot.timer
This module is reusable across all Docker + ML service deployments. Adapt only:
MODEL_PATH, SCALER_PATH, PREPROCESSOR_PATH env keys).libopenblas-dev for scikit-learn; libgomp1 for XGBoost; CUDA base image for GPU inference).train_model.py → your actual script name).model_loaded boolean consistent).appleboy/ssh-action with your preferred action or deployment service).Initial deployment:
git clone <repo-url> ~/app
cd ~/app
cp backend/.env.example backend/.env
# Edit backend/.env: set SECRET_KEY, CORS_ORIGINS with real domain
nano backend/.env
# If model files not in repo, train them first
docker compose --profile training run --rm model-trainer
# Build and start
docker compose build --no-cache
docker compose up -d
docker compose ps
curl http://localhost:8000/api/v1/health
If model files are stored in S3 (not git), add download step before build:
- name: Download models from S3
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
aws s3 cp s3://<your-bucket>/models/ ~/app/backend/models/ --recursive
Verify the health endpoint is called at the end of every deploy job; use || exit 1 to fail the pipeline if the API is unhealthy after deploy.
docker system prune -fmax-size: "10m", max-file: "5" in compose logging block).