Add Open WebUI

This commit is contained in:
2026-05-03 16:24:43 +02:00
parent 45f853b7f5
commit fcf764a3dc

View File

@@ -23,29 +23,35 @@ networks:
configs: configs:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# nginx: reverse proxy for LocalAI (/) and Keycloak (/auth/) # nginx: one server block per subdomain — no path tricks needed
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
nginx_localai_conf: nginx_localai_conf:
content: | content: |
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name ${DOMAIN:-localhost}; server_name ${DOMAIN_WEBUI:-localhost};
client_max_body_size 512M; client_max_body_size 512M;
location /auth/ { location / {
proxy_pass http://keycloak:8080/auth/; proxy_pass http://openwebui:8080;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $$host; proxy_set_header Host $$host;
proxy_set_header X-Real-IP $$remote_addr; proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$http_x_forwarded_proto; proxy_set_header X-Forwarded-Proto $$http_x_forwarded_proto;
proxy_read_timeout 120s; proxy_set_header Upgrade $$http_upgrade;
proxy_buffer_size 128k; proxy_set_header Connection "upgrade";
proxy_buffers 4 256k; proxy_read_timeout 300s;
proxy_busy_buffers_size 256k; proxy_send_timeout 300s;
} }
}
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN_LOCALAI:-localhost};
client_max_body_size 512M;
location / { location / {
proxy_pass http://localai:8080; proxy_pass http://localai:8080;
@@ -61,6 +67,25 @@ configs:
} }
} }
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN_AUTH:-localhost};
location / {
proxy_pass http://keycloak:8080;
proxy_http_version 1.1;
proxy_set_header Host $$host;
proxy_set_header X-Real-IP $$remote_addr;
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $$http_x_forwarded_proto;
proxy_read_timeout 120s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Terraform: main.tf — realm, client, roles, and user resources. # Terraform: main.tf — realm, client, roles, and user resources.
# seed_users values are declared in terraform.tfvars below, not here. # seed_users values are declared in terraform.tfvars below, not here.
@@ -80,7 +105,7 @@ configs:
client_id = "admin-cli" client_id = "admin-cli"
username = var.keycloak_admin_user username = var.keycloak_admin_user
password = var.keycloak_admin_password password = var.keycloak_admin_password
url = "http://keycloak:8080/auth" url = "http://keycloak:8080"
realm = "master" realm = "master"
} }
@@ -93,7 +118,13 @@ configs:
type = string type = string
sensitive = true sensitive = true
} }
variable "localai_base_url" { type = string } variable "localai_base_url" { type = string }
variable "openwebui_base_url" { type = string }
variable "openwebui_client_secret" {
type = string
sensitive = true
}
variable "seed_users" { variable "seed_users" {
type = list(object({ type = list(object({
@@ -133,12 +164,10 @@ configs:
valid_redirect_uris = [ valid_redirect_uris = [
"$${var.localai_base_url}/api/auth/oidc/callback", "$${var.localai_base_url}/api/auth/oidc/callback",
"http://localhost:8080/api/auth/oidc/callback",
] ]
web_origins = [ web_origins = [
var.localai_base_url, var.localai_base_url,
"http://localhost:80",
] ]
} }
@@ -182,8 +211,38 @@ configs:
] ]
} }
resource "keycloak_openid_client" "openwebui" {
realm_id = keycloak_realm.localai.id
client_id = "openwebui"
name = "Open WebUI"
enabled = true
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
direct_access_grants_enabled = false
service_accounts_enabled = false
client_secret = var.openwebui_client_secret
valid_redirect_uris = [
"$${var.openwebui_base_url}/oauth/oidc/callback",
]
web_origins = [
var.openwebui_base_url,
]
}
resource "keycloak_openid_user_realm_role_protocol_mapper" "openwebui_realm_roles" {
realm_id = keycloak_realm.localai.id
client_id = keycloak_openid_client.openwebui.id
name = "realm-roles"
claim_name = "roles"
multivalued = true
add_to_id_token = true
add_to_access_token = true
}
output "oidc_issuer" { output "oidc_issuer" {
value = "http://keycloak:8080/auth/realms/$${keycloak_realm.localai.realm}" value = "http://keycloak:8080/realms/$${keycloak_realm.localai.realm}"
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -222,7 +281,7 @@ configs:
#!/bin/sh #!/bin/sh
set -e set -e
echo "Waiting for Keycloak..." echo "Waiting for Keycloak..."
until wget -qO- http://keycloak:8080/auth/realms/master > /dev/null 2>&1; do until wget -qO- http://keycloak:8080/realms/master > /dev/null 2>&1; do
sleep 5 sleep 5
done done
echo "Keycloak ready." echo "Keycloak ready."
@@ -275,9 +334,8 @@ services:
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-keycloak_secret} KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-keycloak_secret}
KC_HOSTNAME: https://${DOMAIN:-localhost}/auth KC_HOSTNAME: https://${DOMAIN_AUTH:-localhost}
KC_HTTP_RELATIVE_PATH: /auth KC_PROXY_HEADERS: xforwarded
KC_PROXY_HEADERS: xforwarded
KC_HTTP_ENABLED: "true" KC_HTTP_ENABLED: "true"
volumes: volumes:
- ./volumes/keycloak/data:/opt/keycloak/data - ./volumes/keycloak/data:/opt/keycloak/data
@@ -288,7 +346,7 @@ services:
postgres: postgres:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /auth/realms/master HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && grep -q '200 OK' <(cat <&3)"] test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /realms/master HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && grep -q '200 OK' <(cat <&3)"]
interval: 15s interval: 15s
timeout: 10s timeout: 10s
retries: 15 retries: 15
@@ -303,10 +361,12 @@ services:
restart: "no" restart: "no"
entrypoint: ["sh", "/entrypoint.sh"] entrypoint: ["sh", "/entrypoint.sh"]
environment: environment:
TF_VAR_keycloak_admin_user: ${KEYCLOAK_ADMIN_USER:-admin} TF_VAR_keycloak_admin_user: ${KEYCLOAK_ADMIN_USER:-admin}
TF_VAR_keycloak_admin_password: ${KEYCLOAK_ADMIN_PASSWORD:-admin_secret} TF_VAR_keycloak_admin_password: ${KEYCLOAK_ADMIN_PASSWORD:-admin_secret}
TF_VAR_localai_client_secret: ${LOCALAI_OIDC_CLIENT_SECRET:-localai_oidc_secret} TF_VAR_localai_client_secret: ${LOCALAI_OIDC_CLIENT_SECRET:-localai_oidc_secret}
TF_VAR_localai_base_url: https://${DOMAIN:-localhost} TF_VAR_localai_base_url: https://${DOMAIN_LOCALAI:-localhost}
TF_VAR_openwebui_client_secret: ${OPENWEBUI_OIDC_CLIENT_SECRET:-openwebui_oidc_secret}
TF_VAR_openwebui_base_url: https://${DOMAIN_WEBUI:-localhost}
volumes: volumes:
- ./volumes/terraform/tf:/tf - ./volumes/terraform/tf:/tf
configs: configs:
@@ -341,10 +401,10 @@ services:
capabilities: [gpu] capabilities: [gpu]
environment: environment:
LOCALAI_AUTH: "true" LOCALAI_AUTH: "true"
LOCALAI_OIDC_ISSUER: https://${DOMAIN:-localhost}/auth/realms/localai LOCALAI_OIDC_ISSUER: https://${DOMAIN_AUTH:-localhost}/realms/localai
LOCALAI_OIDC_CLIENT_ID: localai LOCALAI_OIDC_CLIENT_ID: localai
LOCALAI_OIDC_CLIENT_SECRET: ${LOCALAI_OIDC_CLIENT_SECRET:-localai_oidc_secret} LOCALAI_OIDC_CLIENT_SECRET: ${LOCALAI_OIDC_CLIENT_SECRET:-localai_oidc_secret}
LOCALAI_BASE_URL: https://${DOMAIN:-localhost} LOCALAI_BASE_URL: https://${DOMAIN_LOCALAI:-localhost}
LOCALAI_ADMIN_EMAIL: ${LOCALAI_ADMIN_EMAIL:-admin@example.com} LOCALAI_ADMIN_EMAIL: ${LOCALAI_ADMIN_EMAIL:-admin@example.com}
LOCALAI_API_KEY: ${LOCALAI_API_KEY:-} LOCALAI_API_KEY: ${LOCALAI_API_KEY:-}
LOCALAI_REGISTRATION_MODE: invite LOCALAI_REGISTRATION_MODE: invite
@@ -370,7 +430,45 @@ services:
start_period: 30s start_period: 30s
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 5. nginx — reverse proxy # 5. Open WebUI — chat frontend
# ---------------------------------------------------------------------------
openwebui:
image: ghcr.io/open-webui/open-webui:main
container_name: localai-openwebui
<<: *restart
environment:
WEBUI_URL: https://${DOMAIN_WEBUI:-localhost}
WEBUI_SECRET_KEY: ${OPENWEBUI_SECRET_KEY:-change_me_webui_secret}
OPENAI_API_BASE_URL: http://localai:8080/v1
OPENAI_API_KEY: ${LOCALAI_API_KEY:-}
OPENID_PROVIDER_URL: https://${DOMAIN_AUTH:-localhost}/realms/localai/.well-known/openid-configuration
OAUTH_CLIENT_ID: openwebui
OAUTH_CLIENT_SECRET: ${OPENWEBUI_OIDC_CLIENT_SECRET:-openwebui_oidc_secret}
OAUTH_PROVIDER_NAME: Keycloak
OAUTH_SCOPES: openid email profile
OAUTH_ROLES_CLAIM: roles
OAUTH_ALLOWED_ROLES: localai-user,localai-admin
OAUTH_ADMIN_ROLES: localai-admin
ENABLE_OAUTH_SIGNUP: "true"
ENABLE_LOGIN_FORM: "false"
volumes:
- ./volumes/openwebui/data:/app/backend/data
networks:
- localai
depends_on:
localai:
condition: service_healthy
keycloak:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
# ---------------------------------------------------------------------------
# 6. nginx — reverse proxy
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
nginx: nginx:
image: nginx:alpine image: nginx:alpine