SiamCafe.net Blog
Technology

LocalAI Self-hosted GitOps Workflow

localai self hosted gitops workflow
LocalAI Self-hosted GitOps Workflow | SiamCafe Blog
2026-02-13· อ. บอม — SiamCafe.net· 1,510 คำ

LocalAI Self-hosted GitOps Workflow คืออะไร

LocalAI เป็น open-source AI inference server ที่รัน LLMs, Image Generation และ Audio models บนเครื่องตัวเอง เข้ากันได้กับ OpenAI API GitOps คือแนวทาง DevOps ที่ใช้ Git repository เป็น single source of truth สำหรับ infrastructure และ application configuration ทุกการเปลี่ยนแปลงต้องผ่าน Git (pull request → review → merge → auto-deploy) การรวม LocalAI กับ GitOps Workflow ช่วยให้จัดการ AI model deployments อย่างเป็นระบบ track changes, rollback ได้ง่าย และ automate ทุกขั้นตอน

GitOps Principles

# gitops_principles.py — GitOps fundamentals
import json

class GitOpsPrinciples:
    PRINCIPLES = {
        "declarative": {
            "name": "Declarative Configuration",
            "description": "ประกาศ desired state ใน YAML/JSON — ไม่ใช่ imperative commands",
            "example": "บอกว่าต้องการ LocalAI 3 replicas + model llama-3 → ไม่ใช่ run kubectl scale",
        },
        "versioned": {
            "name": "Versioned & Immutable",
            "description": "ทุก configuration อยู่ใน Git — มี version history, audit trail",
            "benefit": "rollback ได้ทุกเมื่อ ด้วย git revert",
        },
        "pulled_automatically": {
            "name": "Pulled Automatically",
            "description": "GitOps agent (ArgoCD/Flux) ดึง config จาก Git → apply อัตโนมัติ",
            "benefit": "ไม่ต้อง kubectl apply มือ — auto-sync",
        },
        "continuously_reconciled": {
            "name": "Continuously Reconciled",
            "description": "ตรวจสอบอยู่ตลอดว่า actual state = desired state — fix drift อัตโนมัติ",
            "benefit": "ถ้าใครแก้ manual → GitOps agent จะ revert กลับ",
        },
    }

    TOOLS = {
        "argocd": {
            "name": "Argo CD",
            "description": "GitOps controller สำหรับ Kubernetes — UI ดี, application CRD",
            "use_case": "Multi-cluster, multi-app deployments",
        },
        "flux": {
            "name": "Flux CD",
            "description": "GitOps toolkit จาก Weaveworks — lightweight, composable",
            "use_case": "Simple setups, Helm/Kustomize native",
        },
    }

    def show_principles(self):
        print("=== GitOps Principles ===\n")
        for key, p in self.PRINCIPLES.items():
            print(f"[{p['name']}]")
            print(f"  {p['description']}")
            print()

    def show_tools(self):
        print("=== GitOps Tools ===")
        for key, tool in self.TOOLS.items():
            print(f"  [{tool['name']}] {tool['description']}")

gp = GitOpsPrinciples()
gp.show_principles()
gp.show_tools()

Repository Structure

# repo_structure.py — GitOps repository structure for LocalAI
import json

class RepoStructure:
    STRUCTURE = """
localai-gitops/
├── apps/
│   └── localai/
│       ├── base/
│       │   ├── deployment.yaml
│       │   ├── service.yaml
│       │   ├── configmap.yaml
│       │   ├── pvc.yaml
│       │   └── kustomization.yaml
│       └── overlays/
│           ├── dev/
│           │   ├── kustomization.yaml
│           │   └── patch-replicas.yaml
│           ├── staging/
│           │   ├── kustomization.yaml
│           │   └── patch-replicas.yaml
│           └── production/
│               ├── kustomization.yaml
│               ├── patch-replicas.yaml
│               └── patch-resources.yaml
├── models/
│   ├── dev/
│   │   └── models.yaml
│   ├── staging/
│   │   └── models.yaml
│   └── production/
│       └── models.yaml
├── argocd/
│   ├── application-dev.yaml
│   ├── application-staging.yaml
│   └── application-production.yaml
└── scripts/
    ├── promote-model.sh
    └── rollback.sh
"""

    BASE_DEPLOYMENT = """
# apps/localai/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: localai
  labels:
    app: localai
spec:
  replicas: 1
  selector:
    matchLabels:
      app: localai
  template:
    metadata:
      labels:
        app: localai
    spec:
      containers:
        - name: localai
          image: localai/localai:latest-aio-gpu-nvidia-cuda-12
          ports:
            - containerPort: 8080
          env:
            - name: THREADS
              value: "4"
            - name: CONTEXT_SIZE
              value: "4096"
          volumeMounts:
            - name: models
              mountPath: /build/models
            - name: config
              mountPath: /build/models/config
          resources:
            requests:
              memory: 8Gi
            limits:
              memory: 16Gi
              nvidia.com/gpu: 1
      volumes:
        - name: models
          persistentVolumeClaim:
            claimName: localai-models
        - name: config
          configMap:
            name: localai-models-config
"""

    def show_structure(self):
        print("=== Repository Structure ===")
        print(self.STRUCTURE[:500])

    def show_deployment(self):
        print("\n=== Base Deployment ===")
        print(self.BASE_DEPLOYMENT[:500])

repo = RepoStructure()
repo.show_structure()
repo.show_deployment()

Model Management via Git

# model_mgmt.py — Model management through GitOps
import json

class ModelManagement:
    MODEL_CONFIG = """
# models/production/models.yaml
models:
  - name: llama-3-8b
    url: "huggingface://TheBloke/Llama-3-8B-GGUF/llama-3-8b.Q4_K_M.gguf"
    backend: llama-cpp
    parameters:
      model: llama-3-8b.Q4_K_M.gguf
    context_size: 4096
    threads: 4
    
  - name: mistral-7b
    url: "huggingface://TheBloke/Mistral-7B-v0.3-GGUF/mistral-7b-v0.3.Q4_K_M.gguf"
    backend: llama-cpp
    parameters:
      model: mistral-7b-v0.3.Q4_K_M.gguf
    context_size: 8192
    threads: 4
    
  - name: whisper-large
    url: "huggingface://ggerganov/whisper.cpp/ggml-large-v3.bin"
    backend: whisper
"""

    PROMOTION_FLOW = {
        "step1": "Developer pushes model config change → PR to dev branch",
        "step2": "CI tests: lint YAML, validate model URLs, check resource limits",
        "step3": "Review + merge → ArgoCD auto-deploys to dev cluster",
        "step4": "Smoke tests pass → PR from dev to staging branch",
        "step5": "Staging tests (load test, quality eval) → PR from staging to production",
        "step6": "Production deploy → ArgoCD syncs → canary analysis",
    }

    CODE = """
# model_promoter.py — Promote model between environments
import subprocess
import json
import yaml
from pathlib import Path

class ModelPromoter:
    def __init__(self, repo_path):
        self.repo_path = Path(repo_path)
    
    def get_models(self, env):
        '''Get models config for environment'''
        config_path = self.repo_path / "models" / env / "models.yaml"
        with open(config_path) as f:
            return yaml.safe_load(f)
    
    def promote(self, model_name, source_env, target_env):
        '''Promote a model from one environment to another'''
        source = self.get_models(source_env)
        target = self.get_models(target_env)
        
        # Find model in source
        model = None
        for m in source.get('models', []):
            if m['name'] == model_name:
                model = m
                break
        
        if not model:
            return {'error': f'Model {model_name} not found in {source_env}'}
        
        # Add/update in target
        target_models = target.get('models', [])
        existing = [i for i, m in enumerate(target_models) if m['name'] == model_name]
        
        if existing:
            target_models[existing[0]] = model
        else:
            target_models.append(model)
        
        target['models'] = target_models
        
        # Write target config
        target_path = self.repo_path / "models" / target_env / "models.yaml"
        with open(target_path, 'w') as f:
            yaml.dump(target, f, default_flow_style=False)
        
        # Git commit
        subprocess.run(['git', 'add', str(target_path)], cwd=self.repo_path)
        subprocess.run([
            'git', 'commit', '-m',
            f'Promote {model_name} from {source_env} to {target_env}'
        ], cwd=self.repo_path)
        
        return {
            'model': model_name,
            'from': source_env,
            'to': target_env,
            'status': 'committed',
        }
    
    def rollback(self, env, commit_hash=None):
        '''Rollback environment to previous state'''
        if commit_hash:
            cmd = ['git', 'revert', '--no-commit', commit_hash]
        else:
            cmd = ['git', 'revert', '--no-commit', 'HEAD']
        
        subprocess.run(cmd, cwd=self.repo_path)
        subprocess.run(['git', 'commit', '-m', f'Rollback {env}'], cwd=self.repo_path)
        
        return {'env': env, 'status': 'rolled back'}

# promoter = ModelPromoter("/path/to/localai-gitops")
# promoter.promote("llama-3-8b", "staging", "production")
"""

    def show_config(self):
        print("=== Model Config ===")
        print(self.MODEL_CONFIG[:400])

    def show_flow(self):
        print("\n=== Promotion Flow ===")
        for key, step in self.PROMOTION_FLOW.items():
            print(f"  {step}")

    def show_code(self):
        print("\n=== Promoter Code ===")
        print(self.CODE[:500])

mgmt = ModelManagement()
mgmt.show_config()
mgmt.show_flow()

ArgoCD Application

# argocd.py — ArgoCD application setup
import json

class ArgoCDSetup:
    APPLICATION = """
# argocd/application-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: localai-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/localai-gitops.git
    targetRevision: main
    path: apps/localai/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: ai-production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
"""

    CI_PIPELINE = """
# .github/workflows/localai-gitops.yml
name: LocalAI GitOps CI
on:
  pull_request:
    paths:
      - 'apps/localai/**'
      - 'models/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Validate YAML
        run: |
          pip install yamllint
          yamllint apps/ models/
      
      - name: Validate Kustomize
        run: |
          for overlay in apps/localai/overlays/*/; do
            kustomize build $overlay > /dev/null
            echo "Valid: $overlay"
          done
      
      - name: Check Model URLs
        run: |
          python scripts/validate_models.py models/
      
      - name: Dry-run ArgoCD Diff
        run: |
          argocd app diff localai-production --local apps/localai/overlays/production
"""

    def show_application(self):
        print("=== ArgoCD Application ===")
        print(self.APPLICATION[:500])

    def show_ci(self):
        print("\n=== CI Pipeline ===")
        print(self.CI_PIPELINE[:400])

argocd = ArgoCDSetup()
argocd.show_application()
argocd.show_ci()

Monitoring & Rollback

# rollback.py — GitOps rollback strategies
import json

class GitOpsRollback:
    STRATEGIES = {
        "git_revert": {
            "name": "Git Revert",
            "description": "git revert commit → ArgoCD auto-sync → rollback อัตโนมัติ",
            "speed": "1-3 นาที (ขึ้นกับ sync interval)",
            "command": "git revert HEAD && git push",
        },
        "argocd_rollback": {
            "name": "ArgoCD Rollback",
            "description": "ArgoCD UI → Application → History → Rollback to previous",
            "speed": "< 1 นาที",
            "command": "argocd app rollback localai-production",
        },
        "model_swap": {
            "name": "Model Config Swap",
            "description": "เปลี่ยน model URL กลับ version เก่าใน models.yaml",
            "speed": "2-5 นาที (commit + sync + model download)",
        },
    }

    MONITORING = {
        "sync_status": "ArgoCD sync status: Synced, OutOfSync, Unknown",
        "health_status": "Application health: Healthy, Degraded, Missing",
        "drift_detection": "ตรวจจับว่า actual state ≠ desired state → alert",
        "deployment_history": "Git log = deployment history — ดูว่าใครเปลี่ยนอะไรเมื่อไหร่",
    }

    def show_strategies(self):
        print("=== Rollback Strategies ===\n")
        for key, s in self.STRATEGIES.items():
            print(f"[{s['name']}]")
            print(f"  {s['description']}")
            print(f"  Speed: {s['speed']}")
            print()

    def show_monitoring(self):
        print("=== Monitoring ===")
        for key, desc in self.MONITORING.items():
            print(f"  [{key}] {desc}")

rollback = GitOpsRollback()
rollback.show_strategies()
rollback.show_monitoring()

FAQ - คำถามที่พบบ่อย

Q: GitOps กับ CI/CD ธรรมดาต่างกันอย่างไร?

A: CI/CD ธรรมดา: CI build → CD push ไป cluster (push-based) GitOps: CI build → commit config ไป Git → GitOps agent pull จาก Git → apply (pull-based) ข้อดี GitOps: Git = audit trail, rollback ง่าย (git revert), drift detection, declarative ข้อดี push-based: ง่ายกว่า setup, ไม่ต้อง agent ใน cluster

Q: ArgoCD กับ Flux อันไหนดีกว่า?

A: ArgoCD: UI ดีมาก, Application CRD, multi-cluster, RBAC, SSO — เหมาะ team ใหญ่ Flux: lightweight, GitOps Toolkit (composable), Helm/Kustomize native — เหมาะ simple setups เลือก ArgoCD: ถ้าต้องการ UI + multi-cluster + team collaboration เลือก Flux: ถ้าต้องการ lightweight + Helm-native + ไม่ต้อง UI

Q: Model files ใหญ่มาก เก็บใน Git ได้ไหม?

A: ไม่ควรเก็บ model files ใน Git (หลาย GB) — เก็บเฉพาะ config (URL, parameters) Config ใน Git: model name, download URL, parameters, resource limits Model files: เก็บใน HuggingFace, S3, GCS หรือ internal registry LocalAI download models ตอน startup จาก URL ที่ระบุใน config

Q: ต้องมี Kubernetes ไหม?

A: GitOps เหมาะกับ Kubernetes มากที่สุด — ArgoCD/Flux ออกแบบมาสำหรับ K8s ถ้าไม่มี K8s: ใช้ Docker Compose + Git + simple deploy script ก็ได้ ทางเลือก: Ansible + Git (GitOps-style แต่ไม่ใช่ K8s), Terraform + Git สำหรับ LocalAI: Docker Compose พอสำหรับ single server, K8s + GitOps สำหรับ production scale

📖 บทความที่เกี่ยวข้อง

LocalAI Self-hosted Log Management ELKอ่านบทความ → LocalAI Self-hosted Cloud Native Designอ่านบทความ → LocalAI Self-hosted Progressive Deliveryอ่านบทความ → LocalAI Self-hosted Container Orchestrationอ่านบทความ → LocalAI Self-hosted FinOps Cloud Costอ่านบทความ →

📚 ดูบทความทั้งหมด →