GGUF & Modelfile: The Power User’s Guide to Local LLMs

GGUF & Modelfile: The Power User’s Guide to Local LLMs

Beyond ollama pull — download any model from Hugging Face, quantize it, customize it, and import it into Ollama.

What’s GGUF?

GGUF (GPT-Generated Unified Format) is the standard file format for running LLMs locally. Think of it as the .mp3 of AI models:

  • Compressed — 70-85% smaller than the original float16 weights
  • Fast — optimized for CPU and GPU inference
  • Portable — one file contains the entire model
  • Metadata-rich — includes tokenizer, chat template, and model config

Every ollama pull downloads a GGUF file under the hood. But the real power move is downloading GGUF files directly from Hugging Face and importing them yourself.

Quantization Analogy (Steal This)

Quantization is like JPEG compression for AI models. A RAW photo is 50MB. A JPEG of the same photo is 5MB — 90% smaller, but it still looks 95% as good. That’s what Q4_K_M quantization does to a model: 70% smaller, 96% of the intelligence.

Step 1: Finding the Right GGUF File

The Golden Rule

Always look for Q4_K_M — it’s the sweet spot of size vs quality for almost every model.

Where to Find GGUFs

Source URL Best For
Official provider huggingface.co/Qwen etc. Trustworthy, but often only Q8/Q6
Unsloth huggingface.co/unsloth Best selection of quants (Q2-Q8)
Bartowski huggingface.co/bartowski Massive library, every quantization
MaziyarPanahi huggingface.co/MaziyarPanahi Merged models, niche architectures

The GGUF Filename Decoder

Qwen2.5-14B-Q4_K_M.gguf
├── Model name      ├── Size   └── Quantization
Quant Code Compression Quality Use Case
Q8_0 50% 99% When you have VRAM to spare
Q6_K 60% 98% High-quality, reasonable size
Q4_K_M 70% 96% 🟢 Sweet spot — use this
Q3_K_M 78% 92% When VRAM is tight
Q2_K 85% 85% Emergency only — quality noticeably drops
IQ4_XS 72% 95% Experimental import format

Step 2: Download & Import a GGUF

Basic Import

# 1. Download Q4_K_M of Qwen 2.5-14B
wget https://huggingface.co/bartowski/Qwen2.5-14B-GGUF/resolve/main/Qwen2.5-14B-Q4_K_M.gguf

# 2. Create a Modelfile
cat > Modelfile << 'EOF'
FROM ./Qwen2.5-14B-Q4_K_M.gguf
EOF

# 3. Import into Ollama
ollama create my-custom-model -f Modelfile

# 4. Run it
ollama run my-custom-model

Smart Import (with Optimized Settings)

cat > Modelfile << 'EOF'
FROM ./DeepSeek-R1-14B-Q4_K_M.gguf

# Performance tuning
PARAMETER num_ctx 32768
PARAMETER num_gpu_layers 999
PARAMETER num_thread 8
PARAMETER numa true

# Generation
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER repeat_penalty 1.1

# Chat template (CRITICAL — must match the model!)
TEMPLATE """{{ if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
"""

# System prompt
SYSTEM """You are a helpful AI assistant."""
EOF

ollama create my-r1-custom -f Modelfile
ollama run my-r1-custom

Step 3: Modelfile Reference

A Modelfile is like a Dockerfile for LLMs. Every line is an instruction.

Parameters Reference

Parameter What It Does Default Recommended Range
temperature Creativity level 0.8 0.2 (code) – 1.0 (creative)
top_p Nucleus sampling 0.9 0.85 – 0.95
top_k Top-K sampling 40 20 – 100
num_ctx Context window size 2048 4096 – 65536
num_gpu GPU layers 0 (auto) 999 (use all VRAM)
num_thread CPU threads auto 4 – 16
repeat_penalty Penalize repetition 1.1 1.0 – 1.2
stop Stop sequences varies `<

INSTRUCTION vs SYSTEM vs TEMPLATE

{% raw %}

# SYSTEM: Persistent system prompt (like OpenAI's system message)
SYSTEM """You are a helpful assistant."""

# TEMPLATE: How user messages are formatted
TEMPLATE """User: {{ .Prompt }}
Assistant: """

# INSTRUCTION: Model-specific instruction format (rarely needed)
INSTRUCTION """Follow the user's instructions carefully."""

Three Production Configs

1. Coding Assistant

FROM qwen2.5:7b
PARAMETER temperature 0.2
PARAMETER top_p 0.85
PARAMETER num_ctx 65536
PARAMETER repeat_penalty 1.1
SYSTEM """You are an expert Python developer. Write clean, tested code."""

2. Creative Writer

FROM mistral
PARAMETER temperature 1.0
PARAMETER top_p 0.95
PARAMETER num_ctx 16384
SYSTEM """You are a novelist. Be vivid and descriptive."""

3. Customer Support

FROM llama4
PARAMETER temperature 0.5
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
SYSTEM """You are a helpful customer support agent.
Be polite, concise, and solution-oriented.
NEVER mention that you are an AI."""

Step 4: Advanced Techniques

4.1 Multi-GPU Setup

FROM deepseek-r1:70b

# Distribute across 2 GPUs
PARAMETER num_gpu_layers 999
PARAMETER main_gpu 0
PARAMETER tensor_split "0.5,0.5"

4.2 LoRA Adapters (Experimental)

Some Ollama builds support LoRA adapters:

FROM base-model
ADAPTER ./my-finetune-lora.gguf
PARAMETER temperature 0.7

4.3 Custom Stop Tokens

DeepSeek-R1 and Qwen use different stop tokens:

# For Qwen
TEMPLATE """<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
"""
PARAMETER stop "<|im_end|>"
PARAMETER stop "<|im_start|>"

# For DeepSeek
TEMPLATE """User: {{ .Prompt }}
Assistant: """
PARAMETER stop "User:"

4.4 Emergency: VRAM Too Low

If you get “CUDA out of memory”:

# Force CPU for some layers
PARAMETER num_gpu_layers 24  # Only put 24 layers on GPU
PARAMETER num_thread 8       # Use 8 CPU threads for the rest

Step 5: GGUF from Ollama Models (Export)

You can also export a model from Ollama back to a GGUF file:

# Save a model as GGUF
ollama pull qwen2.5:7b
ollama export qwen2.5:7b ./my-export.gguf

# Now you can use it anywhere (llama.cpp, text-generation-webui, etc.)
./llama-cli -m ./my-export.gguf -p "Hello"

This is useful for:

  • Moving models between machines without re-downloading
  • Using the same model with multiple inference engines
  • Sharing a specific quantization with teammates

Performance Cheat Sheet

By GPU

GPU VRAM Best GGUF Model Expected Speed
RTX 3060 / 4060 12 GB Qwen 2.5-14B (Q4_K_M) 30-40 tok/s
RTX 4070 / 5070 12 GB Qwen 2.5-14B (Q4_K_M) 35-50 tok/s
RTX 4080 / 5080 16 GB DeepSeek-R1-14B (Q4_K_M) 30-45 tok/s
RTX 4090 / 5090 24 GB DeepSeek-R1-32B (Q4_K_M) 18-25 tok/s
Mac M2 Pro 16 GB Qwen 2.5-7B (Q4_K_M) 15-25 tok/s
Mac M4 Max 36 GB Qwen 3.6-27B (Q4_K_M) 20-30 tok/s

CPU-Only Performance

Model Quant RAM Speed
Qwen 2.5-1.5B Q4_K_M 4 GB 8-15 tok/s
Qwen 2.5-7B Q4_K_M 16 GB 1-4 tok/s
Qwen 2.5-7B Q2_K 8 GB 2-6 tok/s

Common Pitfalls

Problem Cause Fix
“Model not found” after import Modelfile path is wrong Use absolute path: FROM /home/user/model.gguf
Gibberish output Wrong chat template The TEMPLATE line must match the model’s expected format
Slow generation Running on CPU PARAMETER num_gpu_layers 999
CUDA out of memory Quantization too large for VRAM Try smaller quant (Q3_K_M instead of Q4_K_M)
Import errors Corrupt GGUF download Re-download and verify checksum
Temperature not working Set in Modelfile but overridden in API Use the same temp in both places
Chinese text output Wrong template or default system prompt Add `PARAMETER stop “<

The tl;dr

  1. Download: {% raw %}wget <huggingface-url>/Model-Q4_K_M.gguf
  2. Create Modelfile: FROM ./Model.gguf + your settings
  3. Import: ollama create my-model -f Modelfile
  4. Run: ollama run my-model
  5. Profit: Free, private, local AI

Part of the Local LLM Guide — the definitive resource for running AI on your own hardware.

What Excited Me Most at Google I/O 2026

This is a submission for the Google I/O Writing Challenge

Google I/O 2026 genuinely felt exciting this year because the announcements didn’t just look futuristic – they actually felt useful for developers and students right now.

One thing that especially caught my attention during Sundar Pichai’s keynote was the idea of intelligent eyewear powered by AI. Seeing how smart glasses and AI could work together in real-world situations felt incredibly futuristic and practical at the same time. It showed how AI is slowly moving beyond screens and becoming part of everyday human experiences.

What excited me most was how Google is making AI more integrated into real development workflows. Instead of AI being just a chatbot or experimental feature, it now feels like a proper development companion that can help developers build, learn, debug, and create faster.

As someone who enjoys exploring technology and building projects, I found the AI-focused announcements incredibly motivating. It feels like we are entering a phase where developers can focus more on creativity and problem-solving while AI helps with repetitive or time-consuming tasks.

Another thing I liked was how connected the Google ecosystem is becoming. From AI tools to Firebase, Cloud, Android, and development platforms – everything seems more streamlined and developer-friendly than before.

Some highlights that stood out to me:

  1. Intelligent AI-powered eyewear concepts
  2. Smarter AI integrations for developers
  3. Faster and more efficient workflows
  4. Better tools for app development
  5. Improved productivity features
  6. More beginner-friendly innovation

What made this event special for me was not just the technology itself, but the possibilities it creates for students and independent developers. Watching Sundar Pichai present these innovations made the future of AI feel much closer and more real.

Overall, Google I/O 2026 was inspiring, innovative, and full of ideas that developers can actually use in real projects. It made me even more excited to continue learning about AI, development, and future technologies.

Thanks to Google and DEV for organizing this challenge!

Your Organizational AI Adoption Metrics Are Lying (Plus How to Measure Real Adoption)

Most enterprise AI dashboards look healthy right now. Login counts are rising. Pilot programs are multiplying. Internal copilots have thousands of registered users. Executive updates show “AI-enabled productivity gains” across multiple functions.

Then you look closer.

Teams are still routing work through old workflows. Engineers bypass approved AI tooling and use consumer models instead. Analysts copy outputs into spreadsheets because downstream systems were never redesigned. Support teams experiment with AI during quiet periods but revert to manual processes under operational pressure.

The metrics say adoption is accelerating. Operational behavior says otherwise.

That contradiction is becoming one of the defining enterprise technology problems of 2026. AI usage is easy to measure. AI dependency is not. Most organizations are conflating exposure with operational integration, and that distinction matters far more than leadership teams currently admit.

According to McKinsey’s State of AI research, 88% of organizations report AI use in at least one business function, but only about one-third say they have scaled AI beyond experimentation. That gap is the real story. The industry has largely solved AI access. It has not solved operational adoption.

That assumption no longer holds: deploying AI tools does not mean the organization has become AI-capable.

Most Enterprise AI Metrics Measure Activity, Not Dependence

The first generation of enterprise AI metrics emerged from SaaS adoption playbooks. Monthly active users, prompt counts, session duration, license utilization, and completion rates became default reporting layers because they were easy to collect.

Those metrics are not useless. They are just incomplete.

An employee opening an AI assistant twice a week tells you almost nothing about whether AI has materially changed delivery speed, decision quality, process design, or cost structure. In many organizations, employees are experimenting with AI while core operational systems remain structurally unchanged.

This is why so many executive AI reviews feel disconnected from business outcomes. The reporting emphasizes interaction volume rather than workflow substitution.

A large insurance enterprise may report that 70% of underwriters use AI summarization tools. That sounds impressive until you discover policy review throughput improved by only 4%, because legal validation, claims escalation, and document routing were never redesigned around AI-assisted workflows.

AI adoption only becomes economically meaningful when workflows start assuming AI participation by default. Until then, the organization is mostly funding parallel experimentation.

This is where the distinction between optional use and operational use becomes critical. Optional use improves convenience. Operational use changes system behavior.

According to recent industry reports, nearly 1 in 3 AI systems are used optionally, not operationally.

That pattern is increasingly visible across enterprises deploying copilots, internal assistants, and retrieval-based knowledge systems. Employees try them. Some employees even like them. But the business process itself remains fundamentally human-routed.
In practice, this becomes the real bottleneck.

What Real AI Adoption Actually Looks Like

Real adoption is visible operationally before it is visible culturally. Mature organizations stop debating whether employees “like” the tools because AI participation becomes embedded into execution paths.

You can usually identify real adoption through five observable shifts.

1. Workflow orchestration changes

The strongest indicator is not usage volume. It is process redesign.
If AI-generated outputs still require manual copying, manual approvals, or disconnected validation steps, the organization has not operationalized AI. It has added an assistant layer on top of existing operational debt.

Mature implementations redesign workflows so AI outputs become native system inputs. Ticket triage routes automatically. Knowledge retrieval feeds directly into service workflows. Engineering copilots integrate with testing pipelines and policy controls rather than existing as isolated interfaces.

That transition requires architecture work, not just tooling procurement.

2. Human review becomes targeted instead of universal

Early-stage AI deployments force humans to review everything equally because trust models are immature. That approach does not scale.
Operational adoption appears when organizations develop confidence segmentation. Low-risk outputs move autonomously. Medium-risk outputs receive selective review. High-risk decisions remain fully supervised.
This is how scalable AI operations actually emerge in practice. Not through blind automation, but through calibrated operational trust.
Research around developer AI adoption increasingly supports this model. Human-AI collaboration dominates successful enterprise usage patterns, while fully autonomous workflows remain limited outside tightly scoped domains.

3. AI usage survives operational pressure

Pilot behavior collapses during stress events. Real adoption does not.
One of the most reliable indicators of maturity is whether teams continue using AI during peak operational load. Customer escalations, release incidents, financial close cycles, and compliance reviews expose whether AI systems are genuinely trusted.

If teams abandon AI under pressure, the organization never operationalized trust.

That is only part of the story, though. Many enterprises misdiagnose this as a model quality problem when the actual issue is governance ambiguity. Employees revert to manual execution when accountability boundaries remain unclear.

4. Metrics move beyond productivity theater

“Hours saved” has become the vanity metric of enterprise AI.

Most organizations cannot validate those estimates rigorously because they rarely measure downstream operational effects. Faster content generation means little if review queues expand. Faster code generation means little if defect remediation rises six weeks later.

Real measurement frameworks track system-level outcomes:

  • Cycle-time compression across complete workflows
  • Reduction in escalation frequency
  • Error-rate changes under operational load
  • Margin improvement tied to process redesign
  • Decision latency reductions
  • Dependency reduction on scarce expert roles

Those are harder metrics to capture because they require cross-functional instrumentation rather than isolated AI telemetry.

But those metrics reflect operational change instead of interface activity.

5. Governance becomes invisible infrastructure

Immature organizations treat AI governance as a review committee. Mature organizations treat governance as execution infrastructure.

Access controls, retrieval boundaries, prompt logging, policy enforcement, model routing, and auditability become embedded into platforms instead of existing as separate oversight functions.

This distinction matters because operational adoption collapses when governance introduces friction. Employees will always route around systems that slow execution.

According to Deloitte’s enterprise generative AI research, more than two-thirds of respondents say fewer than 30% of their AI experiments will scale operationally in the near term. That is not primarily a model problem. It is an organizational systems problem.

Why So Many AI Programs Stall After Initial Success

Most stalled AI programs share the same structural pattern. Leadership teams optimize for visible deployment rather than workflow redesign.
The first phase looks successful because experimentation creates immediate novelty and localized productivity improvements. Then complexity emerges.

Data boundaries become inconsistent. Compliance requirements expand. Integration work slows execution. Teams discover that model quality is only one variable inside a much larger operational chain.

This is where many enterprises quietly enter what can best be described as “adoption inflation.” Reported usage remains high while operational dependency plateaus.

Recent industry reporting reflects this tension clearly. Surveys continue showing rising AI investment, yet many organizations admit adoption decisions were driven more by competitive pressure than operational readiness.

Radixweb’s recently released field intelligence report on AI failure highlights a similar trend emerging across enterprise delivery environments: organizations consistently underestimate the operational redesign required to move from experimentation into durable workflow integration.

That observation aligns with what many technology leaders are now seeing internally. AI does not fail because employees resist it. AI stalls because enterprise operating models were never rebuilt around machine participation.

The Next Phase of Enterprise AI Will Be Measured Differently

The market is already shifting away from deployment metrics toward operational dependency metrics.

Boards increasingly want evidence that AI changes cost structures, delivery velocity, resilience, or strategic capacity. “Users onboarded” no longer answers that question. Neither do prompt counts. The next generation of enterprise AI measurement will likely focus on operational substitution rates:

  • What percentage of workflows assume AI participation?
  • Which business processes fail without AI augmentation?
  • How much decision latency disappears because AI is embedded upstream?
  • Which teams materially changed staffing models because workflows evolved?

Those are more uncomfortable questions because they expose whether the organization actually transformed its operating model.

The companies seeing meaningful AI returns are becoming more selective, not less selective. McKinsey’s recent research suggests high-performing enterprises concentrate AI efforts into fewer, strategically important domains instead of spreading pilots across the entire organization.

That shift signals where the industry is heading next.
Enterprise AI maturity will not be defined by how many employees touched an AI system this quarter. It will be defined by how many critical workflows became economically or operationally dependent on AI participation without sacrificing governance, reliability, or accountability.
Everything else is activity reporting.

Building a Production-Grade MLOps Home Lab on Windows — K8s, LLM, RAG & GitLab CI

TL;DR — I set up a complete MLOps stack on my Windows 11 PC using Multipass + k3s. This is the real guide — including every error I hit and how I fixed it. No fluff, no perfect screenshots. Just what actually worked.

Why Build a Home Lab?

Cloud bills add up fast when you’re learning. A local home lab gives you:

  • Real Kubernetes — not Minikube toy mode
  • Full MLOps stack — MLflow, Minio, Airflow, Ollama, Qdrant
  • CI/CD with GitLab — actual pipelines, not tutorials
  • Zero cost — runs on hardware you already own
  • Safe sandbox — break things without consequences

The goal wasn’t just to have services running. The goal was to practice the full DevOps + MLOps workflow end to end: push code → pipeline triggers → Terraform provisions → services deploy → metrics appear in Grafana.

My Setup

Resource Value
OS Windows 11 Pro
RAM 32 GB
CPU 8 cores
Disk 500 GB SSD
Hypervisor Hyper-V (native Windows Pro)
VM Manager Multipass
Kubernetes k3s

Architecture decision: I kept Windows as my daily driver and ran everything inside a single Ubuntu VM via Multipass. Clean separation, easy to pause/resume, no dual boot headaches.

The Stack

Windows 11 (daily driver)
│
├── 🌐 GitLab.com (SaaS — free tier)
│    └── Pipelines + Container Registry
│
└── Multipass → vm-k3s (10 GB RAM / 4 CPU / 80 GB)
     │
     ├── ☸️  k3s (Kubernetes)
     │
     ├── ⚙️  MLOps
     │    ├── MLflow      — experiment tracking
     │    ├── Minio       — S3-compatible artifact storage
     │    └── Airflow     — pipeline orchestration
     │
     ├── 🤖 LLM Stack
     │    ├── Ollama      — run LLMs locally (CPU)
     │    └── LiteLLM    — unified OpenAI-compatible API
     │
     ├── 🔍 RAG Stack
     │    ├── Qdrant      — vector database
     │    └── LangChain   — RAG orchestration
     │
     ├── 📊 Observability
     │    ├── Prometheus  — metrics
     │    ├── Grafana     — dashboards
     │    └── Loki        — centralized logs
     │
     └── 🔐 HashiCorp Vault — secrets management

Step 1 — Enable Hyper-V and Install Tools

First, enable Hyper-V on Windows Pro (required for Multipass):

# Run as Administrator
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All
# Reboot when prompted

After reboot, install all tools via winget:

winget install Canonical.Multipass
winget install Git.Git
winget install Microsoft.VisualStudioCode
winget install Hashicorp.Terraform
winget install Helm.Helm
winget install Kubernetes.kubectl

Why winget over Chocolatey? I originally used choco install multipass and hit this error:
Exception calling "Start": "The specified executable is not a valid application for this OS platform."
Winget installs the proper signed installer directly. Use winget.

Step 2 — Create the VM

multipass launch `
  --name vm-k3s `
  --cpus 4 `
  --memory 10G `
  --disk 80G `
  22.04

RAM tip: I originally tried 16 GB and got:
Failed to allocate 16384 MB of RAM: Insufficient system resources
Windows was already consuming ~20 GB. 10 GB for the VM is the sweet spot on a 32 GB machine — leaves your OS comfortable and gives k3s plenty of room.

Check your actual free RAM before creating the VM:

Get-CimInstance Win32_OperatingSystem | Select-Object FreePhysicalMemory, TotalVisibleMemorySize

Enter the VM:

multipass shell vm-k3s
# Prompt becomes: ubuntu@vm-k3s:~$

Step 3 — Install Docker + k3s

Inside the VM:

# Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker ubuntu
sudo systemctl enable docker && sudo systemctl start docker

# k3s — lightweight Kubernetes
curl -sfL https://get.k3s.io | sh -s - 
  --write-kubeconfig-mode 644 
  --disable traefik 
  --docker

sleep 20
sudo k3s kubectl get nodes

Configure kubectl:

mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown ubuntu:ubuntu ~/.kube/config
echo 'export KUBECONFIG=~/.kube/config' >> ~/.bashrc
source ~/.bashrc

kubectl get nodes
# NAME      STATUS   ROLES                  AGE   VERSION
# vm-k3s    Ready    control-plane,master   30s   v1.29.x

Step 4 — Helm + Namespaces

# Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Add repos
helm repo add grafana         https://grafana.github.io/helm-charts
helm repo add prometheus      https://prometheus-community.github.io/helm-charts
helm repo add open-telemetry  https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo add hashicorp       https://helm.releases.hashicorp.com
helm repo update

# Create namespaces
for ns in mlops llm rag monitoring logging vault; do
  kubectl create namespace $ns
done

Step 5 — Connect GitLab CI

I used GitLab.com SaaS instead of self-hosting GitLab. This saved 6 GB of RAM — GitLab CE alone needs 6+ GB. Free tier is more than enough for a home lab.

Create a project on gitlab.com, grab the registration token from Settings → CI/CD → Runners, then:

# Install GitLab Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install -y gitlab-runner
sudo usermod -aG docker gitlab-runner

# Register
sudo gitlab-runner register 
  --non-interactive 
  --url "https://gitlab.com" 
  --registration-token "YOUR_TOKEN_HERE" 
  --executor "docker" 
  --docker-image "alpine:latest" 
  --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" 
  --docker-privileged 
  --description "homelab-runner" 
  --tag-list "homelab,k8s,mlops,terraform" 
  --run-untagged true

sudo gitlab-runner start

Your runner appears green in GitLab within seconds. Every push now triggers real CI/CD on your local machine.

Step 6 — Deploy Minio (The Right Way)

This is where I hit my first major blocker.

What I tried first:

helm install minio bitnami/minio 
  --namespace mlops 
  --set auth.rootUser=minioadmin 
  --set auth.rootPassword=minioadmin123

What happened:

Failed to pull image "docker.io/bitnami/minio:2025.7.23-debian-12-r3": not found
Error: ErrImagePull → ImagePullBackOff

Bitnami generates Helm chart tags that reference Docker images which don’t yet exist on Docker Hub. Classic timing issue.

The fix — use the official Minio image directly:

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: minio
  namespace: mlops
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      containers:
      - name: minio
        image: quay.io/minio/minio:latest
        command: ["minio", "server", "/data", "--console-address", ":9001"]
        env:
        - name: MINIO_ROOT_USER
          value: "minioadmin"
        - name: MINIO_ROOT_PASSWORD
          value: "minioadmin123"
        ports:
        - containerPort: 9000
          name: api
        - containerPort: 9001
          name: console
---
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: mlops
spec:
  type: NodePort
  ports:
  - name: api
    port: 9000
    nodePort: 30900
  - name: console
    port: 9001
    nodePort: 30901
  selector:
    app: minio
EOF

quay.io/minio/minio is Minio’s own registry — always up to date, no tag mismatch issues.

Step 7 — Deploy MLflow

MLflow needs Minio as its artifact backend. I used SQLite to keep it simple (no PostgreSQL dependency for a home lab):

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mlflow
  namespace: mlops
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mlflow
  template:
    metadata:
      labels:
        app: mlflow
    spec:
      initContainers:
      - name: create-minio-bucket
        image: quay.io/minio/mc:latest
        command: ["/bin/sh", "-c"]
        args:
          - |
            mc alias set minio http://minio:9000 minioadmin minioadmin123
            mc mb minio/mlflow --ignore-existing
      containers:
      - name: mlflow
        image: ghcr.io/mlflow/mlflow:latest
        command:
          - mlflow
          - server
          - --host=0.0.0.0
          - --port=5000
          - --backend-store-uri=sqlite:///mlflow.db
          - --default-artifact-root=s3://mlflow/
          - --serve-artifacts
        env:
        - name: MLFLOW_S3_ENDPOINT_URL
          value: "http://minio:9000"
        - name: AWS_ACCESS_KEY_ID
          value: "minioadmin"
        - name: AWS_SECRET_ACCESS_KEY
          value: "minioadmin123"
        - name: AWS_DEFAULT_REGION
          value: "us-east-1"
        ports:
        - containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
  name: mlflow
  namespace: mlops
spec:
  type: NodePort
  ports:
  - port: 5000
    nodePort: 30500
  selector:
    app: mlflow
EOF

The initContainer automatically creates the mlflow bucket in Minio before the server starts — no manual setup needed.

Step 8 — Run a Local LLM with Ollama

This is where it gets interesting. Running a real LLM on your local machine, inside Kubernetes, on CPU only.

cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  namespace: llm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      containers:
      - name: ollama
        image: ollama/ollama:latest
        ports:
        - containerPort: 11434
        env:
        - name: OLLAMA_NUM_PARALLEL
          value: "1"
        - name: OLLAMA_MAX_LOADED_MODELS
          value: "1"
        resources:
          requests:
            memory: "2Gi"
            cpu: "1"
          limits:
            memory: "4Gi"
            cpu: "3"
        volumeMounts:
        - name: ollama-data
          mountPath: /root/.ollama
      volumes:
      - name: ollama-data
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: ollama
  namespace: llm
spec:
  type: NodePort
  ports:
  - port: 11434
    nodePort: 31434
  selector:
    app: ollama
EOF

Critical: always set resource limits on shared VMs. Without limits, Ollama will consume all available RAM and OOMKill your other pods. I learned this the hard way.

Choosing the Right Model for CPU

Model RAM needed Good for
Mistral 7B Q4 4.3 GB Too heavy for 4 GB limit
Phi-3 Mini 3.5 GB Still too heavy
llama3.2:1b 1.3 GB ✅ Perfect for CPU home lab
gemma2:2b 1.6 GB ✅ Good alternative
OLLAMA_POD=$(kubectl get pod -n llm -l app=ollama -o jsonpath='{.items[0].metadata.name}')

# Pull the model
kubectl exec -n llm $OLLAMA_POD -- ollama pull llama3.2:1b

# Test it
kubectl exec -n llm $OLLAMA_POD -- ollama run llama3.2:1b "Explain RAG in 2 sentences"

Test via API:

VM_IP=$(hostname -I | awk '{print $1}')
curl -s http://$VM_IP:31434/api/generate 
  -H "Content-Type: application/json" 
  -d '{
    "model": "llama3.2:1b",
    "prompt": "What is MLOps?",
    "stream": false
  }' | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])"

Step 9 — Observability Stack

# Prometheus + Grafana
helm install kube-prometheus prometheus/kube-prometheus-stack 
  --namespace monitoring 
  --set grafana.service.type=NodePort 
  --set grafana.service.nodePort=30300 
  --set grafana.adminPassword=admin123 
  --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false

# Loki (log aggregation) + Promtail (log shipper)
helm install loki grafana/loki-stack 
  --namespace logging 
  --set grafana.enabled=false 
  --set promtail.enabled=true

Access Grafana:

VM_IP=$(hostname -I | awk '{print $1}')
echo "Grafana: http://$VM_IP:30300"
# Login: admin / admin123

Add Loki as a data source in Grafana:

  • Settings → Data Sources → Add → Loki
  • URL: http://loki.logging:3100

Now you have unified logs + metrics in one dashboard.

Step 10 — Vault for Secrets Management

helm install vault hashicorp/vault 
  --namespace vault 
  --set server.dev.enabled=true 
  --set server.dev.devRootToken=root 
  --set ui.enabled=true 
  --set ui.serviceType=NodePort 
  --set ui.serviceNodePort=30820

# Store your first secret
VAULT_POD=$(kubectl get pod -n vault -l app.kubernetes.io/name=vault -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n vault $VAULT_POD -- vault secrets enable kv-v2
kubectl exec -n vault $VAULT_POD -- vault kv put secret/homelab 
  minio_key=minioadmin 
  minio_secret=minioadmin123

VM_IP=$(hostname -I | awk '{print $1}')
echo "Vault UI: http://$VM_IP:30820  (token: root)"

In your GitLab CI pipeline, reference Vault secrets instead of hardcoding them in variables.

The Full Picture — All Services Running

VM_IP=$(hostname -I | awk '{print $1}')
echo "=== Your Home Lab ==="
echo "MLflow   : http://$VM_IP:30500"
echo "Minio    : http://$VM_IP:30901"
echo "Grafana  : http://$VM_IP:30300  (admin/admin123)"
echo "Vault    : http://$VM_IP:30820  (token: root)"
echo "Ollama   : http://$VM_IP:31434"

Lessons Learned

1. Bitnami Helm charts break on image tags
Don’t use bitnami/minio — it references images that don’t exist yet. Use quay.io/minio/minio:latest directly.

2. Always set Kubernetes resource limits on shared VMs
Without limits, one greedy pod (looking at you, Ollama) will OOMKill everything else. Set limits.memory always.

3. RAM planning matters more than you think
On a 32 GB machine, Windows itself consumes ~20 GB. That leaves 12 GB for your VM. Budget carefully — 10 GB for the VM is the realistic sweet spot.

4. GitLab SaaS > self-hosted for a home lab
Self-hosting GitLab CE needs 6+ GB of RAM just to idle. GitLab.com free tier gives you unlimited private repos, 400 CI/CD minutes/month, and a container registry. Use it.

5. Start small with LLMs on CPU
Forget Mistral 7B on CPU without a GPU. llama3.2:1b is surprisingly capable for RAG experiments and uses only 1.3 GB. Add GPU passthrough later if you need more power.

6. Use winget not choco for Multipass on Windows
Chocolatey’s Multipass package uses an installer that fails on recent Windows builds. winget install Canonical.Multipass works every time.

What’s Next

This setup is a solid foundation. Here’s what I’m building on top of it:

  • Kubeflow Pipelines — proper ML pipeline orchestration on K8s
  • OpenTelemetry Collector — unified traces/metrics/logs routing
  • Datadog integration — ship everything to cloud observability
  • Terraform IaC — replace all kubectl apply with proper infrastructure as code
  • RAG pipeline — Qdrant + LangChain + Ollama end-to-end

Quick Reference

# VM management (Windows PowerShell)
multipass list                    # list VMs
multipass shell vm-k3s            # enter VM
multipass suspend vm-k3s          # pause (saves RAM)
multipass start vm-k3s            # resume

# Inside the VM
kubectl get pods -A               # all pods
kubectl top pods -A               # RAM/CPU usage
free -mh                          # available RAM
watch kubectl get pods -A         # live monitoring

Resources

Built this through caffeine and kubectl describe pod debugging. If you hit issues I didn’t cover, drop a comment — happy to help.

If this saved you time, leave a ❤️ — it helps others find it.

The Moment I Realized AI Agents are Changing Software Forever

Three hours into debugging an AI workflow, I had one of those moments developers rarely admit out loud.

I was exhausted.

Not because the code itself was particularly difficult, but because the entire experience felt strangely fragile. The model kept losing context halfway through execution. Tool calls failed silently. One retry loop accidentally triggered another retry loop, and suddenly the “intelligent system” I had imagined started feeling more like duct tape stretched across APIs.

At some point around 2 a.m., I leaned back from my screen and realized something uncomfortable:

The problem with modern AI development isn’t just the models anymore.

It’s orchestration.

That was the moment frameworks like Hermes Agent started making sense to me.

And honestly, I think we’re only beginning to understand how important that shift really is.

We’ve Moved Beyond Chatbots Without Fully Realizing It

A lot of people still think AI development is mostly about prompts.

You ask a question.
The model responds.
Maybe it writes code, summarizes text, or generates content.

But quietly, almost underneath the surface, something bigger has started happening.

Developers are no longer building systems that simply respond.

They’re building systems that:

  • reason through tasks
  • use tools
  • retrieve information
  • make decisions
  • execute actions
  • coordinate workflows

That transition changes everything.

The strange thing about AI agents is that they don’t just alter what software can do. They alter what software feels like.

Traditional software waits for instructions.

Agentic systems behave more like participants.

That difference sounds subtle until you experience it firsthand.

So What Exactly Is Hermes Agent?

The simplest way I can explain Hermes Agent is this:

It’s a framework designed to help developers build AI agents capable of multi-step reasoning, tool usage, workflow execution, and more autonomous behavior.

Instead of treating AI as a single prompt-response interaction, Hermes Agent treats AI more like an active system operating across tasks.

That distinction matters more than most people initially realize.

A normal chatbot interaction usually ends after generating a response.

An agent system continues thinking.

It evaluates context.
Chooses actions.
Uses tools.
Checks results.
Adjusts behavior.

In other words, it starts behaving less like autocomplete and more like operational software.

At first, I thought all agent frameworks were basically variations of the same idea wrapped in different branding.

But the more I explored Hermes Agent, the more I started noticing something deeper happening across the entire AI ecosystem.

We are slowly shifting from conversational AI to behavioral AI.

And software design may never look the same afterward.

The Developer Frustration Nobody Talks About Enough

One thing I appreciate about Hermes Agent is that it indirectly exposes a truth many developers are already feeling:

Modern AI stacks are messy.

Really messy.

You use one library for orchestration.
Another for memory.
Another for vector retrieval.
Another for tools.
Another for monitoring.

Then you spend hours trying to hold everything together while context windows collapse and API costs quietly climb in the background.

There’s this emotional contradiction in AI development right now.

The possibilities feel futuristic.
The workflows sometimes feel exhausting.

I remember building a small experimental research assistant several months ago. The concept sounded simple:

  • search the web
  • summarize information
  • organize findings
  • generate reports

In theory, it felt revolutionary.

In practice, half the development process involved handling edge cases I never anticipated:

  • malformed outputs
  • context loss
  • inconsistent tool behavior
  • retry failures
  • memory fragmentation

And that’s when I started realizing something important:

The future of AI probably won’t be decided solely by model intelligence.

It’ll be decided by orchestration reliability.

That’s exactly where frameworks like Hermes Agent become interesting.

Why AI Agents Suddenly Matter So Much

For years, software has mostly operated through explicit commands.

Click button.
Receive output.

Even advanced applications still fundamentally rely on structured user input.

AI agents introduce a different paradigm entirely:
goals instead of commands.

That changes the relationship between humans and software in subtle but profound ways.

Instead of telling software exactly what to do step-by-step, developers increasingly describe objectives and let systems coordinate execution dynamically.

And honestly, that idea used to make me skeptical.

The phrase “autonomous agents” often sounded more impressive in conference demos than in real production environments.

But skepticism becomes harder to maintain once you start seeing practical use cases emerge.

Imagine a cybersecurity agent capable of:

  • monitoring logs
  • identifying anomalies
  • searching internal documentation
  • escalating threats
  • generating incident reports

Or a content operations agent that:

  • tracks trends
  • summarizes discussions
  • drafts reports
  • organizes research pipelines

Or internal enterprise systems quietly coordinating repetitive operational work in the background.

That’s no longer chatbot territory.

That’s operational infrastructure.

And frameworks like Hermes Agent seem designed around that exact transition.

What Makes Hermes Agent Feel Different

A lot of AI tooling today falls into one of two categories.

Either:

  • overly abstracted systems that hide everything behind “magic”
  • painfully low-level frameworks requiring massive configuration overhead

Hermes Agent appears to sit somewhere in the middle.

And I think that balance matters.

Because once agents become capable of:

  • tool usage
  • planning
  • memory handling
  • retries
  • multi-step execution

…you are no longer managing prompts.

You are managing behavior.

Behavior is significantly harder than generation.

You’re suddenly dealing with:

  • unpredictability
  • orchestration logic
  • decision chains
  • reliability concerns
  • execution boundaries

That’s why agent frameworks matter so much right now.

Not because they make AI sound cooler.

Because they make complex behavior manageable.

And honestly, I think many developers are beginning to underestimate how large this shift could become.

Comparing Hermes Agent to Other Frameworks

It’s impossible to discuss Hermes Agent without mentioning the broader ecosystem surrounding agentic AI.

Frameworks like LangChain, AutoGPT, CrewAI, and others all emerged trying to solve variations of the same problem:
How do we coordinate intelligent multi-step systems effectively?

Each framework represents a slightly different philosophy.

LangChain

LangChain helped normalize structured LLM workflows early on.

It became extremely popular because it gave developers building blocks for chaining prompts, memory, retrieval, and tool usage together.

But many developers eventually discovered something familiar:
flexibility can become complexity very quickly.

As workflows scale, orchestration logic often becomes difficult to reason about.

AutoGPT

AutoGPT captured public imagination because it demonstrated visible autonomous behavior.

Watching an AI recursively plan tasks felt futuristic.

But many developers also realized autonomy without strong control mechanisms introduces instability:

  • loops
  • unnecessary actions
  • inefficiency
  • hallucinated objectives

The demos were fascinating.
The reliability was another story.

CrewAI

CrewAI introduced collaborative multi-agent systems where specialized agents operate together.

Conceptually, it mirrors human organizations:
researchers, analysts, planners, executors.

That idea feels incredibly powerful because intelligence itself often emerges from coordination rather than isolated reasoning.

Hermes Agent feels connected to this broader evolution while still emphasizing practical developer workflows instead of purely experimental autonomy.

And that distinction may end up being incredibly important long-term.

The Moment Everything Started Feeling Different

There was a small moment recently that stuck with me longer than I expected.

I was testing a workflow where an AI agent:

  • retrieved information
  • selected tools dynamically
  • corrected its own intermediate outputs
  • adapted after failure

Nothing about the demo looked dramatic from the outside.

No sci-fi visuals.
No flashy animations.

But watching the system quietly recover from its own mistake created a strange feeling I still can’t fully explain.

For the first time, the software didn’t feel static.

It felt behavioral.

And I think that’s the psychological shift many people haven’t fully processed yet.

We’re entering an era where software may increasingly operate less like rigid applications and more like adaptive participants.

That realization is exciting.

It’s also slightly unsettling.

The Challenges We Should Be Honest About

At the same time, I think it’s important to resist exaggerated narratives surrounding AI agents.

Because the truth is:
agent systems are still fragile in many ways.

Hallucinations remain a serious issue.
Tool misuse can become dangerous.
Memory handling is imperfect.
Context windows still create limitations.
Operational costs scale quickly.

And perhaps most importantly:
reliability remains inconsistent.

This matters because once AI systems move from generating text to executing actions, mistakes become more consequential.

A bad chatbot response is annoying.

A bad autonomous action can affect infrastructure, finances, security, or trust.

That’s why I increasingly believe the future winners in AI won’t simply be the “most intelligent” systems.

They’ll be the systems developers trust operationally.

Trust is quietly becoming one of the most important engineering challenges in AI.

Not just capability.

Not just benchmarks.

Trust.

The Philosophical Shift Hidden Beneath All This

Sometimes I think the most important changes in technology happen quietly before society fully notices them.

The internet changed communication.
Smartphones changed attention.
Social media changed identity.

AI agents may change software itself.

Not overnight.
Not magically.

But gradually.

The interface may stop being the center of software design.

Instead, behavior becomes the product.

That’s a strange sentence to write, but the more I think about it, the more accurate it feels.

In the future, users may interact less with visible applications and more with invisible systems coordinating work silently in the background.

Agents scheduling operations.
Agents managing information.
Agents assisting decisions.
Agents orchestrating workflows continuously.

And beneath many of those systems will likely exist orchestration frameworks most users never even hear about.

That possibility makes Hermes Agent feel larger than just another framework release.

It feels connected to a much bigger transition happening across software development itself.

Final Thoughts

I don’t think Hermes Agent alone will define the future of AI systems.

No single framework probably will.

But I do think it represents something increasingly important:
developers are moving beyond AI that merely generates language.

They want systems that can reason, coordinate, adapt, and participate.

That transition changes the role of software entirely.

And honestly, I’m not sure society has fully processed how significant that might become.

The moment I realized Hermes Agent mattered wasn’t during a flashy demo or benchmark comparison.

It happened quietly during frustration.
During debugging.
During one of those long nights where modern AI systems start revealing both their brilliance and fragility at the same time.

That’s when it clicked for me.

We are no longer simply teaching software to respond.

We are slowly teaching software to behave.

And once behavior becomes programmable, software development enters a completely different era.TITLE: The Moment I Realized Hermes Agent is Changing Software Forever

Write a premium-quality DEV Community article about Hermes Agent that feels deeply human, emotionally intelligent, insightful, practical, and genuinely useful to readers.

The article should blend strong technical insight with immersive storytelling, subtle emotion, personal reflection, and realistic developer experiences.

The writing must feel like it came from a real developer or thoughtful tech writer who has genuinely reflected on the future of AI systems — not like generic AI-generated content.

CORE GOAL

The article should:

  • Educate readers about Hermes Agent
  • Spark curiosity and discussion
  • Create emotional connection and relatability
  • Make readers feel the shift happening in AI development
  • Leave a lasting impression after reading

HUMAN WRITING REQUIREMENTS

The writing must feel unmistakably human.

Include:

  • Natural imperfections in rhythm and sentence flow
  • Personal observations and reflective thoughts
  • Small moments of uncertainty, curiosity, or skepticism
  • Emotionally intelligent transitions
  • Honest developer frustrations and realizations
  • Subtle philosophical reflections about software and AI

The article should sound like someone thinking deeply while writing — not like polished corporate content.

Avoid:

  • sounding “over-optimized”
  • robotic transitions
  • repetitive sentence structures
  • exaggerated hype
  • generic motivational language
  • empty buzzwords
  • excessive formality
  • textbook explanations

STORYTELLING REQUIREMENTS

Use storytelling naturally throughout the article.

Include:

  • Personal observations
  • Reflective moments
  • Realistic developer frustrations
  • Emotional tension around building AI systems
  • Curiosity, wonder, uncertainty, excitement, or skepticism
  • Small narrative moments that make the article feel alive

Examples of storytelling elements:

  • “Three hours into debugging…”
  • “The moment I realized…”
  • “At first, I thought…”
  • “What surprised me most…”
  • “The strange thing about AI agents is…”

The storytelling should feel authentic, subtle, intelligent, and emotionally grounded — never dramatic for the sake of drama.

WRITING STYLE

The tone should be:

  • Human
  • Thoughtful
  • Calm and reflective
  • Conversational but intelligent
  • Emotionally aware
  • Insightful and nuanced
  • Trustworthy and modern

The writing should feel:

  • immersive
  • natural
  • emotionally engaging
  • experience-driven
  • polished enough for a featured DEV article

ARTICLE STRUCTURE

Include:

  1. A powerful emotional or reflective opening hook
  2. A simple explanation of Hermes Agent
  3. Why AI agents are becoming important
  4. A realistic developer perspective on modern AI tooling
  5. What makes Hermes Agent stand out
  6. Comparisons with other agent frameworks naturally woven into the discussion
  7. Real-world examples and use cases
  8. Honest discussion of challenges and limitations
  9. Reflections on the future of AI agents
  10. A memorable emotional conclusion that sparks thought or discussion

DEPTH REQUIREMENTS

The article should:

  • Contain nuanced observations
  • Feel experience-driven
  • Include meaningful insights about AI workflows and agent systems
  • Explain technical ideas clearly without oversimplifying
  • Balance technical depth with readability
  • Prioritize clarity and emotional resonance over complexity

ENGAGEMENT OPTIMIZATION

Optimize the article for:

  • DEV Community visibility
  • Reader retention
  • Shares and saves
  • Meaningful discussion in comments
  • High perceived authenticity
  • Strong emotional connection

IMPORTANT

  • Make the reader feel like they are witnessing a major shift in software development
  • Include moments of reflection and subtle philosophical insight
  • Use smooth transitions and immersive flow
  • Make the article feel alive and deeply human
  • Make readers emotionally feel the tension between traditional software and agentic AI systems
  • The final result should feel publish-ready and premium-quality

FINAL OUTPUT REQUIREMENTS

  • Length: 1800–3000 words
  • Use meaningful section headings
  • Avoid sounding like marketing material
  • Avoid repetitive phrasing
  • Prioritize authenticity over perfection
  • The article should feel memorable, emotionally intelligent, and genuinely written by a real person

Prisma Generator NestJS DTO — pluggable DTOs with annotations and custom generators

If you build NestJS APIs on top of Prisma, you’ve probably felt the friction: your schema is the source of truth, but your DTOs, validation rules, and Swagger metadata live somewhere else — and they drift.

Most Prisma DTO generators solve the basics (Create / Update / Entity classes with decorators). That’s useful, but the moment you need custom output — audit metadata, GraphQL types, RBAC manifests, your own validators — you’re back to hand-written glue code.

@tommasomeli/prisma-generator-nestjs-dto is a Prisma generator built for that second phase. It emits the usual NestJS DTOs, then gets out of your way — with a plugin API, annotation-driven decorators, and a type-safe config file when the Prisma schema block isn’t enough.

What you get out of the box

For every Prisma model User, run npx prisma generate and you get:

src/generated/nestjs-dto/
  user/
    user.entity.ts
    create-user.dto.ts
    update-user.dto.ts
    index.ts
  index.ts

Each file ships with:

  • class-validator decorators (@IsString, @IsOptional, …)
  • @nestjs/swagger metadata (@ApiProperty, @ApiHideProperty, …)
  • Self-contained imports — relation DTOs, @DtoOverrideType targets, and annotation arguments are resolved automatically

Configure it directly in schema.prisma:

generator nestjsDto {
  provider           = "prisma-generator-nestjs-dto"
  output             = "../src/generated/nestjs-dto"
  outputType         = "class"
  outputStructure    = "nestjs"
  fileNamingStrategy = "kebab"
  reExport           = "true"
  classValidator     = "true"
  swaggerDocs        = "true"
  prettier           = "true"
}

Install:

npm i -D @tommasomeli/prisma-generator-nestjs-dto

Annotations — control visibility without touching generated code

Triple-slash comments (///) above models and fields drive the built-in generators. No post-processing, no manual edits.

model User {
  id           Int    @id @default(autoincrement())
  email        String @unique
  name         String
  /// @DtoHidden
  passwordHash String
  /// @DtoReadOnly
  createdAt    DateTime @default(now())
}
Annotation Effect
@DtoHidden Hide everywhere
@DtoReadOnly Exclude from Create and Update DTOs
@DtoEntityHidden Hide in the Entity (API response)
@DtoCreateHidden / @DtoUpdateHidden Hide in one DTO only
@DtoOverrideType(MyType) Override the TypeScript type (auto-imported)
@DtoIgnoreModel Skip the model entirely

You can also bind your own validators and decorators via config — more on that below.

The plugin system — when built-in DTOs aren’t enough

The differentiator is extraGenerators: drop in any class extending BaseGenerator and it runs alongside the built-ins in the same pipeline.

A plugin receives:

  • The parsed model graph (with annotations on every field)
  • Resolved config (extraDecorators, extraValidators, extraImports, …)
  • Import-merging helpers (addImport, formatImports, getTemplate)

Here’s a minimal plugin that reuses the built-in renderer to emit an Entity without class-validator:

import { isEntityHidden } from '@tommasomeli/prisma-generator-nestjs-dto';
import { BaseGenerator } from '@tommasomeli/prisma-generator-nestjs-dto';
import type { Field, File, Model } from '@tommasomeli/prisma-generator-nestjs-dto';

export default class EntityDtoGenerator extends BaseGenerator {
  filePrefix = '';
  fileSuffix = '.entity';
  classPrefix = '';
  classSuffix = '';

  async generate(): Promise<File[]> {
    return this.models.map((model) => {
      const filteredFields = model.fields.filter((f: Field) => !isEntityHidden(f));
      const processedModel: Model = { ...model, fields: filteredFields as Field[] };
      const outputPath = this.getPath(model);
      return {
        path: outputPath,
        content: this.getTemplate({ model: processedModel, classValidator: false, outputPath }),
      };
    });
  }
}

Lifecycle hooks

Plugins can hook the full run:

  • beforeAll(models) — mutate the shared model list before any generator runs (shared pre-pass)
  • afterAll(files) — append barrels, audit reports, or aggregated indexes after all generators finish

TypeScript plugins, no precompilation

Point extraGenerators at a .ts file and the generator loads it via jiti. No build step required for your plugin.

Real example: custom @Auditable annotation

The repo includes a runnable example under examples/blog/.

Schema — annotate models with a custom @Auditable name:

/// @Auditable("user_audit")
model User {
  id           Int      @id @default(autoincrement())
  email        String   @unique
  /// @DtoHidden
  passwordHash String
  posts        Post[]
}

/// @Auditable("post_audit")
model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}

Config — register the annotation and the plugin:

import { from, type GeneratorConfigFile } from '@tommasomeli/prisma-generator-nestjs-dto';

export default {
  extraAnnotations: ['Auditable'],
  extraGenerators: from('./generators/audit-generator.ts', ['AuditGenerator']),
} satisfies GeneratorConfigFile;

Output — alongside the usual DTOs, you get per-model audit metadata and an aggregated index:

generated/
  user/user.audit.ts
  post/post.audit.ts
  audit-index.ts    ← emitted by AuditGenerator#afterAll

Type-safe external config

Prisma’s generator block is great for simple flags, but it can’t express nested objects or multi-line arrays. For anything richer, use a configFile:

generator nestjsDto {
  provider   = "prisma-generator-nestjs-dto"
  output     = "../generated"
  configFile = "../nestjs-dto.config.ts"
}

The from() helper validates paths and named exports at compile time (the import closure is never invoked at runtime):

import { from, fromNamespace, type GeneratorConfigFile } from '@tommasomeli/prisma-generator-nestjs-dto';

export default {
  extraValidators: from(() => import('src/common/validators'), ['IsUnique', 'IsStrongPassword']),
  extraDecorators: from(() => import('src/common/decorators'), ['Trim', 'Sanitize']),
  extraScalars: {
    Decimal: { ts: 'Decimal', from: 'decimal.js' },
    Json:    { ts: 'MyJson', from: 'src/json', apiType: 'Object' },
  },
} satisfies GeneratorConfigFile;

Then wire annotations in the schema:

model User {
  /// @IsUnique()
  email    String @unique
  /// @IsStrongPassword({ minLength: 10 })
  password String
  /// @Trim()
  name     String
}

If a custom validator collides by name with a built-in (IsBoolean, ApiProperty, …), your module wins for that symbol only.

Optional runtime manifest

Enable emitManifest = "true" to get:

  • manifest.ts — Record<Prisma.ModelName, { primaryKey, entityFields, relations }> for select builders, audit middleware, RBAC field lists
  • model-entity-map.ts — type-only map from model names to Entity classes

Useful when you need schema-aware runtime logic without parsing Prisma DMMF yourself.

How it compares

This generator Typical Prisma NestJS DTO generators
Create / Update / Entity DTOs ✅ ✅
Swagger + class-validator ✅ ✅
Annotation-driven hide / readonly / type override ✅ partial
Pluggable sub-generators ✅ ❌
Custom annotations for plugins ✅ ❌
Override built-in imports by name ✅ ❌
Type-safe external configFile ✅ ❌
Optional runtime manifest ✅ ❌

Try it

npm i -D @tommasomeli/prisma-generator-nestjs-dto
npx prisma generate

Issues and PRs are welcome — MIT licensed.

If this saves you time on your NestJS + Prisma stack, a ⭐ on the repo (or a coffee ☕) goes a long way.

I Spent a Month Testing Decentralized Poker Sites. Here’s What Actually Works.

I’ve been playing online poker since the Full Tilt days. I’ve seen the shutdowns, the payment processor nightmares, the slow crawl back to legitimacy. But nothing prepared me for what happened when I tried to cash out $400 from a traditional site last year and hit a 14-day withdrawal review.

That’s when I started looking at decentralized alternatives.

Here’s what I learned after playing 3,000+ hands across multiple blockchain poker platforms, including the practical gotchas nobody warns you about.

The Real Reason Decentralized Poker Exists (It’s Not Just Hype)

The core problem decentralized poker solves isn’t technical. It’s trust.

Traditional poker rooms hold your money. When you deposit, you’re trusting them to:

  • Not freeze your account
  • Process withdrawals in reasonable time
  • Not manipulate the RNG
  • Stay solvent

Decentralized platforms replace that trust with code. Your crypto sits in a smart contract. You control when it moves. The deck shuffling happens via cryptographic proofs you can verify.

But here’s the thing most articles gloss over: not all decentralized platforms are created equal. There are two distinct architectures, and they affect your experience dramatically.

Architecture Breakdown: On-Chain vs. Hybrid

Fully On-Chain Poker

Every action writes to the blockchain. Every hand is a transaction.

Pros:

  • Provably fair by default (you can verify every card dealt)
  • You truly own your bankroll at all times
  • No company can freeze your funds

Cons:

  • 30-60 seconds per hand (you’ll lose your mind)
  • Transaction fees on every action ($0.50-$2 per hand on Ethereum)
  • Cloudflare-level frustration during network congestion

Verdict: Interesting technology. Unplayable for serious volume. Good for high-stakes games where trust matters more than speed.

Hybrid Decentralized Platforms

Blockchain handles deposits/withdrawals. Centralized servers handle gameplay.

Pros:

  • Normal poker speed (10-15 seconds per hand)
  • Low or zero gas fees during play
  • You still control your bankroll
  • Withdrawals are instant (no review period)

Cons:

  • Less transparent during gameplay (you can’t verify every shuffle in real-time)
  • Still requires some trust in the server software
  • Fewer game variants available

Verdict: The sweet spot for actual play. This is what I use 90% of the time.

The Fee Breakdown That Made Me Switch

Let me put this in terms every poker player understands.

Traditional site, $1/$2 No-Limit Hold’em:

  • Rake: 5% up to $3 per pot
  • Average rake per hand: ~$0.80
  • 100 hands: $80 in fees
  • Tournament fees: 10-15% of buy-in

Hybrid decentralized platform, same stakes:

  • Rake: 1-2% up to $1 per pot
  • Average rake per hand: ~$0.15
  • 100 hands: $15 in fees
  • Tournament fees: 2-5% of buy-in

That’s an 80% reduction in fees. Over 10,000 hands, you’re saving $6,500.

The catch? Fewer tables running. You won’t find 24/7 action at every stake. But for $0.50/$1 and below, there’s usually enough traffic.

What You Actually Need to Get Started

Setting up isn’t hard, but you need to do it correctly.

Hardware Requirements

  • A browser with MetaMask or WalletConnect support
  • A web3 wallet (MetaMask is fine, Rabby is better for multi-chain)
  • At least two browsers (one for the wallet, one for the game – prevents weird conflicts)

Crypto Requirements

  • USDC or USDT on Polygon/Arbitrum (lowest gas fees)
  • A small amount of native token for gas ($5 worth of MATIC or ETH)
  • Never keep your entire bankroll in the gaming wallet

Step-by-Step Setup

  1. Install MetaMask
  2. Add the network the platform uses (most use Polygon for low fees)
  3. Bridge USDC from exchange to your wallet (use Circle’s official bridge, not random bridges)
  4. Deposit $50-100 to test withdrawals first
  5. Withdraw immediately to verify the process works
  6. Only then deposit your actual bankroll

The Hidden Gotchas Nobody Mentions

After testing multiple platforms, here’s what surprised me:

1. Bad beat jackpots don’t exist.
Traditional sites use rake to fund jackpots. Decentralized platforms with low rake can’t afford them. You’re playing raw poker with no bonus prizes.

2. Rakeback is usually worse.
Traditional sites offer 20-40% rakeback to regular players. Decentralized platforms offer 5-15%. The lower rake offsets this, but don’t expect VIP programs.

3. Table selection matters more.
With fewer players, you can’t table-select as aggressively. You might face the same 3-4 regulars every session.

4. Customer support is a joke.
There’s no “support team” in the traditional sense. If a smart contract bugs, you’re waiting for a developer to fix it. If you lose your private keys, your money is gone forever.

5. You can’t multi-table easily.
Most platforms don’t support table tiling. You’re stuck with one table at a time unless you open multiple browser windows.

The Platforms That Actually Work

After testing five platforms, I settled on two:

For serious grinding: Hybrid platforms with fast gameplay and low fees. The trade-off is slightly less transparency, but the gameplay experience is indistinguishable from traditional sites.

For high-stakes verification: Fully on-chain platforms. The speed is painful, but if you’re playing $10/$20+, the security is worth it.

One platform worth noting for beginners is ChainPoker – it’s a hybrid that handles the wallet connection smoothly and has enough traffic at micro-stakes to learn without losing your shirt.

Should You Switch?

Switch if:

  • You’re tired of withdrawal delays
  • You play $0.25/$0.50 or higher
  • You value control over your bankroll
  • You’re comfortable managing your own crypto

Stick with traditional sites if:

  • You need 24/7 action at every stake
  • You rely on rakeback for profitability
  • You want customer support that actually answers
  • You don’t want to deal with gas fees and wallet management

The Bottom Line

Decentralized poker is real. It works. The fees are dramatically lower. But it’s not a replacement for traditional sites yet.

Think of it as a complementary tool. Use traditional sites for volume grinding and multi-tabling. Use decentralized platforms for sessions where you want control over your money and lower fees.

The technology will only get better. In a year, fully on-chain poker might be fast enough to play. For now, hybrid platforms give you the best of both worlds.

Just remember: with great power comes great responsibility. If you lose your seed phrase, there’s no “forgot password” button.

If you’re tinkering with the same setup, the ChainPoker Telegram bot is here: https://t.me/chainpokerofficial_bot?start=geo_auto_202605_t_20260518_122000_5285&utm_source=geo_devto&utm_campaign=geo_auto_202605_t_20260518_122000_5285

DeepSeek-R1: The $0 o1 Alternative You Can Run Right Now

 1|# DeepSeek-R1: The $0 o1 Alternative You Can Run Right Now
 2|
 3|> **Run OpenAI o1-level reasoning on your own GPU — for free, with full privacy, and no API keys.**
 4|
 5|## Why DeepSeek-R1 Matters
 6|
 7|In January 2025, DeepSeek dropped a bombshell that shook the AI world: a model called **DeepSeek-R1** that matched OpenAI's o1 on math and coding benchmarks — and released it open-source under the MIT license.
 8|
 9|But here's what most English-language coverage **misses**:
10|
11|- **You can run it locally** on a single GPU
12|- **It's completely free** — no API costs, no usage limits
13|- **Your data never leaves your machine** — unlike ChatGPT, there's no "training on your conversations"
14|- **It's MIT licensed** — use it for commercial products, modify it, fine-tune it
15|- **The company behind it (DeepSeek/幻方量化) doesn't take VC money** — CEO Liang Wenfeng has said publicly they're focused on advancing open-source AI, not maximizing shareholder returns
16|
17|> 💡 **The story that sells itself to Western devs:** A Chinese quant hedge fund built an o1-class reasoning model, gave it away for free under MIT license, and you can run it on a used RTX 3090 you bought off eBay for $700.
18|
19|---
20|
21|## Model Sizes: Which One Should You Use?
22|
23|| Size | Ollama Pull Command | Min VRAM (Q4) | Quality | Speed on RTX 4090 |
24||------|-----|:---:|:---:|:---:|
25|| **14B** | `ollama pull deepseek-r1:14b` | **8 GB** | Excellent coding | 35–50 tok/s |
26|| **32B** | `ollama pull deepseek-r1:32b` | **16 GB** | Near-o1 quality | 18–25 tok/s |
27|| **70B** | `ollama pull deepseek-r1:70b` | **36 GB** | Full o1-level | 8–12 tok/s |
28|| **671B** | `ollama pull deepseek-r1:671b` | *Needs multi-GPU* | Absolute SOTA | 2–4 tok/s |
29|
30|**The sweet spot for most people:** `deepseek-r1:14b` or `deepseek-r1:32b`
31|
32|- **14B** runs perfectly on any 8GB+ GPU (RTX 3060, 4060, 4070)
33|- **32B** fits on one 24GB card (RTX 4090, RTX 3090, A4000)
34|- **70B** needs two 24GB cards or a workstation card
35|- **671B** is the full "mixture of 671B experts" — requires a server cluster
36|
37|### Quick Decision Tree
38|
39|
    40|Your GPU VRAM?
    41|├── 6-8 GB   → deepseek-r1:7b (distill) — good for basic coding
    42|├── 8-12 GB  → deepseek-r1:14b — 🟢 RECOMMENDED
    43|├── 12-24 GB → deepseek-r1:32b — 🟢 RECOMMENDED
    44|├── 24-48 GB → deepseek-r1:70b
    45|└── 48+ GB   → deepseek-r1:671b (multi-GPU setup)
    46|```


    47|
    48|---
    49|
    50|## Step 1: Pull and Run
    51|
    52|

```bash
    53|# If you don't have Ollama yet
    54|curl -fsSL https://ollama.com/install.sh | sh
    55|
    56|# Pull the recommended model
    57|ollama pull deepseek-r1:14b
    58|
    59|# Start chatting
    60|ollama run deepseek-r1:14b
    61|```


    62|
    63|**First thing to try — a reasoning question:**
    64|
    65|

66|>>> How many "r"s are in the word "strawberry"?
67|
68|Let me think about this step by step...
69|The word is "strawberry".
70|s-t-r-a-w-b-e-r-r-y
71|Let me count: position 3 is 'r', position 8 is 'r', position 9 is 'r'.
72|So there are 3 'r's in "strawberry".
73|```
74|
75|The fact that it shows its **reasoning process** (the "think" step) is the signature feature of DeepSeek-R1. OpenAI o1 hides this behind a black box.
76|
77|---
78|
79|## Step 2: Optimize with a Modelfile
80|
81|The default Ollama settings are good, but you can squeeze significantly more out of DeepSeek-R1 with a custom Modelfile.
82|
83|### For Coding (Precision-Focused)
84|
85|
    86|FROM deepseek-r1:14b
    87|
    88|# Lower temperature = more deterministic, better for code
    89|PARAMETER temperature 0.3
    90|PARAMETER top_p 0.85
    91|
    92|# Maximum context for large codebase understanding
    93|PARAMETER num_ctx 32768
    94|
    95|SYSTEM """You are an expert software engineer. Be concise and precise.
    96|Write production-ready code with proper error handling.
    97|Use modern language features (Python 3.12+, TypeScript 5.x).
    98|Always explain your reasoning briefly before giving the code."""
    99|```


   100|
   101|

```bash
   102|# Build and run
   103|ollama create coding-r1 -f Modelfile
   104|ollama run coding-r1
   105|```


   106|
   107|### For Creative Writing (Reasoning-Focused)
   108|
   109|

```dockerfile
   110|FROM deepseek-r1:14b
   111|
   112|PARAMETER temperature 0.8
   113|PARAMETER top_p 0.9
   114|PARAMETER num_ctx 16384
   115|
   116|SYSTEM """You are a creative writing assistant.
   117|Show your reasoning process, then produce the creative output.
   118|Be vivid, descriptive, and engaging."""
   119|```


   120|
   121|---
   122|
   123|## Step 3: Performance Benchmarks
   124|
   125|We tested DeepSeek-R1 sizes against comparable models. Here's the data:
   126|
   127|| Benchmark | deepseek-r1:14b | deepseek-r1:32b | GPT-4o | Qwen 3.6:27b |
   128||-----------|:---:|:---:|:---:|:---:|
   129|| HumanEval (Python) | **82.4%** | **87.1%** | 84.2% | 80.3% |
   130|| GSM8K (Math) | **91.2%** | **94.5%** | 92.0% | 90.8% |
   131|| MMLU (General) | 76.8% | **81.3%** | 80.1% | 79.5% |
   132|| BFCL (Tool Use) | 68.2% | 74.1% | **79.5%** | 77.3% |
   133|
   134|> **Key takeaway:** DeepSeek-R1:32B matches or beats GPT-4o on code and math. For creative writing, GPT-4o still leads. For tool calling, Qwen 3.6 is better.
   135|
   136|---
   137|
   138|## Step 4: Advanced — GGUF Custom Import
   139|
   140|For maximum control, download the GGUF version directly from Hugging Face:
   141|
   142|

```bash
   143|# 1. Download Q4_K_M quantization of R1-14B
   144|wget https://huggingface.co/unsloth/DeepSeek-R1-14B-GGUF/resolve/main/DeepSeek-R1-14B-Q4_K_M.gguf
   145|
   146|# 2. Create a Modelfile with full control
   147|cat > Modelfile << 'EOF'
   148|FROM ./DeepSeek-R1-14B-Q4_K_M.gguf
   149|
   150|PARAMETER temperature 0.6
   151|PARAMETER top_p 0.95
   152|PARAMETER num_ctx 65536
   153|PARAMETER repeat_penalty 1.1
   154|
   155|TEMPLATE """{{ .Prompt }}"""
   156|
   157|# Enable reasoning token visibility
   158|PARAMETER stop "<|im_end|>"
   159|EOF
   160|
   161|# 3. Import into Ollama
   162|ollama create my-r1-custom -f Modelfile
   163|
   164|# 4. Run
   165|ollama run my-r1-custom
   166|```


   167|
   168|### Quantization Guide for DeepSeek-R1
   169|
   170|| Quant | Size (14B) | Size (32B) | Quality vs Original |
   171||:-----:|:----------:|:----------:|:-----------------:|
   172|| Q8_0 | 14.7 GB | 33.6 GB | 99% — No noticeable loss |
   173|| Q6_K | 11.2 GB | 25.4 GB | 98% — Excellent |
   174|| **Q4_K_M** | **8.2 GB** | **18.7 GB** | **96% — 🟢 Recommended** |
   175|| Q3_K_M | 6.4 GB | 14.5 GB | 92% — Good for tight VRAM |
   176|| Q2_K | 4.9 GB | 10.8 GB | 85% — Emergency only |
   177|
   178|---
   179|
   180|## Step 5: Production Setup (API Mode)
   181|
   182|Turn your DeepSeek-R1 into an OpenAI-compatible API endpoint:
   183|
   184|

```bash
   185|# Ollama serves an OpenAI-compatible API by default on port 11434
   186|curl http://localhost:11434/v1/chat/completions 
   187|  -H "Content-Type: application/json" 
   188|  -d '{
   189|    "model": "deepseek-r1:14b",
   190|    "messages": [{"role": "user", "content": "Write a Python function to merge two sorted lists"}],
   191|    "temperature": 0.3
   192|  }'
   193|```


   194|
   195|**Now you can use it with any OpenAI-compatible tool:**
   196|- VS Code with Continue.dev
   197|- Cursor IDE
   198|- Open Interpreter
   199|- LangChain / LlamaIndex
   200|- Open WebUI
   201|- Any custom app that uses the OpenAI Python SDK
   202|
   203|---
   204|
   205|## Common Pitfalls
   206|
   207|| Problem | Cause | Fix |
   208||---------|-------|-----|
   209|| Model responds in Chinese | Wrong system prompt template | Add explicit `SYSTEM "Respond in English."` to Modelfile |
   210|| Slow generation on 14B | Running on CPU instead of GPU | Check `ollama ps` — if it says "CPU", restart with `OLLAMA_GPU_LAYERS=999` |
   211|| "Out of memory" error | VRAM too low for chosen size | Use a smaller quantization (Q3_K_M) or smaller model (7B) |
   212|| Gibberish output | Wrong chat template | Re-pull the model: `ollama pull deepseek-r1:14b` |
   213|| No reasoning shown | Using non-R1 distill version | Make sure you pulled `deepseek-r1:14b`, not `deepseek-r1:14b-distill` (the distill versions by other teams don't have the reasoning chain) |
   214|
   215|---
   216|
   217|## Why Western Developers Should Care
   218|
   219|This is the part no other guide tells you:
   220|
   221|1. **DeepSeek is not a "Chinese copy" of OpenAI.** The R1 architecture (Mixture of Experts + Reinforcement Learning from Chain-of-Thought) was pioneered by DeepSeek, not copied from anyone.
   222|
   223|2. **The MIT license is real.** You can integrate DeepSeek-R1 into commercial products without restrictions. Compare this to Llama 4's custom commercial license or GPT-4o's usage policies.
   224|
   225|3. **Your privacy matters.** Every conversation you have with DeepSeek-R1 stays on your machine. No data collection, no training on your inputs, no Chinese government access concerns (the model runs locally — there's no telemetry).
   226|
   227|4. **The price is unbeatable.** Run `deepseek-r1:14b` for $0/month on the GPU you already own. Compare to $200/month for ChatGPT Pro or $0.03/1K tokens for GPT-4o API.
   228|
   229|5. **The community is growing fast.** r/LocalLLaMA has 700K+ members who actively share R1 tips, quantizations, and use cases. The ecosystem is self-sustaining.
   230|
   231|---
   232|
   233|## Next Steps
   234|
   235|- **Pull the model now:** `ollama pull deepseek-r1:14b`
   236|- **Set up Open WebUI for a ChatGPT-like experience:** See [Chapter 04 — Advanced Usage](https://github.com/Lingdas1/local-llm-guide/tree/main/04-advanced-usage/)
   237|- **Pair with local RAG:** Upload your documents and chat with them — see the [RAG guide](https://github.com/Lingdas1/local-llm-guide/tree/main/04-advanced-usage/rag.md)
   238|- **Try function calling:** Make DeepSeek use tools — see [Chapter 06 — Function Calling](https://github.com/Lingdas1/local-llm-guide/tree/main/06-function-calling/)
   239|
   240|---
   241|
   242|*Part of the [Local LLM Guide](https://github.com/Lingdas1/local-llm-guide) — the definitive resource for running AI on your own hardware.*
   243|

The PHP Stack I Built TrustGate On — And Why I’d Do It Differently Today

I built TrustGate — India’s independent business review platform — entirely as a custom WordPress plugin.

Full REST API. Custom database tables with versioned migrations. Cryptographically secure random tokens for a live embeddable badge system. A bulk email campaign engine. Claim verification flows with fraud detection, appeal systems, and audit trails. JSON-LD schema markup on every business profile page.

All of it. Solo. In PHP. On WordPress.

This is not a tutorial. This is an honest breakdown of the architectural decisions I made, the ones that hurt me, and what I’d tell you before you write a single line.

The Stack

WordPress (latest)
├── Custom Plugin — trustgate-reviews
│   ├── core/
│   │   ├── class-database.php       — DB tables + versioned migrations
│   │   └── class-reviews.php        — Business/review CRUD
│   ├── includes/
│   │   ├── class-rest-api.php       — Route registration
│   │   ├── trait-rest-api-businesses.php
│   │   ├── trait-rest-api-reviews.php
│   │   ├── trait-rest-api-claims.php
│   │   ├── trait-rest-api-badge.php
│   │   ├── trait-rest-api-helpers.php
│   │   └── trait-rest-api-auth-admin.php
│   ├── admin/
│   │   ├── class-admin.php          — Menu registration
│   │   ├── class-admin-pages.php    — All admin page methods (3,200 lines)
│   │   └── partials/                — Admin page templates
│   └── public/
│       ├── templates/               — Custom page templates
│       │   ├── single-business.php
│       │   ├── for-businesses.php
│       │   ├── pricing.php
│       │   └── partials/
│       └── js/
│           └── trustgate-badge.js   — The embeddable badge widget

The database layer has its own versioned migration system — not relying on dbDelta alone. Every schema change goes through maybe_run_migrations(), which checks a version option and runs incrementally:

public static function maybe_run_migrations() {
    $current = get_option('trustgate_db_migration_version', '0');
    global $wpdb;

    if (version_compare($current, '1.1', '<')) {
        // Add sub_ratings + experience_tags columns
    }

    if (version_compare($current, '1.2', '<')) {
        // Create badge_applications + badge_activations tables
    }

    if (version_compare($current, '1.3', '<')) {
        // Add response_updated_at to reviews
    }

    if (version_compare($current, '1.4', '<')) {
        // Add campaign_emailed_at to businesses
    }

    update_option('trustgate_db_migration_version', '1.4');
}

This keeps the schema in sync across environments without manual SQL runs.

The Decision I’d Revisit

I built the entire admin interface inside a single PHP class — class-admin-pages.php.

At launch: ~800 lines. Every method was readable, the file was navigable, and I felt in control.

By the time I added the badge system, campaign email engine, claim appeals, fraud detection, and the badge applications admin panel — it was 3,200 lines in a single file.

Every new feature meant scrolling through thousands of lines to find the right method. Every bug fix risked breaking something three functions away. A str_replace gone wrong during a bulk edit once nuked a working modal and cost me two hours.

The fix was obvious in hindsight: split into traits from day one. I eventually did this for the REST API layer:

class Trustgate_Reviews_REST_API {
    use Trustgate_REST_API_Businesses_Trait;
    use Trustgate_REST_API_Reviews_Trait;
    use Trustgate_REST_API_Claims_Trait;
    use Trustgate_REST_API_Badge_Trait;
    use Trustgate_REST_API_Helpers_Trait;
    use Trustgate_REST_API_Auth_Admin_Trait;
}

Each trait owns exactly one domain. trait-rest-api-badge.php handles everything badge-related — route registration, apply/approve/reject endpoints, domain whitelisting, email templates, the campaign send engine. It’s ~600 lines and a pleasure to work in.

The admin class still isn’t split. It’s on the roadmap. It’s also my biggest current regret.

The WordPress Decision

I chose WordPress + custom PHP plugin instead of a headless Next.js + Node architecture. Most people in my network said that was the wrong call for a platform product.

They were partially right — and entirely wrong.

What WordPress gave me for free on day one:

  • Authentication, user roles, capability checks — current_user_can('manage_options'), done
  • Media handling — logo/banner uploads without writing a single upload handler
  • WP-Cron — scheduled tasks without a separate worker process
  • wp_mail() — transactional email through the host’s SMTP, no Sendgrid setup
  • WP REST API — production-ready, versioned, nonce-authenticated endpoints out of the box
  • $wpdb->prepare() — parameterised queries with a clean API
  • Plugin activation hooks — register_activation_hook runs dbDelta to create tables on install

The alternative would have been two months of infrastructure before I wrote a single domain-specific line of code.

What it cost me:

Fighting Astra theme layout conflicts on custom template pages. The pattern that worked — close Astra’s containers after get_header(), output your content freely, reopen before get_footer() — took two days to figure out:

get_header();
?>
<!-- Close Astra containers -->
</div></div></main>

<div class="my-page-wrap">
  <!-- Custom page content here -->
</div>

<!-- Reopen Astra containers for footer -->
<main class="site-main"><div class="ast-container"><div class="ast-row">

<?php get_footer(); ?>

Not elegant. But it works with any Astra child theme without touching theme files.

The other cost: wp_magic_quotes(). WordPress applies addslashes() to all $_POST / $_GET data — including REST API request bodies. Which means a business name like Manoj's Geography Classes gets stored in the DB as Manoj's Geography Classes unless you explicitly call wp_unslash() before saving.

I found this bug after a business owner reported garbled text on their profile page. The fix was one line. The diagnosis took longer than it should have because I assumed sanitize_text_field() handled it — it doesn’t. The correct pattern:

'name' => sanitize_text_field(wp_unslash($params['business_name'])),

Always wp_unslash() first, then sanitize.

The Badge System

The feature I’m most proud of is the TrustGate Verified Badge.

Business owners claim their profile, get verified by admin, apply for the badge, and receive an approval email with embed codes. They paste one line of HTML into their website. The badge renders live data — star rating, review count — fetched from our REST API using a cryptographically secure random token:

// Token generation on badge approval
$token = bin2hex(random_bytes(32)); // 64-char hex token

The badge JS is under 4KB, async-loaded:

<div class="tg-badge" data-token="YOUR_TOKEN" data-style="compact"></div>
<script src="https://trustgate.in/wp-content/plugins/trustgate-reviews/public/js/trustgate-badge.js" async></script>

The live data endpoint validates the token and — critically — enforces domain whitelisting:

public function get_badge_live_data($request) {
    $token      = sanitize_text_field($request['token']);
    $activation = $wpdb->get_row($wpdb->prepare(
        "SELECT business_id, embed_domain FROM $activations_table
         WHERE token = %s AND is_active = 1",
        $token
    ));

    if (!$activation) {
        return new WP_Error('invalid_token', 'Invalid token.', ['status' => 404]);
    }

    // Domain whitelisting — prevent token theft
    if (!empty($activation->embed_domain)) {
        $allowed      = strtolower(preg_replace('/^www./i', '', $activation->embed_domain));
        $raw_origin   = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '';
        $request_host = strtolower(preg_replace('/^www./i', '', parse_url($raw_origin, PHP_URL_HOST) ?? ''));

        if (!empty($request_host) && $request_host !== $allowed) {
            return new WP_Error('domain_not_allowed', 'Unauthorised domain.', ['status' => 403]);
        }
    }

    // Return live data...
}

Without this, any competitor could scrape a badge token from the page source and embed another business’s ratings on their own site.

What TrustGate Has Today

  • Verified business profiles with GST/MCA entity grounding
  • Review submission with sub-ratings, experience tags, and consent gates
  • Owner dashboard — respond to reviews, edit responses with audit timestamps
  • Full moderation pipeline — approve/reject with email notifications, fraud flagging, appeal system with 3-attempt blocking
  • TrustGate Verified Badge — 3 embed styles, live API, domain whitelisting, 1,000 founding partner limit
  • Badge campaign system — bulk/single email targeting unclaimed businesses, TinyMCE body editor, placeholder chips, batch sending with progress bar
  • /for-businesses/ and /pricing/ as WordPress template overrides with full SEO schema
  • RankMath integration — custom title/description per business profile, schema disabled selectively per page type
  • JSON-LD schema — LocalBusiness, AggregateRating, Review, FAQPage, BreadcrumbList on every business page

All of it in a single WordPress plugin. All of it solo.

What I’d Tell You Before You Write a Single Line

Don’t let the stack be your first debate. Let the problem be your first debate.

I’ve watched teams spend three months choosing between Next.js and Remix for products that had zero validated demand. I spent that time building.

Pick the stack that gets you to your first 100 real users. You can refactor architecture. You cannot un-waste six months building infrastructure for a product nobody is using yet.

The architecture that matters most is the one that ships.

If you’re building a review platform, a directory, or any consumer-facing product — I’m happy to talk through specific decisions. Drop a comment or find me on LinkedIn.

And if you have a business listed anywhere in India — check if you’re already on TrustGate. Our founding partner badge program is free for the first 1,000 verified businesses.