Khi triển khai hệ thống kiểm tra chất lượng sản phẩm bằng thị giác máy tính cho một nhà máy sản xuất linh kiện điện tử tại Bình Dương, tôi đã đối mặt với một cơn ác mộng: hệ thống AI phát hiện lỗi bề mặt chip liên tục báo ConnectionError: timeout trong giờ cao điểm sản xuất, dẫn đến dây chuyền lắp ráp phải dừng lại 12 lần trong một ngày. Đó là khoảnh khắc tôi quyết định xây dựng một API Gateway chuyên dụng với HolySheep AI — giải pháp giúp giảm 94% downtime và tiết kiệm 85% chi phí so với việc dùng trực tiếp OpenAI API.

Tại sao cần API Gateway riêng cho Industrial Vision?

Trong môi trường sản xuất công nghiệp, các yêu cầu khác biệt hoàn toàn so với ứng dụng web thông thường:

Kiến trúc HolySheep Vision Gateway

Sơ đồ luồng xử lý

┌─────────────────────────────────────────────────────────────────────────────┐
│                         HOLYSHEEP VISION GATEWAY                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────┐    ┌──────────────┐    ┌─────────────────────────────────┐   │
│  │ Camera   │───▶│ Preprocessor │───▶│ Multi-stage Verification Queue │   │
│  │ 30fps    │    │ (resize/zip) │    │                                 │   │
│  └──────────┘    └──────────────┘    │  Stage 1: Gemini 2.5 Flash     │   │
│                                       │  Stage 2: DeepSeek V3.2       │   │
│                                       │  Stage 3: Human Review (flag)  │   │
│                                       └─────────────────────────────────┘   │
│                                              │                              │
│                                              ▼                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │                      Retry & Rate Limiter                             │   │
│  │  • Circuit Breaker: 5 failures → 30s cooldown                        │   │
│  │  • Rate Limit: 500 req/min → exponential backoff                     │   │
│  │  • Fallback: Cached results for known patterns                        │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                              │                              │
│                                              ▼                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │                      SLA Monitoring Dashboard                        │   │
│  │  • Latency P50/P95/P99 (< 50ms với HolySheep)                        │   │
│  │  • Error rate tracking                                                │   │
│  │  • Cost per 1000 inspections                                          │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                              │                              │
│                                              ▼                              │
│                                      ┌──────────────┐                      │
│                                      │   Results    │                      │
│                                      │  (PASS/FAIL) │                      │
│                                      └──────────────┘                      │
└─────────────────────────────────────────────────────────────────────────────┘

Cài đặt và cấu hình

1. Khởi tạo dự án

npm install @holysheep/vision-gateway \
              @holysheep/sdk \
              ioredis \
              prom-client \
              winston

Tạo cấu trúc thư mục

mkdir -p vision-gateway/src/{handlers,middleware,services,models} cd vision-gateway

2. Cấu hình Environment Variables

# .env.production
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1

Redis cho caching và rate limiting

REDIS_URL=redis://localhost:6379

Cấu hình rate limit

RATE_LIMIT_REQUESTS=500 RATE_LIMIT_WINDOW_MS=60000

Circuit breaker

CIRCUIT_BREAKER_THRESHOLD=5 CIRCUIT_BREAKER_TIMEOUT_MS=30000

Monitoring

PROMETHEUS_PORT=9090 LOG_LEVEL=info

Triển khai Gateway Service

Core Gateway với Retry Logic

// src/services/HolySheepVisionGateway.js
const { HolySheep } = require('@holysheep/sdk');
const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.File('logs/gateway.log'),
        new winston.transports.Console()
    ]
});

class HolySheepVisionGateway {
    constructor(apiKey, options = {}) {
        this.client = new HolySheep({
            apiKey: apiKey,
            baseURL: 'https://api.holysheep.ai/v1'  // Luôn dùng HolySheep endpoint
        });
        
        // Retry configuration
        this.maxRetries = options.maxRetries || 3;
        this.retryDelay = options.retryDelay || 1000;
        this.backoffMultiplier = options.backoffMultiplier || 2;
        
        // Circuit breaker state
        this.failureCount = 0;
        this.lastFailureTime = null;
        this.circuitOpen = false;
        
        // Rate limiting state
        this.requestCounts = new Map();
        
        this.logger = logger;
    }

    async analyzeWithRetry(imageBuffer, options = {}) {
        const { maxRetries = this.maxRetries } = options;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                // Check circuit breaker
                if (this.circuitOpen) {
                    const timeSinceFailure = Date.now() - this.lastFailureTime;
                    if (timeSinceFailure < 30000) {
                        throw new Error('Circuit breaker is OPEN - retry later');
                    }
                    this.circuitOpen = false;
                    this.failureCount = 0;
                }
                
                // Kiểm tra rate limit
                await this.checkRateLimit();
                
                // Gọi HolySheep API với Gemini 2.5 Flash
                const startTime = Date.now();
                
                const response = await this.client.chat.completions.create({
                    model: 'gemini-2.5-flash',  // $2.50/MTok - tối ưu chi phí
                    messages: [
                        {
                            role: 'user',
                            content: [
                                {
                                    type: 'image_url',
                                    image_url: {
                                        url: data:image/jpeg;base64,${imageBuffer.toString('base64')}
                                    }
                                },
                                {
                                    type: 'text',
                                    text: options.prompt || 'Phân tích ảnh này và xác định các khuyết tật bề mặt (vết xước, bụi, biến dạng). Trả về JSON với format: {"defects": [], "confidence": 0.95, "passed": true}'
                                }
                            ]
                        }
                    ],
                    max_tokens: 1024,
                    temperature: 0.1  // Độ chính xác cao cho QC
                });
                
                const latency = Date.now() - startTime;
                this.logger.info('HolySheep API call successful', {
                    latency_ms: latency,
                    attempt,
                    model: 'gemini-2.5-flash',
                    cost_estimate: this.estimateCost(response)
                });
                
                // Reset failure count on success
                this.failureCount = 0;
                
                return {
                    success: true,
                    data: JSON.parse(response.choices[0].message.content),
                    latency_ms: latency,
                    model: 'gemini-2.5-flash',
                    tokens_used: response.usage.total_tokens
                };
                
            } catch (error) {
                this.logger.error(Attempt ${attempt} failed, {
                    error: error.message,
                    code: error.code,
                    status: error.status
                });
                
                this.failureCount++;
                this.lastFailureTime = Date.now();
                
                // Open circuit breaker after threshold
                if (this.failureCount >= 5) {
                    this.circuitOpen = true;
                    this.logger.warn('Circuit breaker OPENED', {
                        failureCount: this.failureCount
                    });
                }
                
                // Retry with exponential backoff
                if (attempt < maxRetries) {
                    const delay = this.retryDelay * Math.pow(this.backoffMultiplier, attempt - 1);
                    this.logger.info(Retrying in ${delay}ms...);
                    await this.sleep(delay);
                } else {
                    // Final fallback - try DeepSeek for comparison
                    return this.fallbackAnalysis(imageBuffer, error);
                }
            }
        }
    }

    async fallbackAnalysis(imageBuffer, originalError) {
        this.logger.warn('Using fallback model: DeepSeek V3.2', {
            originalError: originalError.message
        });
        
        try {
            const response = await this.client.chat.completions.create({
                model: 'deepseek-v3.2',  // $0.42/MTok - rẻ nhất
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'image_url',
                                image_url: {
                                    url: data:image/jpeg;base64,${imageBuffer.toString('base64')}
                                }
                            },
                            {
                                type: 'text',
                                text: 'Quick defect check: return JSON with "passed" boolean and "defects" array'
                            }
                        ]
                    }
                ],
                max_tokens: 256
            });
            
            return {
                success: true,
                data: JSON.parse(response.choices[0].message.content),
                latency_ms: 0,
                model: 'deepseek-v3.2',
                fallback: true
            };
            
        } catch (fallbackError) {
            throw new Error(All models failed. Last error: ${fallbackError.message});
        }
    }

    async checkRateLimit() {
        const now = Date.now();
        const key = 'rate_limit';
        
        let count = this.requestCounts.get(key) || { count: 0, resetTime: now + 60000 };
        
        if (now > count.resetTime) {
            count = { count: 0, resetTime: now + 60000 };
        }
        
        if (count.count >= 500) {
            throw new Error('Rate limit exceeded (500 req/min). Retry after cooldown.');
        }
        
        count.count++;
        this.requestCounts.set(key, count);
    }

    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    estimateCost(response) {
        const pricing = {
            'gemini-2.5-flash': 2.50,  // $2.50/MTok
            'deepseek-v3.2': 0.42      // $0.42/MTok
        };
        const pricePerMillion = pricing[response.model] || 2.50;
        return (response.usage.total_tokens / 1000000) * pricePerMillion;
    }
}

module.exports = { HolySheepVisionGateway };

SLA Monitoring Dashboard

// src/services/SLAMonitor.js
const client = require('prom-client');

// Khởi tạo Prometheus metrics
const register = new client.Registry();
client.collectDefaultMetrics({ register });

// Custom metrics cho Vision Gateway
const httpRequestDuration = new client.Histogram({
    name: 'vision_gateway_request_duration_seconds',
    help: 'Duration of HTTP requests in seconds',
    labelNames: ['method', 'route', 'status_code'],
    buckets: [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5]
});

const apiLatency = new client.Gauge({
    name: 'holysheep_api_latency_ms',
    help: 'HolySheep API response latency in milliseconds',
    labelNames: ['model', 'endpoint']
});

const errorRate = new client.Counter({
    name: 'vision_gateway_errors_total',
    help: 'Total number of errors',
    labelNames: ['type', 'model']
});

const costAccumulator = new client.Counter({
    name: 'vision_gateway_cost_usd',
    help: 'Total cost in USD',
    labelNames: ['model']
});

const throughput = new client.Gauge({
    name: 'vision_gateway_throughput_rpm',
    help: 'Requests per minute'
});

register.registerMetric(httpRequestDuration);
register.registerMetric(apiLatency);
register.registerMetric(errorRate);
register.registerMetric(costAccumulator);
register.registerMetric(throughput);

class SLAMonitor {
    constructor(gateway) {
        this.gateway = gateway;
        this.slaTargets = {
            latency_p50: 50,      // ms
            latency_p95: 150,     // ms
            latency_p99: 300,    // ms
            error_rate: 0.01,     // 1%
            uptime: 0.999         // 99.9%
        };
        this.metricsHistory = [];
    }

    recordRequest(result) {
        const { latency_ms, model, tokens_used, success } = result;
        
        // Record latency
        apiLatency.labels(model, 'vision').set(latency_ms);
        
        // Record cost
        const cost = this.calculateCost(tokens_used, model);
        costAccumulator.labels(model).inc(cost);
        
        // Record error if failed
        if (!success) {
            errorRate.labels('api_error', model).inc();
        }
        
        // Update throughput
        this.updateThroughput();
        
        // Store for SLA calculation
        this.metricsHistory.push({
            timestamp: Date.now(),
            latency_ms,
            model,
            success
        });
        
        // Keep only last 1000 records
        if (this.metricsHistory.length > 1000) {
            this.metricsHistory.shift();
        }
    }

    calculateCost(tokens, model) {
        const pricing = {
            'gemini-2.5-flash': 2.50,
            'deepseek-v3.2': 0.42,
            'gpt-4.1': 8.00,
            'claude-sonnet-4.5': 15.00
        };
        return (tokens / 1000000) * (pricing[model] || 2.50);
    }

    updateThroughput() {
        const now = Date.now();
        const oneMinuteAgo = now - 60000;
        const recentRequests = this.metricsHistory.filter(m => m.timestamp > oneMinuteAgo);
        throughput.set(recentRequests.length);
    }

    getSLAStatus() {
        const now = Date.now();
        const fiveMinutesAgo = now - 300000;
        const recentMetrics = this.metricsHistory.filter(m => m.timestamp > fiveMinutesAgo);
        
        if (recentMetrics.length === 0) {
            return { status: 'NO_DATA' };
        }
        
        // Calculate P50, P95, P99 latencies
        const latencies = recentMetrics.map(m => m.latency_ms).sort((a, b) => a - b);
        const p50 = latencies[Math.floor(latencies.length * 0.50)];
        const p95 = latencies[Math.floor(latencies.length * 0.95)];
        const p99 = latencies[Math.floor(latencies.length * 0.99)];
        
        // Calculate error rate
        const failedRequests = recentMetrics.filter(m => !m.success).length;
        const errorRate = failedRequests / recentMetrics.length;
        
        // Calculate total cost
        const totalCost = recentMetrics.reduce((sum, m) => {
            return sum + this.calculateCost(m.tokens_used || 100, m.model);
        }, 0);
        
        return {
            status: errorRate <= this.slaTargets.error_rate ? 'HEALTHY' : 'DEGRADED',
            latency: {
                p50_ms: p50,
                p95_ms: p95,
                p99_ms: p99,
                target_p95: this.slaTargets.latency_p95
            },
            error_rate: errorRate,
            target_error_rate: this.slaTargets.error_rate,
            total_requests: recentMetrics.length,
            cost_5min_usd: totalCost,
            throughput_rpm: recentMetrics.length
        };
    }

    async getMetrics() {
        return register.metrics();
    }

    getContentType() {
        return register.contentType;
    }
}

module.exports = { SLAMonitor };

Multi-stage Verification với Gemini + DeepSeek

// src/services/MultiStageVerifier.js
const { HolySheepVisionGateway } = require('./HolySheepVisionGateway');

class MultiStageVerifier {
    constructor(apiKey) {
        this.gateway = new HolySheepVisionGateway(apiKey, {
            maxRetries: 3,
            retryDelay: 1000,
            backoffMultiplier: 2
        });
        
        this.stages = [
            { name: 'primary', model: 'gemini-2.5-flash', weight: 0.7 },
            { name: 'secondary', model: 'deepseek-v3.2', weight: 0.3 }
        ];
    }

    async verifyProduct(imageBuffer, metadata = {}) {
        const { batch_id, product_type, inspection_level } = metadata;
        
        const results = await Promise.allSettled([
            // Stage 1: Primary verification with Gemini 2.5 Flash
            this.gateway.analyzeWithRetry(imageBuffer, {
                prompt: this.buildGeminiPrompt(product_type, inspection_level)
            }),
            
            // Stage 2: Secondary verification with DeepSeek
            this.gateway.analyzeWithRetry(imageBuffer, {
                prompt: this.buildDeepSeekPrompt(product_type)
            })
        ]);

        // Aggregate results
        const primaryResult = results[0].status === 'fulfilled' ? results[0].value : null;
        const secondaryResult = results[1].status === 'fulfilled' ? results[1].value : null;

        return this.aggregateResults(primaryResult, secondaryResult, {
            batch_id,
            product_type,
            inspection_level
        });
    }

    buildGeminiPrompt(product_type, inspection_level) {
        const basePrompts = {
            'pcb': 'Phân tích board mạch PCB. Kiểm tra: (1) vết xước bề mặt, (2) chân linh kiện gãy, (3) hàn chì không đều, (4) linh kiện lệch vị trí. Độ nhạy: CAO.',
            'chip': 'Kiểm tra chip bán dẫn. Phát hiện: (1) vết nứt vi mô, (2) bụi particles, (3) độ đồng phẳng của die, (4) chất lượng wire bonding. Độ nhạy: RẤT CAO.',
            'display': 'Kiểm tra màn hình hiển thị. Tìm: (1) điểm chết pixel, (2) vết trầy, (3) hiện tượng bleeding, (4) đồng đều màu sắc.'
        };

        return basePrompts[product_type] || 'Kiểm tra sản phẩm công nghiệp, báo cáo các khuyết tật.';
    }

    buildDeepSeekPrompt(product_type) {
        return `Quick defect detection for ${product_type}. 
                Return JSON: {"defects": [{"type": "string", "severity": "low|medium|high", "location": "string"}], "passed": boolean, "confidence": number}`;
    }

    aggregateResults(primary, secondary, metadata) {
        // Weighted voting system
        let finalDecision = 'PASS';
        let totalConfidence = 0;
        let defects = [];

        if (primary) {
            totalConfidence += primary.data.confidence * 0.7;
            if (!primary.data.passed) {
                finalDecision = 'FAIL';
                defects = [...defects, ...primary.data.defects.map(d => ({...d, source: 'gemini'}))];
            }
        }

        if (secondary) {
            totalConfidence += secondary.data.confidence * 0.3;
            if (!secondary.data.passed) {
                finalDecision = 'FAIL';
                defects = [...defects, ...secondary.data.defects.map(d => ({...d, source: 'deepseek'}))];
            }
        }

        // Auto-escalate to human review for high-severity defects
        const hasHighSeverity = defects.some(d => d.severity === 'high');
        const requiresHumanReview = hasHighSeverity || finalDecision === 'FAIL';

        return {
            decision: finalDecision,
            confidence: totalConfidence,
            defects,
            requires_human_review: requiresHumanReview,
            stages_completed: [primary ? 'gemini' : null, secondary ? 'deepseek' : null].filter(Boolean),
            processing_metadata: {
                batch_id: metadata.batch_id,
                product_type: metadata.product_type,
                timestamp: new Date().toISOString(),
                primary_latency_ms: primary?.latency_ms,
                secondary_latency_ms: secondary?.latency_ms,
                fallback_used: !primary || secondary?.fallback
            }
        };
    }
}

module.exports = { MultiStageVerifier };

Express API Handler với Error Handling

// src/handlers/visionHandler.js
const express = require('express');
const multer = require('multer');
const { MultiStageVerifier } = require('../services/MultiStageVerifier');
const { SLAMonitor } = require('../services/SLAMonitor');

const upload = multer({ 
    storage: multer.memoryStorage(),
    limits: { fileSize: 10 * 1024 * 1024 }  // 10MB max
});

class VisionHandler {
    constructor(apiKey) {
        this.verifier = new MultiStageVerifier(apiKey);
        this.monitor = new SLAMonitor(this.verifier.gateway);
    }

    getRouter() {
        const router = express.Router();

        // POST /api/v1/inspect - Main inspection endpoint
        router.post('/inspect', upload.single('image'), async (req, res) => {
            const startTime = Date.now();
            
            try {
                if (!req.file) {
                    return res.status(400).json({
                        error: 'MISSING_IMAGE',
                        message: 'Vui lòng gửi file ảnh trong field "image"'
                    });
                }

                const metadata = {
                    batch_id: req.body.batch_id || BATCH_${Date.now()},
                    product_type: req.body.product_type || 'unknown',
                    inspection_level: req.body.inspection_level || 'standard'
                };

                const result = await this.verifier.verifyProduct(
                    req.file.buffer,
                    metadata
                );

                // Record metrics
                this.monitor.recordRequest({
                    latency_ms: Date.now() - startTime,
                    model: result.stages_completed[0] || 'gemini',
                    tokens_used: 500,  // Estimate
                    success: true
                });

                return res.json({
                    success: true,
                    result,
                    performance: {
                        total_latency_ms: Date.now() - startTime
                    }
                });

            } catch (error) {
                const latency = Date.now() - startTime;
                
                // Record error metrics
                this.monitor.recordRequest({
                    latency_ms: latency,
                    model: 'unknown',
                    tokens_used: 0,
                    success: false
                });

                this.handleError(error, res, latency);
            }
        });

        // GET /api/v1/sla - Get SLA status
        router.get('/sla', async (req, res) => {
            try {
                const slaStatus = this.monitor.getSLAStatus();
                return res.json(slaStatus);
            } catch (error) {
                return res.status(500).json({ error: error.message });
            }
        });

        // GET /metrics - Prometheus metrics
        router.get('/metrics', async (req, res) => {
            try {
                res.set('Content-Type', this.monitor.getContentType());
                res.send(await this.monitor.getMetrics());
            } catch (error) {
                res.status(500).send(error.message);
            }
        });

        // GET /health - Health check
        router.get('/health', (req, res) => {
            res.json({
                status: 'healthy',
                timestamp: new Date().toISOString(),
                uptime_seconds: process.uptime()
            });
        });

        return router;
    }

    handleError(error, res, latency) {
        // Specific error handling based on type
        if (error.code === 'ECONNREFUSED') {
            return res.status(503).json({
                error: 'SERVICE_UNAVAILABLE',
                message: 'HolySheep API không khả dụng. Hệ thống đang thử kết nối lại.',
                retry_after_ms: 5000
            });
        }

        if (error.message.includes('Circuit breaker')) {
            return res.status(429).json({
                error: 'RATE_LIMIT_EXCEEDED',
                message: 'Quá nhiều yêu cầu thất bại. Vui lòng đợi 30 giây.',
                retry_after_ms: 30000
            });
        }

        if (error.status === 401) {
            return res.status(401).json({
                error: 'UNAUTHORIZED',
                message: 'API key không hợp lệ. Vui lòng kiểm tra HOLYSHEEP_API_KEY.'
            });
        }

        // Generic error
        return res.status(500).json({
            error: 'INTERNAL_ERROR',
            message: error.message,
            latency_ms: latency
        });
    }
}

module.exports = { VisionHandler };

Lỗi thường gặp và cách khắc phục

Mã lỗi Mô tả Nguyên nhân gốc Giải pháp
ECONNREFUSED Kết nối bị từ chối HolySheep API server quá tải hoặc network issue Kích hoạt retry với exponential backoff, fallback sang DeepSeek
401 Unauthorized Xác thực thất bại API key sai hoặc hết hạn Kiểm tra lại YOUR_HOLYSHEEP_API_KEY tại dashboard
429 Rate Limit Vượt giới hạn request Gửi quá 500 req/phút Implement queue system với cooldown 60 giây
Circuit Breaker OPEN Hệ thống tạm dừng 5 lỗi liên tiếp trong 30 giây Đợi 30s hoặc restart service
Timeout 30s Request quá lâu Image quá lớn hoặc model busy Nén ảnh trước khi gửi (resize + JPEG quality 80)

Mã khắc phục chi tiết

// src/middleware/errorHandler.js

// Handler cho 401 Unauthorized
function handleAuthError(res) {
    console.error('❌ Authentication failed - Check API key');
    res.status(401).json({
        error: 'UNAUTHORIZED',
        message: 'API key không hợp lệ. Truy cập https://www.holysheep.ai/register để lấy key mới.',
        action: 'Verify API key at dashboard'
    });
}

// Handler cho Rate Limit 429
function handleRateLimit(res, retryAfter = 60000) {
    console.warn(⚠️ Rate limit hit - Cooldown ${retryAfter}ms);
    res.status(429).json({
        error: 'RATE_LIMIT_EXCEEDED',
        message: 'Đã vượt giới hạn 500 req/phút',
        retry_after_ms: retryAfter,
        retry_after_date: new Date(Date.now() + retryAfter).toISOString()
    });
}

// Image preprocessing để tránh timeout
async function preprocessImage(buffer, options = {}) {
    const sharp = require('sharp');
    const { 
        maxWidth = 1920, 
        maxHeight = 1080, 
        quality = 80 
    } = options;

    return await sharp(buffer)
        .resize(maxWidth, maxHeight, { 
            fit: 'inside',
            withoutEnlargement: true 
        })
        .jpeg({ quality })
        .toBuffer();
}

Triển khai Production với Docker

# docker-compose.yml
version: '3.8'

services:
  vision-gateway:
    build: .
    ports:
      - "3000:3000"
      - "9090:9090"  # Prometheus metrics
    environment:
      - HOLYSHEEP_API_KEY=${HOLYSHEEP_API_KEY}
      - HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
      - REDIS_URL=redis://redis:6379
      - NODE_ENV=production
    depends_on:
      - redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped

volumes:
  redis-data:

Tài nguyên liên quan

Bài viết liên quan