T11-L04 · Run your own AI · Level 4 Integrator · 22-minute read, plus 3 hours of doing
The assistant everyone has to walk to
In a lab it's a Tuesday in October. Lena's research group has used its own assistant for three weeks: Open WebUI, a chat interface that runs on your own hardware, with a local model on the big workstation in room SB-07. It works. The trouble is where it works. Eight people walk to SB-07 to use it, two of them queue at lunchtime, and a colleague has asked Lena, twice, to "just open the port" so she can use it from her desk. Lena knows what that would mean: the whole group's unpublished work behind a login page on a network that several thousand people share.
In a company the same week, Jonas runs the pilot assistant for a six-person quality team on his laptop. It sends prompts to an EU model provider under the company's contract. When his laptop sleeps, the team's assistant is gone. When he's on holiday, it's gone for two weeks.
Both need the same thing: a server that colleagues reach from their own desks, that nobody outside the team can reach, and that keeps running when its builder is away.
You are at Level 4 Integrator. The server joins your organisation's real network and holds everyone's chats. A mistake here puts real work in front of people who should never see it.
After this you can
- Choose where the server runs and who may reach it, and get IT to agree the network, the address and the certificate.
- Set up Open WebUI, a model and Caddy with Docker Compose so only the HTTPS address answers.
- Move the pilot's accounts and chats onto the server and keep sign-up closed, with two named admins.
- Prove from a second device that the address works and that no other port answers.
- Back up the server, restore it once, and update it with a way back.
Before you start
- Books: T01-L01 for the four-quadrant method. T02-L04, where you set up Open WebUI for a team and wrote its record; this book is stage 3 of that record. T11-L03 for volume export and restore.
- A pilot that works: Open WebUI running in Heidelberg AI Desk (Desk for short), the free desktop app from T11-L03 that installs reviewed AI apps in containers on your own computer. It should hold the accounts and settings you want to keep. You also need a fresh volume export: one file with all of the pilot's data, made with
podman volume exportas T11-L03 shows. - A contact in IT who can give you a server (or approve a rented one), a network, a DNS name (the address people type, such as assistant.example.org) and a firewall rule. Ask them before you buy anything.
- The name of your data protection officer (DPO). The server will store every colleague's chats.
- A second device for testing, such as a colleague's laptop or your phone.
- About 3.5 hours: 22 minutes to read, about 3 hours of doing, spread over a week because IT will take a few days to answer.
Last verified 23 September 2026. Open WebUI, Ollama, Caddy and Docker are free. A small Hetzner cloud server costs €19.99 a month before VAT; a Hetzner GPU server with 24 GB of graphics memory, €214 a month plus €209 setup. Details for experts at the end of this book.
The idea in one page
A team server has one front door. Everything else stays inside.
colleague's browser
│ HTTPS, port 443 (the only way in)
▼
Caddy ──► Open WebUI ──► model
│ (Ollama on the server, or a provider's API)
▼
volume: accounts, chats, settings
Three programs run on the server, each in its own container, a sealed box that runs one program from a fixed image (the program at a set version). You met both in T11-L03.
- Caddy is a reverse proxy: the one program that answers requests from outside and passes them on to Open WebUI. It also gets and renews the certificate, the file that lets browsers check they're talking to your server and encrypt the connection. That's what HTTPS means.
- Open WebUI is the chat interface your team logs in to. Its accounts, chats and settings live in a volume, storage that survives when the container is replaced.
- The model runs in Ollama on the same server if you host it yourself, or at a provider you reach with an API key.
Docker Compose starts all three from one file, compose.yaml, which you keep and version like any other document. Only Caddy is published, meaning reachable from outside the server. Open WebUI and Ollama talk to Caddy over a private network that Docker creates inside the server. Ollama has no login at all, so if its port were reachable, anyone could use the model and skip every account you set up.
Around the server sit two more locks. A firewall, which your provider or IT runs, decides which computers may reach port 443 at all: the group's network segment, the office, or the company VPN (a private, encrypted tunnel into the office network). And SSH, the remote login administrators use, is open only to the two people who look after the server, from IT's admin network.
Four decisions aren't yours alone. Write each one down with the name of the person who made it:
| Decision | Who decides |
|---|---|
| Which network may reach the address; the firewall rules; the DNS name | IT |
| Where the certificate comes from | IT |
| How long chats are kept; whether personal data may go in | The data protection officer |
| Whether admins may read colleagues' chats, and who is told | The group leader or manager, with the works council, the elected staff body, where there is one |
You run the containers, the accounts, the backups and the updates.
Worked example: two moves to a server, four quadrants
The method comes from T01-L01. Here it is for Lena's group, then for Jonas's team.
Lab: a GPU server on the group's network segment
Structure it. Lena starts from the record she wrote in T02-L04, which already says "next: group network segment only, via IT (ticket 4471)". "Eight named people reach the assistant from their own desks, over HTTPS, and nobody else on the institute network can. Prompts and chats never leave the institute. The group can keep working when I'm away. A useful result: IT has signed the server record, a colleague has used it from her desk, and a restore from last night's backup works."
Understand it. She meets the IT contact for half an hour and learns three things.
First, IT would rather run the hardware than take SB-07 into the server room. They offer a virtual machine with a 24 GB graphics card in the institute data centre, with Ubuntu, a common server version of Linux, already installed, and the NVIDIA drivers the card needs. They'll patch the operating system. Lena looks after the containers.
Second, IT can put the server on the group's own network segment, the slice of the institute network that only the group's computers are on. The firewall between segments stays closed to everyone else.
Third, and this changes her plan: the server will never be reachable from the internet. Lena had planned to let Caddy fetch a free certificate from Let's Encrypt, a public certificate authority. That only works when Let's Encrypt can reach the server from outside. IT issues certificates for internal servers from the university's own certificate service instead, and hands over two files: the certificate and its private key, the secret half that proves the server is the one named in the certificate. Anyone with the key can pretend to be the server, so only root may read it.
IT also mentions that Open WebUI can use the university login through single sign-on (SSO): people log in with their normal university account, and when someone leaves the university, their access ends without Lena doing anything.
Back to Structure it: "Same goal, different parts. The certificate comes from IT, not from Caddy. Logins come from the university, not from passwords I hand out. And the model moves from LM Studio, a desktop app, to Ollama, which runs without a screen on a server."
She also checks the load. Eight people rarely type at once; at lunchtime maybe three. A 14 GB model on a 24 GB card leaves room for that. Measuring what happens when all eight type at once is a job for later (T11-L05).
Choose tools.
- Docker Engine with Compose to run the containers, because IT's Ubuntu servers already use it and Open WebUI's own instructions assume it. Podman, which Desk used, would also work; one tool across IT's servers matters more.
- Caddy as the front door, because its whole configuration for this job is three lines. Nginx, another widely used web server, is the alternative, with more to write.
- Ollama for the model, with gpt-oss-20b, an open-weight model (one whose files anyone may download and run) from OpenAI under the Apache 2.0 licence, which allows use at work without fees, and which fits the card with room to spare. Gemma 4, which the group used in LM Studio, is the alternative; its larger versions leave less room.
- The university login through SSO for everyone, and one local admin account kept in the password manager for the day SSO fails.
Build it. IT gives her SSH access to the server and the DNS name assistant.lindqvist.bio.example.org. She creates a folder /opt/assistant, owned by root, the server's administrator account.
She puts four things in it: the Compose file, a Caddyfile, a folder called certs with IT's two files, and openwebui.env, a file of settings that Open WebUI reads when it starts. One command makes the env file and the key readable by root alone:
chmod 600 /opt/assistant/openwebui.env /opt/assistant/certs/assistant.key
The env file holds the SSO settings. IT registers the assistant with the university login and gives her three values: the login service's address, a client ID (the assistant's name there) and a client secret (its password there). She asks IT to allow one return address, https://assistant.lindqvist.bio.example.org/oauth/oidc/callback, where the university login sends people back after they sign in.
WEBUI_URL=https://assistant.lindqvist.bio.example.org
ENABLE_OAUTH_SIGNUP=true
OAUTH_PROVIDER_NAME=University login
OPENID_PROVIDER_URL=<address from IT>
OAUTH_CLIENT_ID=<client ID from IT>
OAUTH_CLIENT_SECRET=<client secret from IT>
WEBUI_SECRET_KEY=<a long random string>
WEBUI_URL is the address people type. ENABLE_OAUTH_SIGNUP lets a person's first university login create their account. The provider name is the label on the login button.
The last line is the secret key Open WebUI signs logins with and encrypts stored secrets with. She creates it once with openssl rand -hex 32, which prints 64 random characters, and pastes it in. If you leave it out, Open WebUI makes its own key and keeps it inside the container, not in the volume, so a restore onto a new container would log everyone out. A copy of the env file goes into the institute's password vault.
This is the Compose file:
services:
caddy:
image: caddy:2.11.4
restart: unless-stopped
ports:
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./certs:/certs:ro
- caddy-data:/data
open-webui:
image: ghcr.io/open-webui/open-webui:v0.11.4
restart: unless-stopped
env_file: ./openwebui.env
environment:
OLLAMA_BASE_URL: http://ollama:11434
volumes:
- open-webui:/app/backend/data
ollama:
image: ollama/ollama:0.34.3
restart: unless-stopped
volumes:
- ollama:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
caddy-data:
open-webui:
name: open-webui
ollama:
Read it top to bottom. Each block under services is one container, started from the image named after image:, at a fixed version. The restart line brings it back after a reboot.
Only Caddy has a ports: line, so only Caddy is published, and only on 443. Open WebUI finds the model at http://ollama:11434, a name that exists only on the private network inside the server.
The deploy block gives Ollama the graphics card. The last lines keep the volume's name exactly open-webui, which the restore step needs.
The Caddyfile tells Caddy which name to answer for, which certificate to show and where to pass requests:
assistant.lindqvist.bio.example.org {
tls /certs/assistant.crt /certs/assistant.key
reverse_proxy open-webui:8080
}
Before anything starts, she brings the pilot across. On the workstation she stops Open WebUI in Desk, exports its volume to open-webui.tar as in T11-L03, and copies the file to /opt/assistant on the server with scp, the copy command that travels over SSH. On the server, one command creates the empty volume and unpacks the export into it:
docker volume create open-webui && docker run --rm -v open-webui:/data -v /opt/assistant:/backup alpine tar -xf /backup/open-webui.tar -C /data
The second half starts a throwaway container from alpine, a tiny Linux image, that sees both the volume and the folder, unpacks the file, and removes itself.
The pilot's settings travel in the volume, and two of them matter here. Sign-up with a password stays off. And every new account, including one created by a first university login, starts as pending: it can't see or do anything until an admin approves it.
Then she starts everything in the background:
cd /opt/assistant && docker compose up -d
and loads the model into Ollama, which downloads it once into the ollama volume:
docker compose exec ollama ollama pull gpt-oss:20b
Ollama can use the graphics card only if IT has installed the NVIDIA Container Toolkit, the package that hands the card to containers. She sends one test chat, then runs docker compose exec ollama ollama ps, which lists the loaded model. The PROCESSOR column says 100% GPU; if it named the CPU, she would ask IT for the toolkit.
She opens the Admin Panel (click your name at the bottom left, then Admin Panel) and checks three things. Under Settings, Authentication: new sign-ups are off and the default role for new users is pending. In the model list at the top of a new chat: gpt-oss-20b appears. Under Users: there are exactly two admins, Lena and Tom.
Test from a second device. Mira, a PhD student in the group, opens the address on her office PC. The browser shows the lock symbol with no warning, the university login appears, and her new account arrives as pending. Lena approves it under Users in the Admin Panel by changing Mira's role from pending to user. Then Mira tries the back doors. In the address bar she types http://assistant.lindqvist.bio.example.org:8080 (Open WebUI's own port) and then the same with :11434 (Ollama's port). Neither answers. From a laptop on the guest Wi-Fi, which isn't on the group's segment, even the HTTPS address doesn't load. That's the firewall doing its job.
Back up, restore, sign. A small script, /opt/assistant/backup.sh, stops Open WebUI for a few seconds, packs the volume into a dated file with the same alpine trick, starts it again, and copies the file to the group share, which IT backs up:
#!/bin/sh
cd /opt/assistant
f=open-webui-$(date +%F).tar
docker compose stop open-webui
docker run --rm -v open-webui:/data -v /opt/assistant/backups:/backup alpine tar -cf /backup/$f -C /data .
docker compose start open-webui
cp backups/$f /mnt/groupshare/assistant/
The last line's share path stands for wherever IT has connected the group share on the server. She makes the script runnable with chmod 700. Then she runs crontab -e as root, which opens the list of jobs that cron, the server's built-in timer, runs on schedule, and adds one line that runs the script at 02:30 every night:
30 2 * * * /opt/assistant/backup.sh
The model volume isn't backed up; it can be downloaded again.
On Friday Lena restores Thursday's file on a spare machine with the same Compose file and the unpack command above, and logs in with a test account. The chats are there.
She adds a Server section to the assistant record: the address, who may reach it, the image versions, the backup time and place, the date of the last restore test, and who does what. IT signs for the network and certificate. The DPO reads the chat line (kept six months, deleted at each review) and signs too, adding one condition: the backup files on the group share get the same six months.
What it taught her: the containers took an afternoon. The certificate question, which she hadn't known to ask, took most of the week.
Company: a small cloud server behind the office and the VPN
Structure it. Jonas: "Six people in quality reach the assistant from the office and from home. Nobody else on the internet can. The provider key stays on the server, not on laptops. A useful result: the external IT contractor has signed the server record, and a colleague has used it from home through the VPN."
Understand it. His team doesn't need a GPU: the model runs at the EU provider, so the server only carries Open WebUI and Caddy. He starts with a tutorial's Compose file on a test server, and then tests it the way Lena's colleague did, from his phone on mobile data. The HTTPS address works. So does the server's address followed by :3000, where Open WebUI answers directly, without encryption.
He had switched on ufw, Ubuntu's simple firewall, and blocked 3000. It made no difference. Docker's own documentation explains why: ports that Docker publishes bypass ufw's rules. The tutorial's ports: "3000:8080" line published Open WebUI to the whole internet.
He takes this to the external IT contractor, who adds a second point: the company already has a VPN, and every laptop has it. Back to Structure it: "Only Caddy is published. The provider's firewall, which sits outside the server where Docker can't change it, lets port 443 in from the office and the VPN, and nothing else."
One detail comes back from Caddy's documentation. Caddy gets its certificate from Let's Encrypt, and Let's Encrypt must reach the server on port 80 to check that the company controls the name. So port 80 stays open to everyone. Caddy answers there with nothing but that check and a redirect to HTTPS.
Choose tools.
- A Hetzner cloud server, type CPX22 (2 processors, 4 GB memory) in Nuremberg, because the company already rents from Hetzner under a processing agreement, the contract on how Hetzner handles the company's data, and Open WebUI without a local model needs little. OVHcloud is the alternative if the contractor prefers it.
- Hetzner's cloud firewall, set in its web console, for the two port rules. It runs outside the server, so no container can open a hole in it.
- The same Compose layout as Lena's, minus Ollama and the certificate files, with
portson Caddy for 80 and 443. - Local accounts in Open WebUI, because the company has no single sign-on. Removing a leaver's account goes on the HR leaving checklist, next to the email account.
Build it. The contractor creates the server in the company's Hetzner account, not in Jonas's, and the DNS name assistant.example-quality.de. In Hetzner's console he adds a firewall with three incoming rules and attaches it to the server: port 443 from the office's address and the VPN's address range, port 80 from anywhere, and port 22 (SSH) from the office and the VPN. Everything else coming in is dropped. He gives SSH access to two people: Jonas and himself.
Jonas's Compose file is Lena's with three changes: no Ollama service and no OLLAMA_BASE_URL line, no certs folder, and a second port, "80:80", on Caddy. Jonas copies his pilot's volume export across and restores it with the same command as Lena. The provider key comes with it, because Open WebUI stores its connection settings in the volume. His Caddyfile has two lines inside the braces fewer than Lena's:
assistant.example-quality.de {
reverse_proxy open-webui:8080
}
With no tls line, Caddy fetches and renews the certificate on its own.
He runs the phone test again. The HTTPS address loads only when the phone is on the VPN. :3000 and :8080 don't answer from anywhere. A colleague logs in from home through the VPN and drafts a change to a standard operating procedure (SOP), one of the team's written work instructions.
Two sign-offs follow. The contractor signs the server record. The managing director asks the works council, which in Germany must agree before a tool can be used to monitor employees, about one line, Admins can read all chats, because chat logs could be used to watch how people work. They agree on a sentence in the record: admins open a colleague's chats only for a support request from that colleague, and each time it happens it's written down.
What it taught him: he had trusted a firewall that Docker walked around. The test from outside was the only thing that showed him.
In both places the server itself was quick. The work was in learning who controls the network, the certificate and the logins, and in proving from outside that only one door was open.
Updates you can undo
Every image in the Compose file has a fixed version, such as v0.11.4. That label after the colon is the image's tag, and fixing it is deliberate. With a moving tag like latest, which always points at the newest release, a restart could quietly install a new version, and you'd have nothing to go back to.
An update is three steps. Take a backup right before it, because Open WebUI changes its database when a new version starts for the first time, and an older version may not read the changed database. Change the version in compose.yaml. Then pull the new image and restart:
docker compose pull && docker compose up -d
The first command downloads the images named in the file; the second replaces any container whose image changed and leaves the rest alone.
If something breaks, going back is the same in reverse: put the old version back in the file, restore the backup you took, and start again. Write each update into the record with the date, the old and new versions and who did it. For anything bigger than a small version step, try it first on a copy: restore last night's backup on a spare machine with the same Compose file, update the copy, and use it for a day.
What goes wrong
The port opened "just for a demo". Symptom: Open WebUI answers on its own port from other computers, without HTTPS. Fix: remove every ports: line except Caddy's, run docker compose up -d, and repeat the second-device test. Ports Docker publishes bypass ufw, so the provider's or IT's firewall is the one that counts.
The model with no lock. Symptom: Ollama answers on port 11434 from another machine, and someone uses the model without an account. Fix: Ollama never gets a ports: line. Open WebUI reaches it by name on the private network.
No certificate, or a browser warning. Symptom: the address shows a security warning, or Caddy's log says it couldn't get a certificate. Fix: for a public certificate, check that the DNS name points to the server and that ports 80 and 443 are open from outside. On an internal network, ask IT for a certificate and add the tls line. Never tell colleagues to click through a warning; it trains them to ignore the one that matters.
Everyone can sign up. Symptom: strangers, or people who left, appear in the user list. Fix: check in the Admin Panel, under Settings, Authentication, that new sign-ups are off and the default role is pending, and remove unknown accounts under Users. Settings like this are stored in the volume after the first start, so changing an environment variable later won't change them.
The server belongs to one person. Symptom: the rented server, the domain or the invoice sits in a private account, and nobody else can log in. Fix: the organisation's account, two named people with SSH, and the record in a place the team can read.
A backup nobody has restored. Symptom: the nightly file exists, but when the disk fails nobody knows how to use it. Fix: restore one backup onto another machine before colleagues depend on the server, and again after each large update.
The update that can't be undone. Symptom: after an update, Open WebUI won't start, and the old version won't read the database either. Fix: a backup taken right before every update, and fixed versions in the Compose file.
Do it yourself (3 hours, over one week)
- Structure it (15 min). Write one sentence: who needs to reach the assistant, from where, what may go in, and what a useful result looks like. Put it at the top of the assistant record from T02-L04.
- Understand it (45 min, plus IT's reply). Meet IT. Ask: where may the server run, which network may reach it, what DNS name it gets, where the certificate comes from, whether single sign-on is possible, and who may log in over SSH. Ask the DPO how long chats may be kept. If the answers change your sentence, rewrite it.
- Choose tools (15 min). Decide the server (IT's machine or a rented one in your organisation's account), the model (Ollama with an open-weight model, or your provider's API), and how people log in. Write one alternative for each.
- Build it (90 min). Create the folder with the Compose file, the Caddyfile and any env file, and make the env file and any key readable by root alone. Copy your pilot's export across and restore it into the volume. Start the stack and load the model. In the Admin Panel check sign-up, the default role and the two admins.
- Build it: test (15 min). From a second device on the allowed network, open the address and log in with an ordinary account. Type http:// and the server's name followed by the ports 8080, 3000 and 11434, as Mira did; none may answer. Then try the HTTPS address from a device outside the allowed network, such as a phone on mobile data.
- Build it: back up and sign (30 min). Adapt Lena's backup script and its cron line, run it once by hand, and restore the file on another machine. Add the Server section to the record. Take it to IT and the DPO to sign.
Exit check
Written finding: the Server section of your assistant's record, plus the result of a second-device test.
- The record names the address, the networks that may reach it, where the certificate comes from, the image versions, how people log in, the two admins, the chat retention, the backup time and place, the date of the last restore, and the update steps with the way back.
- The second-device test lists what you tried and what happened: the HTTPS address from an allowed network, the same address from outside it, and the Open WebUI and model ports.
It passes when the HTTPS address works from a colleague's computer with no browser warning, no other port answers from there, a restore from a recent backup has worked on another machine, and IT has signed the record. If chats may contain personal data, the DPO has signed it too.
Rule to remember
One front door, and it's the proxy.
Details for experts
Checked on 23 September 2026. You don't need this to follow the book; it's for setting things up or answering a colleague.
| What | Details | What it means for you |
|---|---|---|
| Versions | Open WebUI v0.11.4 (released 21 September 2026), Ollama 0.34.3, Caddy 2.11.4. | Pin these, or newer, in compose.yaml. Read the release notes before each step up. |
| Docker Engine | Supported on Ubuntu 22.04, 24.04 and 26.04 LTS (long-term support versions). Docker's documentation warns that ports it publishes bypass ufw rules. | Use the provider's or IT's firewall, and publish only Caddy. |
| GPU in containers | Ollama in Docker needs the NVIDIA Container Toolkit, the package that lets containers use the graphics card, installed and switched on for Docker. | On IT's machine, ask IT; on a rented one, it's the first step after the driver. |
| gpt-oss-20b | Ollama tag gpt-oss:20b, 14 GB download, 128K context (how much text it reads at once), Apache 2.0 licence. OpenAI says it runs in 16 GB of memory. | Fits a 24 GB card with room for a few chats at once. |
| Caddy certificates | Automatic HTTPS from Let's Encrypt or ZeroSSL (another free certificate authority) needs the DNS name to point at the server. The authority then proves you control the name in one of three ways: an HTTP check on port 80, a TLS check on port 443, both reachable from outside, or a DNS check, a record Caddy writes into your DNS, which needs a Caddy plugin for your DNS provider. | On an internal network, use IT's certificate with the tls line. |
| Open WebUI settings | ENABLE_SIGNUP (password sign-up, default on) and DEFAULT_USER_ROLE (role of new accounts, default pending) are stored in the database after the first start and then ignore the environment. WEBUI_URL must be set for SSO. For SSO through OpenID Connect, the standard most university and company logins speak: OPENID_PROVIDER_URL, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET; the return address is your address plus /oauth/oidc/callback. | Check sign-up in the Admin Panel, not in the env file. IT gives you the SSO values. |
| Hetzner cloud | CPX22: 2 AMD processors, 4 GB memory, 80 GB disk, €19.99 a month before VAT, in Nuremberg or Helsinki. The cloud firewall is free. | Enough for Open WebUI with a provider's model. |
| Hetzner GPU | GEX45: NVIDIA RTX PRO 4000 Blackwell with 24 GB, 64 GB memory, Helsinki only, €214 a month before VAT plus €209 setup, no minimum term. | The rented option if IT has no GPU server. Capacity for many users is in T11-L05. |
Further reading and tools
Next books
- T12-L04 · Lock Down a Self-Hosted AI Service Before Real Data Goes In: the full check that the server is locked before real data goes in.
- T11-L05: keeping the server fast enough and affordable when many people use it for months.
- T02-L04: the assistant record this book extends.
- T11-L03: volume export and restore.
- T06-L05: owning an app colleagues depend on for years.
Tools
- Open WebUI: Taught. The chat interface on the server.
- Caddy: Taught. The front door, with HTTPS.
- Docker: Taught. Runs the containers from one Compose file.
- Ollama: Taught. Runs the model on the lab's GPU server.
- gpt-oss-20b: Catalogued. The open-weight model in the lab example.
- Podman: Catalogued. The alternative container engine, used by Heidelberg AI Desk.
- OVHcloud: Catalogued. An alternative EU provider for a rented server.
- Heidelberg AI Desk: Catalogued. Where the pilot ran.