rface would be open to anyone with network access.
Implementation Steps
1. Initialize Project Structure
Create a dedicated directory for the stack. Separating configuration and data volumes improves maintainability.
mkdir -p ~/ai-gateway-stack/config
mkdir -p ~/ai-gateway-stack/data
cd ~/ai-gateway-stack
2. Configure Environment Variables
Create a .env file to manage sensitive and domain-specific values. This keeps the compose file clean and allows easy environment switching.
nano .env
Add the following variables:
APP_DOMAIN=ai-gateway.example.com
ACME_EMAIL=admin@example.com
APP_DOMAIN: The fully qualified domain name pointing to your server.
ACME_EMAIL: Email address for Let's Encrypt account registration and expiration notices.
3. Define Docker Compose Manifest
Create the docker-compose.yaml file. This manifest defines the proxy and the application service.
nano docker-compose.yaml
services:
proxy-edge:
image: traefik:v3.6
container_name: proxy-edge
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "letsencrypt-storage:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart: unless-stopped
chat-ui-service:
image: ghcr.io/open-webui/open-webui:main
container_name: chat-ui-service
hostname: chat-ui
expose:
- "8080"
volumes:
- "./data:/app/backend/data"
environment:
- WEBUI_AUTH=true
labels:
- "traefik.enable=true"
- "traefik.http.routers.ai-gateway.rule=Host(`${APP_DOMAIN}`)"
- "traefik.http.routers.ai-gateway.entrypoints=websecure"
- "traefik.http.routers.ai-gateway.tls.certresolver=letsencrypt"
- "traefik.http.services.ai-gateway.loadbalancer.server.port=8080"
restart: unless-stopped
volumes:
letsencrypt-storage:
Key Implementation Details:
expose: "8080" vs ports: The chat-ui-service uses expose instead of ports. This makes the port available to linked services but does not publish it to the host. Traefik can still route to it via the Docker network, but external traffic cannot bypass the proxy.
- Traefik Commands:
--providers.docker.exposedbydefault=false: Ensures only containers with explicit traefik.enable=true labels are exposed. This is a critical security default.
--entrypoints.web.http.redirections...: Forces all HTTP traffic to redirect to HTTPS, enforcing secure connections.
--certificatesresolvers...: Configures ACME with HTTP challenge for automatic certificate issuance.
- Labels on
chat-ui-service:
traefik.http.routers.ai-gateway.rule: Routes traffic based on the APP_DOMAIN.
traefik.http.routers.ai-gateway.tls.certresolver: Associates the router with the Let's Encrypt resolver.
traefik.http.services...: Defines the backend service and target port.
4. Launch and Verify
Start the stack in detached mode:
docker compose up -d
Verify service status:
docker compose ps
Check logs for certificate issuance:
docker compose logs -f proxy-edge
Look for messages indicating successful ACME certificate retrieval. Once the proxy is ready, navigate to https://<APP_DOMAIN> in your browser.
5. Initial Configuration
The first user to register via the web interface becomes the administrator. Complete the registration to gain access to the Admin Panel. Subsequent users can sign up and be managed by the administrator.
Pitfall Guide
Deploying containerized AI interfaces involves specific risks. The following pitfalls are common in production environments and include mitigation strategies.
| Pitfall | Explanation | Fix |
|---|
| Unrestricted Docker Socket | Mounting /var/run/docker.sock gives the container root-level control over the Docker daemon. If Traefik is compromised, an attacker could escalate privileges. | Use Docker socket proxy or restrict access via TLS. In single-node setups, ensure Traefik is not exposed to untrusted networks and keep the image updated. |
Missing WEBUI_AUTH | Omitting WEBUI_AUTH=true leaves the interface open. Anyone with the URL can access the chat and potentially upload malicious documents. | Always set WEBUI_AUTH=true in the environment. Verify the login screen appears on first access. |
| ACME Rate Limiting | Let's Encrypt enforces rate limits on certificate requests. Frequent restarts or misconfigurations can trigger limits, blocking certificate issuance. | Use the Let's Encrypt staging environment (--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory) during testing. Switch to production only after validation. |
| Volume Permission Errors | OpenWebUI may fail to write to the data volume if the host directory has incorrect ownership, leading to startup crashes. | Ensure the ./data directory is writable by the container user. Use chown -R 1000:1000 ./data if permission denied errors appear in logs. |
| First User Admin Trap | The first registered user becomes the admin. If a non-admin registers first, they gain full control, which may be unintended. | Plan the admin account registration. If needed, delete the data volume and restart to reset the first-user flow, or use the admin panel to assign roles immediately after setup. |
| Backend Connectivity Failure | OpenWebUI cannot connect to Ollama or other backends if they are not reachable from the container network. | Use http://host.docker.internal:11434 for local Ollama instances, or configure a shared Docker network. Ensure the backend allows CORS if accessed from the browser. |
| Port Conflicts | Ports 80 or 443 may be occupied by existing web servers, causing Traefik to fail on startup. | Check for conflicts using sudo lsof -i :80 and sudo lsof -i :443. Stop conflicting services or change Traefik ports and configure a upstream load balancer. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Solo Developer | Single-node Docker Compose | Simple setup, low resource usage, sufficient for personal use. | Low (Single VM) |
| Small Team | Docker Compose + Traefik | Provides auth, HTTPS, and user management with minimal ops overhead. | Low (Single VM) |
| High Availability | Kubernetes + Ingress Controller | Enables scaling, rolling updates, and redundancy for critical workloads. | Medium-High (Cluster resources) |
| Air-Gapped Environment | Self-Signed Certs + Internal CA | ACME requires internet. Use internal CA for TLS in isolated networks. | Medium (CA management) |
Configuration Template
Copy this template for a production-ready deployment. Replace placeholder values with your actual configuration.
.env
APP_DOMAIN=your-domain.com
ACME_EMAIL=your-email@example.com
docker-compose.yaml
services:
proxy-edge:
image: traefik:v3.6
container_name: proxy-edge
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "letsencrypt-storage:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart: unless-stopped
chat-ui-service:
image: ghcr.io/open-webui/open-webui:main
container_name: chat-ui-service
hostname: chat-ui
expose:
- "8080"
volumes:
- "./data:/app/backend/data"
environment:
- WEBUI_AUTH=true
labels:
- "traefik.enable=true"
- "traefik.http.routers.ai-gateway.rule=Host(`${APP_DOMAIN}`)"
- "traefik.http.routers.ai-gateway.entrypoints=websecure"
- "traefik.http.routers.ai-gateway.tls.certresolver=letsencrypt"
- "traefik.http.services.ai-gateway.loadbalancer.server.port=8080"
restart: unless-stopped
volumes:
letsencrypt-storage:
Quick Start Guide
- Create Directory: Run
mkdir -p ~/ai-gateway-stack/data && cd ~/ai-gateway-stack.
- Edit Config: Create
.env with your domain and email. Create docker-compose.yaml using the template.
- Deploy: Execute
docker compose up -d.
- Register: Open
https://<APP_DOMAIN> and register the first user as admin.
- Connect Model: Navigate to Settings β Connections and add your Ollama or OpenAI-compatible endpoint.