SiamCafe.net Blog
Technology

Medusa Commerce GitOps Workflow — Deploy E-commerce ด้วย ArgoCD

medusa commerce gitops workflow
Medusa Commerce GitOps Workflow | SiamCafe Blog
2025-07-23· อ. บอม — SiamCafe.net· 1,340 คำ

Medusa Commerce คืออะไรและเหมาะกับธุรกิจแบบไหน

Medusa เป็น Open Source headless commerce platform ที่พัฒนาด้วย Node.js/TypeScript ออกแบบมาให้เป็นทางเลือกแทน Shopify สำหรับธุรกิจที่ต้องการควบคุมระบบ e-commerce ของตัวเองอย่างเต็มที่ Medusa แยก backend (API) ออกจาก frontend ทำให้สามารถใช้ frontend framework อะไรก็ได้เช่น Next.js, Gatsby หรือ Nuxt

Medusa มีฟีเจอร์หลักครบถ้วนสำหรับ e-commerce ได้แก่ Product Management สำหรับจัดการสินค้าและ variants, Order Management สำหรับจัดการคำสั่งซื้อ, Cart และ Checkout flow ที่ปรับแต่งได้, Payment Integration รองรับ Stripe PayPal และอื่นๆ, Shipping Integration รองรับหลาย fulfillment providers, Multi-region support สำหรับขายหลายประเทศ และ Plugin System สำหรับเพิ่มฟังก์ชัน

GitOps เป็นแนวทางการจัดการ infrastructure และ application deployment โดยใช้ Git เป็น single source of truth ทุกการเปลี่ยนแปลงต้องผ่าน Git commit และ Pull Request ทำให้มี audit trail ที่ชัดเจน สามารถ rollback ได้ง่าย และทีมทำงานร่วมกันได้ดี

การรวม Medusa กับ GitOps ช่วยให้การ deploy e-commerce platform เป็นไปอย่างเป็นระบบ ทุก config change ถูกบันทึกใน Git สามารถ review ก่อน apply และ rollback ได้ทันทีถ้ามีปัญหา

ติดตั้ง Medusa Commerce ด้วย Docker Compose

ตั้งค่า Medusa Backend พร้อม PostgreSQL และ Redis

# docker-compose.yml — Medusa Commerce Stack
version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: medusa
      POSTGRES_PASSWORD: medusa_db_pass
      POSTGRES_DB: medusa_db
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U medusa"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  medusa-backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://medusa:medusa_db_pass@postgres:5432/medusa_db
      REDIS_URL: redis://redis:6379
      JWT_SECRET: your-jwt-secret-key-here
      COOKIE_SECRET: your-cookie-secret-key-here
      STORE_CORS: "http://localhost:8000, https://store.example.com"
      ADMIN_CORS: "http://localhost:7001, https://admin.example.com"
      MEDUSA_ADMIN_ONBOARDING_TYPE: default
    ports:
      - "9000:9000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: sh -c "npx medusa migrations run && npx medusa start"

  medusa-admin:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - ./admin:/app
    ports:
      - "7001:7001"
    command: npm run dev
    depends_on:
      - medusa-backend

volumes:
  pgdata:
  redisdata:

# สร้าง Medusa Backend
# npx create-medusa-app@latest my-store
# cd my-store

# Dockerfile สำหรับ Medusa Backend
cat > backend/Dockerfile << 'DOCKERFILE'
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 9000
CMD ["npx", "medusa", "start"]
DOCKERFILE

# docker compose up -d
# ทดสอบ: curl http://localhost:9000/health
# Admin: http://localhost:7001

ตั้งค่า GitOps Workflow ด้วย ArgoCD

ตั้งค่า ArgoCD สำหรับ GitOps deployment ของ Medusa Commerce

# ติดตั้ง ArgoCD บน Kubernetes
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# รอ ArgoCD พร้อม
kubectl wait --for=condition=available deployment/argocd-server -n argocd --timeout=300s

# ดึง admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Kubernetes Manifests สำหรับ Medusa
# k8s/base/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: medusa-commerce

---
# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: medusa-backend
  namespace: medusa-commerce
spec:
  replicas: 2
  selector:
    matchLabels:
      app: medusa-backend
  template:
    metadata:
      labels:
        app: medusa-backend
    spec:
      containers:
        - name: medusa
          image: myregistry/medusa-backend:v1.0.0
          ports:
            - containerPort: 9000
          envFrom:
            - secretRef:
                name: medusa-secrets
            - configMapRef:
                name: medusa-config
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi
          readinessProbe:
            httpGet:
              path: /health
              port: 9000
            initialDelaySeconds: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 9000
            initialDelaySeconds: 60
            periodSeconds: 30

---
# k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: medusa-backend
  namespace: medusa-commerce
spec:
  selector:
    app: medusa-backend
  ports:
    - port: 9000
      targetPort: 9000

---
# k8s/base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: medusa-config
  namespace: medusa-commerce
data:
  NODE_ENV: "production"
  STORE_CORS: "https://store.example.com"
  ADMIN_CORS: "https://admin.example.com"

---
# ArgoCD Application
# k8s/argocd/medusa-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: medusa-commerce
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/medusa-k8s.git
    targetRevision: main
    path: k8s/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: medusa-commerce
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        maxDuration: 3m

# kubectl apply -f k8s/argocd/medusa-app.yaml

สร้าง CI/CD Pipeline สำหรับ Medusa

GitHub Actions Pipeline สำหรับ build, test และ deploy Medusa

# .github/workflows/medusa-ci-cd.yml
name: Medusa Commerce CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: }/medusa-backend

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: medusa_test
        ports: ["5432:5432"]
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
      redis:
        image: redis:7-alpine
        ports: ["6379:6379"]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - name: Run tests
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/medusa_test
          REDIS_URL: redis://localhost:6379
          JWT_SECRET: test-secret
          COOKIE_SECRET: test-cookie
        run: |
          npx medusa migrations run
          npm test

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    outputs:
      image_tag: }

    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: }
          username: }
          password: }

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: }/}
          tags: |
            type=sha, prefix=
            type=raw, value=latest

      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: }

  update-manifests:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          repository: myorg/medusa-k8s
          token: }

      - name: Update image tag
        run: |
          SHORT_SHA=$(echo } | cut -c1-7)
          cd k8s/overlays/production
          kustomize edit set image myregistry/medusa-backend=}/}:

      - name: Commit and push
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add .
          git commit -m "Deploy medusa-backend: }"
          git push

จัดการ Database Migration และ Seed Data

สร้าง migration workflow ที่ปลอดภัยสำหรับ production

#!/bin/bash
# scripts/migrate.sh — Safe Database Migration
set -euo pipefail

echo "=== Medusa Database Migration ==="
echo "Environment: "
echo "Database: @***"
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"

# 1. Backup ก่อน migrate
echo "[1/4] Creating database backup..."
BACKUP_FILE="/tmp/medusa_backup_$(date +%Y%m%d_%H%M%S).sql"
pg_dump "$DATABASE_URL" > "$BACKUP_FILE"
echo "  Backup saved: $BACKUP_FILE ($(du -sh $BACKUP_FILE | cut -f1))"

# 2. รัน migration (dry-run ก่อน)
echo "[2/4] Checking pending migrations..."
npx medusa migrations run --dry-run 2>&1 | tee /tmp/migration_plan.txt
PENDING=$(grep -c "migration" /tmp/migration_plan.txt || true)
echo "  Pending migrations: $PENDING"

if [ "$PENDING" -eq 0 ]; then
    echo "No pending migrations. Done."
    exit 0
fi

# 3. รัน migration จริง
echo "[3/4] Running migrations..."
npx medusa migrations run 2>&1 | tee /tmp/migration_result.txt

if [ $? -ne 0 ]; then
    echo "ERROR: Migration failed! Rolling back..."
    psql "$DATABASE_URL" < "$BACKUP_FILE"
    echo "Rollback complete."
    exit 1
fi

# 4. Verify
echo "[4/4] Verifying migration..."
npx medusa migrations run --dry-run 2>&1 | grep -c "migration" && echo "  WARNING: Still pending!" || echo "  All migrations applied."

echo "=== Migration Complete ==="

# Seed data script
cat > scripts/seed.js << 'SEED'
const { MedusaContainer } = require("@medusajs/medusa");

async function seed() {
  const container = await MedusaContainer.init();
  const productService = container.resolve("productService");
  const regionService = container.resolve("regionService");

  // สร้าง Region
  const region = await regionService.create({
    name: "Thailand",
    currency_code: "thb",
    tax_rate: 7,
    payment_providers: ["manual"],
    fulfillment_providers: ["manual"],
    countries: ["th"],
  });
  console.log(`Region created: `);

  // สร้าง Product
  const product = await productService.create({
    title: "Sample T-Shirt",
    description: "A comfortable cotton t-shirt",
    handle: "sample-tshirt",
    is_giftcard: false,
    options: [{ title: "Size" }],
    variants: [
      { title: "S", prices: [{ amount: 590, currency_code: "thb" }], options: [{ value: "S" }], inventory_quantity: 100 },
      { title: "M", prices: [{ amount: 590, currency_code: "thb" }], options: [{ value: "M" }], inventory_quantity: 100 },
      { title: "L", prices: [{ amount: 590, currency_code: "thb" }], options: [{ value: "L" }], inventory_quantity: 100 },
    ],
  });
  console.log(`Product created: `);
}

seed().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); });
SEED

Monitoring และ Rollback Strategy

ตั้งค่า monitoring และ automated rollback สำหรับ Medusa deployment

#!/usr/bin/env python3
# medusa_monitor.py — Medusa Commerce Health Monitor
import requests
import time
import json
from datetime import datetime

MEDUSA_URL = "http://localhost:9000"
ARGOCD_URL = "http://argocd-server.argocd.svc:443"
ARGOCD_TOKEN = "your-argocd-token"
SLACK_WEBHOOK = "https://hooks.slack.com/services/xxx"

def check_health():
    try:
        r = requests.get(f"{MEDUSA_URL}/health", timeout=10)
        return r.status_code == 200
    except Exception:
        return False

def check_api():
    endpoints = [
        "/store/products?limit=1",
        "/store/regions",
        "/store/collections",
    ]
    results = {}
    for ep in endpoints:
        try:
            r = requests.get(f"{MEDUSA_URL}{ep}", timeout=10)
            results[ep] = {
                "status": r.status_code,
                "latency": r.elapsed.total_seconds(),
                "ok": r.status_code == 200
            }
        except Exception as e:
            results[ep] = {"status": 0, "latency": 0, "ok": False, "error": str(e)}
    return results

def rollback_argocd(app_name="medusa-commerce"):
    headers = {"Authorization": f"Bearer {ARGOCD_TOKEN}"}
    
    # Get current sync history
    r = requests.get(
        f"{ARGOCD_URL}/api/v1/applications/{app_name}",
        headers=headers, verify=False
    )
    app = r.json()
    history = app.get("status", {}).get("history", [])
    
    if len(history) < 2:
        print("No previous revision to rollback to")
        return False
    
    prev_revision = history[-2]["revision"]
    print(f"Rolling back to revision: {prev_revision}")
    
    # Rollback
    r = requests.post(
        f"{ARGOCD_URL}/api/v1/applications/{app_name}/rollback",
        headers=headers,
        json={"id": history[-2]["id"]},
        verify=False
    )
    return r.status_code == 200

def notify(message, severity="info"):
    icon = {"critical": "🔴", "warning": "🟡", "info": "🟢"}[severity]
    print(f"[{icon}] {datetime.now()} {message}")
    if SLACK_WEBHOOK and severity != "info":
        requests.post(SLACK_WEBHOOK, json={"text": f"{icon} {message}"})

def monitor_loop():
    consecutive_failures = 0
    max_failures = 3
    
    print(f"Medusa Monitor started: {MEDUSA_URL}")
    
    while True:
        healthy = check_health()
        api_results = check_api()
        all_ok = all(r["ok"] for r in api_results.values())
        
        if healthy and all_ok:
            consecutive_failures = 0
            latencies = [r["latency"] for r in api_results.values() if r["ok"]]
            avg_lat = sum(latencies) / len(latencies) if latencies else 0
            
            if avg_lat > 2.0:
                notify(f"High latency: {avg_lat:.2f}s", "warning")
        else:
            consecutive_failures += 1
            failed = [ep for ep, r in api_results.items() if not r["ok"]]
            notify(f"Health check failed ({consecutive_failures}/{max_failures}): {failed}", "warning")
            
            if consecutive_failures >= max_failures:
                notify("Triggering automatic rollback!", "critical")
                if rollback_argocd():
                    notify("Rollback initiated successfully", "warning")
                    consecutive_failures = 0
                else:
                    notify("Rollback failed! Manual intervention required", "critical")
        
        time.sleep(30)

if __name__ == "__main__":
    monitor_loop()

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

Q: Medusa กับ Shopify ต่างกันอย่างไร?

A: Medusa เป็น Open Source self-hosted ไม่มีค่าบริการรายเดือน ปรับแต่งได้ 100% เหมาะสำหรับธุรกิจที่ต้องการ full control ส่วน Shopify เป็น SaaS ใช้งานง่ายไม่ต้องจัดการ server แต่มีค่าบริการรายเดือนและ transaction fee ปรับแต่งได้จำกัด สำหรับ startup ที่มีทีม dev แนะนำ Medusa สำหรับธุรกิจที่ไม่มีทีม dev แนะนำ Shopify

Q: GitOps ดีกว่า CI/CD แบบปกติอย่างไร?

A: GitOps ใช้ Git เป็น single source of truth ทำให้ทุกการเปลี่ยนแปลงถูกบันทึกและ review ได้ สามารถ rollback ได้ด้วย git revert มี drift detection ที่ตรวจจับเมื่อ cluster state ไม่ตรงกับ Git ส่วน CI/CD แบบปกติอาจมี manual changes ที่ไม่ถูกบันทึก GitOps เหมาะสำหรับ Kubernetes environments

Q: Database migration กับ GitOps ทำอย่างไร?

A: ควรแยก migration ออกจาก application deployment โดยรัน migration เป็น Kubernetes Job หรือ init container ก่อนที่ application จะ start ต้อง backup database ก่อน migrate เสมอ ใช้ backward-compatible migrations ที่ทำงานได้กับทั้ง code เวอร์ชันเก่าและใหม่ ห้ามลบ column โดยตรงแต่ต้อง deprecate ก่อน

Q: ArgoCD กับ Flux CD ควรเลือกอันไหน?

A: ArgoCD มี Web UI ที่สวยและใช้งานง่าย มี application-centric approach เหมาะสำหรับทีมที่ต้องการ visibility ส่วน Flux CD เป็น lightweight มากกว่า ใช้ CRDs เป็นหลัก เหมาะสำหรับทีมที่ prefer CLI-first approach ทั้งสองตัวเป็น CNCF graduated projects สำหรับผู้เริ่มต้นแนะนำ ArgoCD เพราะ UI ช่วยให้เข้าใจง่าย

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

Medusa Commerce Docker Container Deployอ่านบทความ → Medusa Commerce Serverless Architectureอ่านบทความ → Medusa Commerce Best Practices ที่ต้องรู้อ่านบทความ → Medusa Commerce Site Reliability SREอ่านบทความ → Medusa Commerce Monitoring และ Alertingอ่านบทความ →

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