This guide picks up directly from the Ollama on Proxmox video. We’re going to add two more services on top of the Ollama server: Open WebUI for a browser-based chat interface to your local models, and n8n for self-hosted workflow automation with a Postgres backend. Everything runs in Docker on the same Ubuntu VM, connected through Tailscale sidecars for HTTPS access. Nothing phones home.
To generate the n8n encryption key, run this on your Linux server:
Replace
Click Get Started, create your admin account with a strong password, and you’re in. Open WebUI automatically detects the Ollama server you pointed it to โ your available models (e.g. llama3.2) will appear in the model selector at the top of the chat interface. Select a model and start chatting.
Register your admin account on first login. You can also register for a free n8n community licence key โ it unlocks a few extra features without any cost. Skip the setup survey and go straight to the workflow editor.
To verify Ollama is connected, create a simple test workflow:
๐ฅ Watch the Video Tutorial
๐ก What We’re Building
By the end of this guide you’ll have three services working together:- Open WebUI โ a browser-based chat interface that connects directly to your Ollama server and presents your local LLMs as a clean, familiar chat UI
- n8n โ a self-hosted workflow automation platform with a visual node editor. Think Zapier or Make, but running entirely on your own hardware with no per-execution fees
- Postgres โ a dedicated database backend for n8n. More reliable under load than n8n’s default SQLite storage and easier to back up
โน๏ธ Note: Prerequisite: Ollama must already be running on a Proxmox VM and accessible via Tailscale. See: Run AI Locally: Install Ollama on Proxmox with Ubuntu and Tailscale
๐ What You’ll Need
- The Ubuntu Docker VM from the previous Docker guide โ with Portainer, Watchtower, and Tailscale already configured
- Ollama running on a separate VM and accessible over Tailscale on port 11434
- A Tailscale auth key (reusable key works for a home lab โ in production, use per-device keys)
- Open WebUI โ free, open source
- n8n โ free community edition
๐ Step-by-Step Setup
1. The Docker Compose stack overview
The new stack has four services: Postgres (n8n’s database), n8n itself, the n8n Tailscale sidecar, Open WebUI, and the Open WebUI Tailscale sidecar. All services share a dedicated bridge network on the172.20.4.0/24 subnet.
Static IP addresses are assigned to each service within that subnet. This avoids port conflicts with the existing base stack โ services on different networks can use the same port numbers without clashing.
๐ก Tip: The Postgres Alpine image is used rather than the full Postgres image. It has a significantly smaller footprint โ important when you’re stacking multiple services on the same host.
2. The environment files
Two environment files are used, and the order matters: the second file overrides the first. This pattern lets you keep a template.env file in version control with placeholder values, and a separate secrets.env file with the actual credentials that never gets committed:
/media/docker/scripts/.envโ base variables: config path, Tailscale key placeholder, timezone, user ID/media/docker/scripts/secrets.envโ the real values that override the placeholders: actual Tailscale auth key, n8n database password, n8n encryption key
# .env (safe to commit โ placeholder values only) CONFIG=/media/docker/configs TS_KEY=tskey-auth-example TZ=Australia/Melbourne PUID=1000 PGID=1000 N8N_DB_PASSWORD=changeme N8N_ENCRYPTION_KEY=changeme
# secrets.env (never commit โ real values) TS_KEY=tskey-auth-xxxxxxxxxxxxx N8N_DB_PASSWORD=your-strong-db-password N8N_ENCRYPTION_KEY=your-generated-key
openssl rand -hex 32
โ ๏ธ Warning: The n8n encryption key is tied to your n8n installation. If you lose it and have to regenerate it, you lose access to all saved credentials stored in n8n. Store it somewhere safe alongside your other important credentials.
3. The Docker Compose file
The full compose file covers all five services. The key configuration points for each:Postgres (n8n database)
n8n-postgres:
image: postgres:16-alpine
container_name: n8n-postgres
restart: unless-stopped
env_file:
- /media/docker/scripts/.env
- /media/docker/scripts/secrets.env
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
volumes:
- ${CONFIG}/n8n/postgres:/var/lib/postgresql/data
networks:
ai_network:
ipv4_address: 172.20.4.2
labels:
- "com.centurylinklabs.watchtower.enable=true"
n8n
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
env_file:
- /media/docker/scripts/.env
- /media/docker/scripts/secrets.env
user: "${PUID}:${PGID}"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_HOST=n8n-postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- WEBHOOK_URL=https://n8n..ts.net
- TZ=${TZ}
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
- N8N_BINARY_DATA_STORAGE_PATH=/home/node/.n8n/binaryData
volumes:
- ${CONFIG}/n8n/data:/home/node/.n8n
network_mode: "service:n8n-tailscale"
depends_on:
- n8n-postgres
- n8n-tailscale
labels:
- "com.centurylinklabs.watchtower.enable=true"
n8n Tailscale sidecar
n8n-tailscale:
image: tailscale/tailscale:latest
container_name: n8n-tailscale
restart: unless-stopped
hostname: n8n
environment:
- TS_AUTHKEY=${TS_KEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_SERVE_CONFIG=/config/serve.json
- TS_STATE_DIR=/var/lib/tailscale
volumes:
- ${CONFIG}/n8n/tailscale:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
networks:
ai_network:
ipv4_address: 172.20.4.3
Open WebUI
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
env_file:
- /media/docker/scripts/.env
environment:
- OLLAMA_BASE_URL=http://:11434
volumes:
- ${CONFIG}/open-webui:/app/backend/data
network_mode: "service:open-webui-tailscale"
depends_on:
- open-webui-tailscale
labels:
- "com.centurylinklabs.watchtower.enable=true"
<ollama-tailscale-ip> with the Tailscale IP of your Ollama VM. This is how Open WebUI reaches Ollama โ directly over the Tailscale network rather than through your local LAN.
Open WebUI Tailscale sidecar
open-webui-tailscale:
image: tailscale/tailscale:latest
container_name: open-webui-tailscale
restart: unless-stopped
hostname: open-webui
environment:
- TS_AUTHKEY=${TS_KEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
volumes:
- ${CONFIG}/open-webui/tailscale:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
networks:
ai_network:
ipv4_address: 172.20.4.4
networks:
ai_network:
driver: bridge
ipam:
config:
- subnet: 172.20.4.0/24
4. The build script
Rather than runningdocker compose up -d manually each time, a build script handles the full startup sequence including folder ownership โ which is the most common source of container restart loops:
#!/bin/bash # Set env file paths ENV_FILE=/media/docker/scripts/.env SECRETS_FILE=/media/docker/scripts/secrets.env # Bring down if already running docker compose --env-file $ENV_FILE --env-file $SECRETS_FILE down # First bring up โ creates the config folder structure docker compose --env-file $ENV_FILE --env-file $SECRETS_FILE up -d # Bring back down to fix folder ownership before n8n starts properly docker compose --env-file $ENV_FILE --env-file $SECRETS_FILE down # Fix ownership of n8n and open-webui config folders sudo chown -R 1000:1000 /media/docker/configs/n8n sudo chown -R 1000:1000 /media/docker/configs/open-webui # Bring up for real docker compose --env-file $ENV_FILE --env-file $SECRETS_FILE up -d # Wait for Tailscale sidecars to register sleep 60 # Enable HTTPS via Tailscale serve docker exec n8n-tailscale tailscale serve --bg http://localhost:5678 docker exec open-webui-tailscale tailscale serve --bg http://localhost:8080 # Verify services are up docker compose ps
โ ๏ธ Warning: The up โ down โ chown โ up again sequence is essential. If n8n starts before its config folder ownership is corrected, it creates files as root and then can’t write to them as the configured user โ causing an infinite restart loop. The build script handles this automatically.
Make the script executable before running it:
sudo chmod +x build.sh ./build.sh
5. Approve the new Tailscale devices
While the 60-second sleep runs, open your Tailscale admin console. You’ll see n8n and open-webui appear as new devices. If you haven’t set your auth key to auto-approve, approve them now. I also disable key expiry on these devices โ they’re home lab services that need to stay connected indefinitely.6. Access Open WebUI
Once the build script completes, open a browser and go to your Open WebUI Tailscale URL:https://open-webui..ts.net
7. Access n8n and create your first workflow
Open n8n at its Tailscale URL:https://n8n..ts.net
- Click New Workflow โ Start from scratch
- Click the + to add a node โ search for HTTP Request
- Method: POST
- URL:
http://<ollama-tailscale-ip>:11434/api/generate - Body Content Type: JSON
- Body:
{"model": "llama3.2", "prompt": "Explain what Docker is in one sentence.", "stream": false} - Click Save then Execute
๐ก Tip: Set
The response from Ollama appears in the output panel at the bottom of the n8n editor. Response time will vary depending on your hardware โ on CPU-only setups expect several seconds for a short prompt. The workflow works the same way at any speed.
stream: false in the request body. By default Ollama streams responses token by token, which makes the output very difficult to work with in n8n. With streaming disabled, n8n receives the complete response as a single JSON object that’s easy to parse in subsequent workflow nodes.