Be Your Own Cloud code · PART 3 — THE STACK
Chapter 16 Local AI: The Sanitiser and the Full Assistant
Going further than the chapter: The Sanitiser, Presidio, Ollama and Open WebUI: Going Deeper
Part A: The Sanitiser (Low-Hardware Tier)
# p. 70
from flask import Flask, request, render_template_string
import re
app = Flask(__name__)
SECRET = r'([A-Za-z0-9_]+_(KEY|TOKEN|SECRET|PASSWORD))\s*=.*'
# Order matters: emails before domains, so an address isn't half-
# redacted.
PATTERNS = [
(r'[\w.-]+@[\w.-]+\.\w+', 'EMAIL'),
(r'([0-9]{1,3}\.){3}[0-9]{1,3}', 'IP'),
(r'\b[\w.-]+\.(com|net|org|io|dev|xyz)\b', 'DOMAIN'),
]
def sanitize(text):
text = re.sub(SECRET, r'\1=REDACTED', text)
for pattern, label in PATTERNS:
# real value -> placeholder, for this one paste only
seen = {}
def number(match):
value = match.group(0)
if value not in seen:
seen[value] = 'REDACTED-%s-%d' % (label, len(seen) + 1)
return seen[value]
text = re.sub(pattern, number, text)
return text
PAGE = '''
<h2>Sanitizer</h2>
<form method="post">
<textarea name="input_text" rows="12" cols="80">
{{ input_text }}</textarea><br>
<button type="submit">Sanitize</button>
</form>
<h3>Result (nothing is saved):</h3>
<textarea rows="12" cols="80" readonly>{{ output_text }}</textarea>
'''
@app.route('/', methods=['GET', 'POST'])
def index():
input_text = ''
output_text = ''
if request.method == 'POST':
input_text = request.form.get('input_text', '')
output_text = sanitize(input_text)
return render_template_string(PAGE, input_text=input_text,
output_text=output_text)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # p. 71
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install flask
COPY app.py .
CMD ["python", "app.py"] # p. 71
services:
sanitizer:
build: .
container_name: sanitizer
restart: unless-stopped
networks:
- proxy # Chapter 8's shared network -- no host port published
networks:
proxy:
external: true # p. 72
Paste under services:, after the sanitizer service — the indentation is already right.
# Add these to the sanitizer's docker-compose.yml, so all three share
# a network.
# The front end calls http://presidio-analyzer:3000 -- no published
# ports needed.
presidio-analyzer:
image: mcr.microsoft.com/presidio-analyzer:2.2.362
restart: unless-stopped
presidio-anonymizer:
image: mcr.microsoft.com/presidio-anonymizer:2.2.362
restart: unless-stopped
# Presidio is API-first -- a paste-box front end like the Flask
# example above
# still needs to sit on top of it for a comfortable interface. Part B: The Full Assistant (Ollama + Open WebUI)
# p. 72
services:
ollama:
image: ollama/ollama:0.35.0
container_name: ollama
restart: unless-stopped
volumes:
- ./ollama-data:/root/.ollama
ports:
- '127.0.0.1:11434:11434'
# Uncomment for NVIDIA GPU passthrough:
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
open-webui:
image: ghcr.io/open-webui/open-webui:v0.11.4
container_name: open-webui
restart: unless-stopped
depends_on:
- ollama
environment:
- OLLAMA_BASE_URL=http://ollama:11434
volumes:
- ./webui-data:/app/backend/data
ports:
- '8090:8080' # p. 73
docker compose up -d
docker exec -it ollama ollama pull qwen2.5-coder:7b
# 16GB+ VRAM: try qwen2.5-coder:14b or deepseek-coder-v2:16b instead Reproduced exactly from Be Your Own Cloud, Chapter 16. The book explains what each line does and what to do when it doesn't work. About the book
Something here not working as the book describes? Tell the author.