Your own AI stack means no API bills, full control, and models running locally. This guide gets Docker Compose, Ollama, Open WebUI, and n8n running in under 30 minutes.
Prerequisites
- Docker and Docker Compose installed (Docker Desktop on Windows/Mac, or Docker on Linux)
- 8GB+ RAM, preferably GPU support (NVIDIA with nvidia-runtime, or Apple Silicon)
- 20GB free disk space for models
- Basic terminal skills
Architecture Overview
Your Machine
βββ Ollama (port 11434)
β βββ LLM models (Llama 2, Mistral, etc.)
βββ Open WebUI (port 3000)
β βββ UI for Ollama + Claude API fallback
βββ n8n (port 5678)
β βββ Workflow automation
βββ PostgreSQL (port 5432)
βββ n8n database
Step 1: Create docker-compose.yml
Create a new directory for your stack:
mkdir ai-stack && cd ai-stack
touch docker-compose.yml
Add this to docker-compose.yml:
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
environment:
- OLLAMA_HOST=0.0.0.0:11434
volumes:
- ollama_data:/root/.ollama
# Optional: GPU support for NVIDIA
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
open-webui:
image: ghcr.io/open-webui/open-webui:latest
container_name: open-webui
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- OPENAI_API_KEY=${OPENAI_API_KEY:-} # Optional
depends_on:
- ollama
volumes:
- open_webui_data:/app/backend/data
postgres:
image: postgres:15-alpine
container_name: n8n-postgres
ports:
- "5432:5432"
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: n8n_password_change_me
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
image: n8nio/n8n:latest
container_name: n8n
ports:
- "5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=n8n_password_change_me
- N8N_HOST=localhost
- N8N_PORT=5678
- NODE_ENV=production
- WEBHOOK_URL=http://your-server:5678
- GENERIC_TIMEZONE=UTC
depends_on:
- postgres
volumes:
- n8n_data:/home/node/.n8n
volumes:
ollama_data:
open_webui_data:
postgres_data:
n8n_data:
Step 2: Start the Stack
docker-compose up -d
Verify containers are running:
docker-compose ps
Expected output:
NAME STATUS
ollama Up 2 minutes
open-webui Up 2 minutes
n8n-postgres Up 2 minutes
n8n Up 1 minute
Step 3: Pull Your First Model
Open terminal and pull Mistral (4.1GB, fast):
docker exec ollama ollama pull mistral
Or Llama 2 (4GB):
docker exec ollama ollama pull llama2
Models download to ollama_data volume. First pull takes 2-5 minutes.
Step 4: Test Open WebUI
- Open http://localhost:3000 in browser
- Create account (first user is admin)
- Model dropdown β select "mistral"
- Chat: "Hello, what is Docker?"
- Response should come from local Ollama
Step 5: Configure n8n
- Open http://localhost:5678
- Set up admin account (email + password)
- Decline "AI Agent" setup (optional)
- Dashboard β Credentials β New
- Add "Ollama" credential:
- Base URL:
http://ollama:11434 - Model:
mistral(or your pulled model) - Test connection
- Base URL:
Step 6: Create Your First Workflow
Simple: Chat to Ollama
- Dashboard β New Workflow
- Add node: "Webhook" (trigger)
- Method: POST
- Path:
/chat
- Add node: "Ollama" (after Webhook)
- Prompt:
{{ $json.message }} - Model:
mistral
- Prompt:
- Add node: "Respond to Webhook"
- Response:
{{ $json.response }}
- Response:
- Save, activate, copy webhook URL
Test with curl:
curl -X POST http://localhost:5678/webhook/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is an AI stack?"}'
Storage & Persistence
All data persists in Docker volumes:
# List volumes
docker volume ls | grep ai-stack
# Backup Ollama models
docker run --rm -v ollama_data:/data \
-v /path/to/backup:/backup \
ubuntu tar czf /backup/ollama-backup.tar.gz /data
# Restore
docker run --rm -v ollama_data:/data \
-v /path/to/backup:/backup \
ubuntu tar xzf /backup/ollama-backup.tar.gz -C /data
Networking
Access from other machines:
# Find your machine IP
# Windows/Mac: ipconfig / ifconfig
# Linux: hostname -I
# Update docker-compose.yml:
# OLLAMA_HOST=0.0.0.0:11434 (already set)
# WEBHOOK_URL=http://YOUR_IP:5678
Common Issues
Port already in use:
# Change ports in docker-compose.yml
# ports:
# - "3001:8080" # Use 3001 instead of 3000
GPU not detected:
# Install nvidia-docker and update docker-compose.yml
docker-compose down
docker-compose up -d
docker exec ollama ollama list # Check if GPU is used
Out of memory:
# Reduce model size: pull a smaller variant
docker exec ollama ollama pull neural-chat # Smaller than mistral
Slow responses:
Check Ollama logs: docker logs ollama
Security Checklist
- Change
n8n_password_change_meto strong password - Set
WEBHOOK_URLto your actual domain (not localhost) - Behind firewall: don't expose ports 5432, 5678 to internet
- For production: use reverse proxy (nginx) with HTTPS
- Backup volumes weekly
Next Steps
- Import starter workflows from n8n templates
- Connect external APIs (email, Slack, webhooks)
- Scale to multi-GPU setup
- Set up monitoring with Prometheus + Grafana
Checklist
- Docker and Docker Compose installed
- docker-compose.yml created with 4 services
-
docker-compose up -dcompleted without errors - At least one model pulled (mistral/llama2)
- Open WebUI accessible at http://localhost:3000
- Can chat with local model in Open WebUI
- n8n admin account created
- Ollama credential added to n8n
- First webhook workflow created and tested
- Volumes backed up
Advanced: GPU Optimization
If you have an NVIDIA GPU (RTX 3080+), enable GPU acceleration:
# Updated docker-compose.yml (GPU section)
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
environment:
- OLLAMA_HOST=0.0.0.0:11434
- CUDA_VISIBLE_DEVICES=0
- OLLAMA_NUM_GPU=1
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
Verify GPU is used:
docker exec ollama nvidia-smi
# OR
docker exec ollama ollama list --verbose
Performance improvement: 5-10x faster inference with GPU.
Multi-GPU Setup (Advanced)
If you have 2+ GPUs:
ollama:
environment:
- OLLAMA_NUM_GPU=-1 # Use all GPUs
Load distribution (automatic via Ollama).
Apple Silicon Optimization
If running on Mac (Apple Silicon):
ollama:
# No changes neededβOllama detects Metal automatically
# VRAM allocation: up to 16GB shared memory
Performance: Near-GPU speed on M-series Macs.
Extending the Stack: Add More Services
Add Postgres for Data
postgres-data:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: changeme
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5433:5432" # Port 5433 (not 5432, n8n uses that)
Then in Open WebUI or n8n, add PostgreSQL data source.
Add Redis for Caching
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"
Use in n8n workflows to cache Ollama responses.
Add Minio (S3-Compatible Storage)
minio:
image: minio/minio:latest
container_name: minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio_data:/minio
command: minio server /minio --console-address :9001
Access at http://localhost:9001 (credentials: minioadmin/minioadmin)
Monitoring & Health Checks
Prometheus + Grafana for monitoring
Add to docker-compose.yml:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3001:3000" # Port 3001 (not 3000, open-webui uses that)
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
depends_on:
- prometheus
volumes:
- grafana_data:/var/lib/grafana
Create prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'ollama'
static_configs:
- targets: ['ollama:11434']
Access Grafana at http://localhost:3001 (admin/admin).
Health Check Script
#!/bin/bash
# health-check.sh
echo "Checking AI Stack health..."
# Ollama
if curl -s http://localhost:11434/api/tags > /dev/null; then
echo "β Ollama: healthy"
else
echo "β Ollama: OFFLINE"
exit 1
fi
# Open WebUI
if curl -s http://localhost:3000 > /dev/null; then
echo "β Open WebUI: healthy"
else
echo "β Open WebUI: OFFLINE"
exit 1
fi
# n8n
if curl -s http://localhost:5678/rest/health > /dev/null; then
echo "β n8n: healthy"
else
echo "β n8n: OFFLINE"
exit 1
fi
# PostgreSQL
if docker exec n8n-postgres pg_isready -U n8n > /dev/null 2>&1; then
echo "β PostgreSQL: healthy"
else
echo "β PostgreSQL: OFFLINE"
exit 1
fi
echo ""
echo "All systems operational! β"
Run: chmod +x health-check.sh && ./health-check.sh
Scaling: Move to Cloud GPU
For production workloads, move Ollama to cloud:
RunPod GPU Rental
# Pull model on RunPod (cheaper)
runpod-cli start-pod --gpu-id a40-large --template ollama
# Instead of local http://localhost:11434
# Use RunPod endpoint: https://your-pod.runpod.io:11434
Update n8n:
Ollama Base URL: https://your-pod.runpod.io:11434
Cost: β¬0.30-0.50/hour vs. β¬2000 GPU purchase.
Lambda Labs GPU
Similar setup:
# Rent GPU instance
# SSH in, run docker-compose
docker-compose up -d
# Get public IP
# Update n8n with http://public-ip:11434
Disaster Recovery
Backup Script
#!/bin/bash
# backup-stack.sh
BACKUP_DIR="/path/to/backups"
DATE=$(date +%Y-%m-%d)
echo "Backing up AI Stack..."
# Backup n8n database
docker exec n8n-postgres pg_dump -U n8n n8n > \
$BACKUP_DIR/n8n-db-$DATE.sql
# Backup Ollama models
docker run --rm -v ollama_data:/data -v $BACKUP_DIR:/backup \
ubuntu tar czf /backup/ollama-$DATE.tar.gz /data
# Backup Open WebUI
docker run --rm -v open_webui_data:/data -v $BACKUP_DIR:/backup \
ubuntu tar czf /backup/webui-$DATE.tar.gz /data
# Cleanup old backups (keep last 7 days)
find $BACKUP_DIR -mtime +7 -delete
echo "Backup complete!"
Cron job: 0 2 * * * /path/to/backup-stack.sh (daily at 2 AM)
Restore from Backup
# Restore n8n database
docker exec -i n8n-postgres psql -U n8n n8n < n8n-db-2026-03-21.sql
# Restore Ollama
docker run --rm -v ollama_data:/data -v /path/to/backups:/backup \
ubuntu tar xzf /backup/ollama-2026-03-21.tar.gz -C /data
# Restart services
docker-compose restart ollama open-webui n8n
Production Checklist
- GPU enabled if available
- Monitoring set up (Prometheus + Grafana)
- Health checks automated
- Backups running daily
- Restore tested (quarterly)
- HTTPS reverse proxy in front (nginx)
- Firewall configured (only necessary ports exposed)
- Resource limits set (Docker memory caps)
- Error logging centralized (ELK stack optional)
- Model updates automated (Ollama auto-pull)
Troubleshooting Advanced Issues
Ollama OOM on larger models
# Reduce model size
docker exec ollama ollama pull mistral:7b # Smaller variant
# OR increase swap
docker update --memory 16g ollama
n8n crashes on large workflows
# Increase Node.js heap
docker exec -e NODE_OPTIONS=--max-old-space-size=4096 n8n npm start
Open WebUI slow
# Check Ollama queue
docker logs ollama | grep "queue"
# Reduce concurrent connections
# Settings β Max Concurrent Requests
Next Steps for Production
- Add authentication (OAuth, LDAP)
- Set up CDN for Open WebUI (CloudFlare)
- Implement request rate limiting
- Add log aggregation (ELK, Loki)
- Set up alerts (PagerDuty, Slack)
- Document runbooks for common failures
- Schedule quarterly disaster recovery tests
