Oracle Local AI Provider
Section titled “Oracle Local AI Provider”Current production note: NutsNews currently uses the home-server local AI setup documented in Home Server Local AI Provider. This Oracle document remains as an alternative runbook if Oracle Free Tier capacity becomes available later.
This document describes the Oracle Free Tier alternative for running article review through a local AI service instead of sending every candidate article directly to OpenAI.
The Oracle alternative design is intentionally simple:
Cloudflare Worker Shards ↓ HTTPS + API keyOracle Free Tier VM ↓ localhostOllama ↓qwen2.5:3b or another local model ↓Supabase article_ai_reviews / articles / ai_usage_runsOpenAI support remains in place. The worker can run in OpenAI mode, local AI mode, or local AI mode with OpenAI fallback.
What This Oracle Alternative Adds
Section titled “What This Oracle Alternative Adds”Worker
Section titled “Worker”The Worker supports these variables:
AI_PROVIDER=openai | localLOCAL_AI_URL=https://ai.nutsnews.comLOCAL_AI_MODEL=qwen2.5:3bAI_REVIEW_CONCURRENCY=1AI_PROVIDER_FALLBACK_TO_OPENAI=true | falseThe Worker supports this secret:
LOCAL_AI_API_KEY=long-random-secret-shared-with-oracle-serviceWhen AI_PROVIDER=local, the Worker calls:
POST {LOCAL_AI_URL}/reviewHeader: x-nutsnews-ai-key: {LOCAL_AI_API_KEY}The local service returns the same decision shape used by the OpenAI review path:
{ "decision": "accept", "category": "Community", "positivity_score": 9, "summary": "Short summary here.", "reason": "Why the model accepted or rejected it.", "provider": "local", "model": "qwen2.5:3b", "prompt_tokens": 123, "completion_tokens": 45, "total_tokens": 168, "duration_ms": 21847}Supabase
Section titled “Supabase”The migration 20260621090000_add_ai_provider_tracking.sql adds provider/model tracking to:
article_ai_reviewsarticlesai_usage_runsImportant new fields:
ai_providerai_modelreview_duration_mslocal_ai_modellocal_ai_call_countlocal_ai_prompt_tokenslocal_ai_completion_tokenslocal_ai_total_tokenslocal_ai_accepted_countlocal_ai_rejected_countlocal_ai_duration_msNew admin visibility:
/admin/articles Shows whether OpenAI, qwen/local AI, or local rules processed each article.
/admin/local-ai Shows Oracle/local AI usage, model breakdown, fallback calls, latency, recent runs, and recent decisions.Recommended Rollout
Section titled “Recommended Rollout”Do not flip all shards at once.
Use this rollout:
Step 1: Deploy database migration.Step 2: Deploy web/admin updates.Step 3: Deploy worker updates still using AI_PROVIDER=openai.Step 4: Create Oracle VM.Step 5: Run Ollama and the local AI service.Step 6: Test one shard manually with AI_PROVIDER=local.Step 7: Watch /admin/local-ai.Step 8: Roll out to all shards only after quality looks good.Oracle VM Shape
Section titled “Oracle VM Shape”Recommended first VM:
Shape: VM.Standard.A1.FlexOCPU: 4Memory: 24 GBBoot volume: 100 GB or 150 GBOS: Ubuntu ARM64Use one larger VM instead of multiple small VMs. The model benefits from having all RAM and CPU available in one place.
Local AI Service
Section titled “Local AI Service”The local service lives in:
local-ai-service/Files:
local-ai-service/package.jsonlocal-ai-service/server.mjslocal-ai-service/.env.exampleRoutes:
GET /healthPOST /reviewThe service listens on localhost only:
127.0.0.1:8788Caddy should be the public HTTPS reverse proxy. Ollama should also stay local-only on:
127.0.0.1:11434Oracle Setup Steps
Section titled “Oracle Setup Steps”Step 1 — Create the VM
Section titled “Step 1 — Create the VM”In Oracle Cloud Console:
Compute → Instances → Create instanceUse:
Name: nutsnews-local-ai-prodImage: Ubuntu 24.04 ARM64 or Ubuntu 22.04 ARM64Shape: VM.Standard.A1.FlexOCPU: 4Memory: 24 GBBoot volume: 100 GB or 150 GBNetworking: assign public IPv4SSH key: upload or paste your public keyKeep the VM Always Free eligible.
Step 2 — Open Required Ports
Section titled “Step 2 — Open Required Ports”In the Oracle VCN security list or network security group, allow:
22/tcp from your IP80/tcp from 0.0.0.0/0443/tcp from 0.0.0.0/0Do not expose Ollama port 11434 publicly.
Do not expose Node port 8788 publicly if Caddy is reverse proxying locally.
Step 3 — SSH Into the VM
Section titled “Step 3 — SSH Into the VM”From your Mac:
ssh ubuntu@YOUR_ORACLE_PUBLIC_IPIf you used Oracle Linux instead of Ubuntu, the username is usually:
ssh opc@YOUR_ORACLE_PUBLIC_IPStep 4 — Update the VM
Section titled “Step 4 — Update the VM”sudo apt updatesudo apt upgrade -ysudo apt install -y curl git ufw ca-certificates gnupg unzipStep 5 — Install Node 20
Section titled “Step 5 — Install Node 20”curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -sudo apt install -y nodejsnode --versionnpm --versionStep 6 — Install Ollama
Section titled “Step 6 — Install Ollama”curl -fsSL https://ollama.com/install.sh | shsudo systemctl enable --now ollamaollama --versionStep 7 — Pull the First Model
Section titled “Step 7 — Pull the First Model”Start with the small model:
ollama pull qwen2.5:3bollama run qwen2.5:3b 'Return only JSON: {"ok":true}'If quality is not good enough and speed is acceptable, test later:
ollama pull qwen2.5:7bStep 8 — Copy the Local AI Service
Section titled “Step 8 — Copy the Local AI Service”From your Mac, inside the NutsNews repo:
rsync -av local-ai-service/ ubuntu@YOUR_ORACLE_PUBLIC_IP:/tmp/nutsnews-local-ai-service/On Oracle:
sudo mkdir -p /opt/nutsnews/local-ai-servicesudo rsync -av /tmp/nutsnews-local-ai-service/ /opt/nutsnews/local-ai-service/sudo chown -R ubuntu:ubuntu /opt/nutsnewscd /opt/nutsnews/local-ai-servicecp .env.example .envnano .envSet:
PORT=8788LOCAL_AI_API_KEY=make-this-a-long-random-secretOLLAMA_URL=http://127.0.0.1:11434OLLAMA_MODEL=qwen2.5:3bREQUEST_TIMEOUT_MS=120000MAX_ARTICLE_CHARS=6000Test it:
cd /opt/nutsnews/local-ai-servicenode server.mjsIn another SSH session:
curl http://127.0.0.1:8788/healthStop the test server with Ctrl+C.
Step 9 — Run the Service With systemd
Section titled “Step 9 — Run the Service With systemd”Create the service:
sudo nano /etc/systemd/system/nutsnews-local-ai.servicePaste:
[Unit]Description=NutsNews Local AI ServiceAfter=network-online.target ollama.serviceWants=network-online.target
[Service]Type=simpleWorkingDirectory=/opt/nutsnews/local-ai-serviceEnvironmentFile=/opt/nutsnews/local-ai-service/.envExecStart=/usr/bin/node /opt/nutsnews/local-ai-service/server.mjsRestart=alwaysRestartSec=5User=ubuntuGroup=ubuntuNoNewPrivileges=truePrivateTmp=true
[Install]WantedBy=multi-user.targetEnable it:
sudo systemctl daemon-reloadsudo systemctl enable --now nutsnews-local-aisudo systemctl status nutsnews-local-ai --no-pagerjournalctl -u nutsnews-local-ai -fStep 10 — Install Caddy for HTTPS
Section titled “Step 10 — Install Caddy for HTTPS”sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curlcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpgcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.listsudo apt updatesudo apt install -y caddyCreate a DNS record first:
ai.nutsnews.com → YOUR_ORACLE_PUBLIC_IPThen configure Caddy:
sudo nano /etc/caddy/CaddyfilePaste:
ai.nutsnews.com { reverse_proxy 127.0.0.1:8788}Reload:
sudo systemctl reload caddycurl https://ai.nutsnews.com/healthStep 11 — Enable the Worker Local AI Provider
Section titled “Step 11 — Enable the Worker Local AI Provider”The generated Wrangler config can include local AI variables when these shell variables are set before generation:
cd /Users/ramideltoro/WebstormProjects/nutsnews2/worker
export NUTSNEWS_SECRETS_STORE_ID="your-cloudflare-secrets-store-id"export AI_PROVIDER="local"export LOCAL_AI_URL="https://ai.nutsnews.com"export LOCAL_AI_MODEL="qwen2.5:3b"export AI_REVIEW_CONCURRENCY="1"export AI_PROVIDER_FALLBACK_TO_OPENAI="true"export ENABLE_LOCAL_AI_SECRET_BINDING="true"
npm run generate:wranglerAdd LOCAL_AI_API_KEY to your Cloudflare Secrets Store with the same value you put in Oracle .env.
Then deploy one shard first:
npx wrangler deploy -c generated-wrangler/wrangler.shard0.jsonccurl "https://nutsnews-worker-0.nutsnews.workers.dev/?limit=1"Watch:
/admin/local-ai/admin/articlesBetter Stack logsOracle: journalctl -u nutsnews-local-ai -fOnly after shard 0 looks good, deploy all shards:
npm run deploy:allPerformance Rules
Section titled “Performance Rules”Start with:
AI_REVIEW_CONCURRENCY=1LOCAL_AI_MODEL=qwen2.5:3bMAX_ARTICLE_CHARS=6000This protects the free Oracle CPU VM.
Increase slowly only after checking:
/admin/local-ai average review timeOracle CPU loadOracle memory usagefailed local AI requestsOpenAI fallback countUseful Oracle checks:
free -huptimetopjournalctl -u nutsnews-local-ai -n 100 --no-pagerjournalctl -u ollama -n 100 --no-pagerRollback
Section titled “Rollback”To switch back to OpenAI:
cd /Users/ramideltoro/WebstormProjects/nutsnews2/worker
export NUTSNEWS_SECRETS_STORE_ID="your-cloudflare-secrets-store-id"unset LOCAL_AI_URLunset LOCAL_AI_MODELunset AI_REVIEW_CONCURRENCYunset AI_PROVIDER_FALLBACK_TO_OPENAIunset ENABLE_LOCAL_AI_SECRET_BINDINGexport AI_PROVIDER="openai"
npm run generate:wranglernpm run deploy:allThe code keeps OpenAI support in place, so rollback is a config change.
Safety Notes
Section titled “Safety Notes”- Keep Ollama private on localhost.
- Require
x-nutsnews-ai-keyon/review. - Use HTTPS through Caddy.
- Start with one shard.
- Keep OpenAI fallback enabled until local quality is proven.
- Do not train a competing model from OpenAI/Claude-generated outputs. Use human-reviewed labels and public datasets for future fine-tuning.
