Hugging Face Chat UI
Overview
Hugging Face Chat UI is the open-source codebase that powers HuggingChat, packaged here as a private, self-hosted chat front end for your own team. This guide covers deploying and configuring it on Ubuntu 24.04 on Azure, using the SecureAnyCloud Azure Marketplace image.
Chat UI gives you a fast, familiar chat interface that talks to the model endpoint you choose. Conversations are stored in your own database on your own VM, not in somebody else's hosted product. You can start conversations, keep them organised in a sidebar, switch model per conversation, upload images to multimodal models, watch replies stream token by token, and export your own data whenever you want.
Chat UI is a front end, not a model host. It calls an OpenAI-compatible endpoint and discovers that endpoint's model list automatically, so it runs comfortably on a small CPU-only VM and needs no GPU. Point it at the Hugging Face Inference Router for hundreds of open models, at OpenAI or Azure OpenAI, or at your own private Ollama, vLLM, TGI or LM Studio server, and the model picker fills itself in.
Chat UI has no password login of its own, so this image puts an nginx access gateway in front of it. No model provider token is ever baked into the image.
Hugging Face and Chat UI are trademarks of their respective owners. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by Hugging Face. It ships the free and open-source, Apache 2.0 licensed, self-hosted software, unmodified.
What you get:
- Hugging Face Chat UI v0.9.6 (the Apache 2.0 licensed upstream container
ghcr.io/huggingface/chat-ui), pinned by image digest, not by a moving tag - MongoDB 8.0.28 (official Docker Library image, pinned by digest) as a single-node replica set for conversation history
- Docker Engine and the Compose plugin from the official Docker APT repository
- An nginx access gateway on port
80, installed as a host package so that unattended security upgrades keep the internet-facing component patched - A unique 32-character web credential and a unique MongoDB password, generated per VM on first boot and never baked into the image. The chat-ui and nginx services refuse to start without the credential
- Fail-closed guards: both the application service and the gateway refuse to start if the access credential is missing, empty or unhashed
- MongoDB published on
127.0.0.1only, with authorization enforced and a per-VM keyFile - The model provider token shipped empty, so you always supply your own
- Ubuntu 24.04 LTS base with the latest security patches applied at build time
- Azure Linux Agent for seamless cloud integration and SSH key injection
- 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA
Prerequisites
- An active Azure subscription, an SSH public key, and a VNet and subnet in the target region
- A subscription to the Hugging Face Chat UI listing on Azure Marketplace
- A model endpoint that Chat UI can reach. The image ships pointed at the public Hugging Face Inference Router, whose model list is readable without a token, so the interface works the moment the VM boots. Sending a message needs your own API token or your own OpenAI-compatible endpoint. SecureAnyCloud does not supply one and never bundles one.
- Outbound HTTPS from the VM to whichever model endpoint you configure. Chat UI reads its model list at startup and will not serve until that endpoint answers.
Recommended virtual machine size: Standard_B2ms (2 vCPU, 8 GB RAM). Chat UI itself is a small Node.js server, but it shares the VM with MongoDB, which is where the extra memory goes as conversation history grows.
Standard_B2s (2 vCPU, 4 GB) works for light single-user use. The image is built and tested on it, with the MongoDB cache capped at 1 GB and no swap.
For the NSG, allow inbound 22/tcp from your management network and 80/tcp from the networks that use the interface.
Deploy from the Azure Portal
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Hugging Face Chat UI by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then select Review + create and Create.
Deploy from the Azure CLI
Create the network and the VM:
RG="chat-ui-prod"; LOCATION="eastus"; VM_NAME="chat-ui-01" IMAGE_URN="sac1647283583153:huggingface-chat-ui-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name cu-vnet --address-prefix 10.100.0.0/16 --subnet-name cu-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name cu-nsg az network nsg rule create -g "$RG" --nsg-name cu-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name cu-nsg --name allow-http --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:huggingface-chat-ui-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
--size Standard_B2ms --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name cu-vnet --subnet cu-subnet --nsg cu-nsg --public-ip-sku StandardConnect to your VM
Connect over SSH as azureuser:
ssh azureuser@<vm-ip>Confirm the services are running
Chat UI runs as two containers under chat-ui.service, behind an nginx gateway on host port 80. Confirm that everything is active and see which images are running:
sudo systemctl is-active docker chat-ui-firstboot chat-ui nginx
sudo docker compose -f /var/lib/chat-ui/docker-compose.yml ps \
--format 'table {{.Name}}\t{{.Status}}'All four services report active, and both containers are Up:
active active active active NAME STATUS chat-ui Up 2 minutes chat-ui-mongo Up 2 minutes (healthy)Both images are pinned by digest, so the VM you booted runs the exact images that SecureAnyCloud tested. Inspect the Chat UI image:
sudo docker inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}} {{index .Config.Labels "org.opencontainers.image.licenses"}} {{index .Config.Labels "org.opencontainers.image.revision"}}' \
ghcr.io/huggingface/chat-ui@sha256:41e450e64739b3ca45d7db1752de9a9eea11bb0829706f00b8dd7d3be016657bThe licence and the upstream commit are recorded in the image itself:
latest Apache-2.0 a33743a5c35ca0774ca82aadc2b49994a82e3d67Commit a33743a5 is exactly what upstream git tag v0.9.6 points at.
Read the per instance credentials
A unique 32-character web credential and a unique MongoDB password were generated for this VM on its first boot, before anything was reachable. They were written to a root-only file. Read them:
sudo cat /root/chat-ui-credentials.txtThe file (mode 0600 root:root) holds CHAT_UI_URL, CHAT_UI_USERNAME, CHAT_UI_PASSWORD, MONGODB_USERNAME and MONGODB_PASSWORD. None of them ship in the image. Each is unique to this VM, so no two instances share one, and there is no default to change.
Open the web interface
Browse to the CHAT_UI_URL from Step 5 - which is http://<vm-ip>/. The browser asks for a username and password. Enter chatui and the CHAT_UI_PASSWORD from the credentials file. Chat UI then loads with its model picker already populated from the configured endpoint.
Type a message and press Enter. The reply streams back token by token from the model endpoint. The conversation is titled automatically and appears in the sidebar, and everything is written to the MongoDB on this VM.
Understand the access gate
This is the most important section of this guide. Chat UI has no password login of its own. Upstream offers OpenID Connect single sign-on, and nothing else.
An instance left reachable on a public IP with a working model provider token would let anyone who finds the address spend your inference credit. This image closes that gap with an nginx gateway that denies every route by default, with a credential generated per VM.
Confirm that the gate refuses anything but your own credential:
B=http://localhost curl -s -o /dev/null -w 'no credentials: HTTP %{http_code}\n' $B/ curl -s -o /dev/null -w 'wrong password: HTTP %{http_code}\n' -u 'chatui:WRONGPASSWORD' $B/ curl -s -o /dev/null -w 'wrong username: HTTP %{http_code}\n' -u 'nobody:WRONGPASSWORD' $B/ curl -s -o /dev/null -w 'settings page: HTTP %{http_code}\n' $B/settings curl -s -o /dev/null -w 'models API: HTTP %{http_code}\n' $B/api/models echo -n 'liveness probe: '; curl -s $B/healthcheck; echoEverything is refused with 401 except the static liveness probe. The probe returns the literal string OK and nothing else, so a load balancer never needs to hold this VM's credential:
no credentials: HTTP 401 wrong password: HTTP 401 wrong username: HTTP 401 settings page: HTTP 401 models API: HTTP 401 liveness probe: OKNow send the same request with your password. Read the password straight out of the credentials file rather than typing it, so that it never lands in your shell history:
P=$(sudo grep -m1 '^CHAT_UI_PASSWORD=' /root/chat-ui-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'your credential: HTTP %{http_code}\n' -u "chatui:$P" http://localhost/your credential: HTTP 200The gate is fail-closed, not merely configured. Both services carry a preflight guard. The guard refuses to start if the credential file is missing, empty or holds a plaintext entry, or if the site configuration disables authentication anywhere but the liveness probe:
systemctl show -p ExecStartPre --value chat-ui.service | tr ';' '\n' | grep -o '/usr/local/sbin/[^ "]*'
systemctl show -p ExecStartPre --value nginx.service | tr ';' '\n' | grep -o '/usr/local/sbin/[^ "]*'
sudo /usr/local/sbin/chat-ui-preflight.sh --nginx --gate-file /dev/null; echo "exit=$?"Both units run the guard, and the guard rejects an empty credential file rather than serving without one:
/usr/local/sbin/chat-ui-preflight.sh /usr/local/sbin/chat-ui-preflight.sh chat-ui-preflight: REFUSING TO START — /dev/null is empty — the HTTP Basic gate would accept nobody and protect nothing. exit=1Change the password at any time by running sudo htpasswd -B /etc/nginx/chat-ui.htpasswd chatui, which prompts for the new value. The gate reads the file on every request, so no restart is needed.
To add a second person, run the same command with a different username.
To use single sign-on instead, configure OPENID_CLIENT_ID and OPENID_CLIENT_SECRET in /var/lib/chat-ui/.env.local. Keep the gateway credential in place as well, because the preflight guard checks it.
Point Chat UI at your own model endpoint
SecureAnyCloud never ships a model provider token. The image is configured for the public Hugging Face Inference Router, whose model list is readable without one. This is why the interface and the model picker work immediately.
To actually send messages, add your own token. The whole configuration surface is one file. Print it with the secrets masked:
sudo grep -vE '^\s*#' /var/lib/chat-ui/.env.local | grep -v '^$' \
| sed -E 's|^(MONGODB_URL=).*|\1<generated on this VM at first boot>|'OPENAI_BASE_URL=https://router.huggingface.co/v1 OPENAI_API_KEY= <set-at-first-boot>=<generated on this VM at first boot> MONGODB_DB_NAME=chat-ui COOKIE_SAMESITE=lax COOKIE_SECURE=false PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host TASK_MODEL= OPENID_CLIENT_ID= OPENID_CLIENT_SECRET= <set-at-first-boot>=true ENABLE_DATA_EXPORT=trueEdit that file with your preferred editor as root and set OPENAI_API_KEY to your own token. These are the settings to change:
# Hugging Face Inference Router (the shipped default) OPENAI_BASE_URL=https://router.huggingface.co/v1 OPENAI_API_KEY=<your-token>
# or OpenAI
# OPENAI_BASE_URL=https://api.openai.com/v1
# or your own private endpoint - Ollama, vLLM, TGI, LM Studio
# OPENAI_BASE_URL=http://10.100.1.20:11434/v1
# OPENAI_API_KEY=<set-at-first-boot>Then restart the service:
sudo systemctl restart chat-ui.serviceChat UI rediscovers the model list from ${OPENAI_BASE_URL}/models on every start, so whatever your endpoint serves is what appears in the picker. The interface shows the configured endpoint under Settings, along with the exact upstream commit this image was built from.
Your token lives only on this VM, and the access gate from Step 7 is what stops anyone else from using it.
The database
Conversation history lives in MongoDB on this VM. MongoDB is published on the loopback address only and is never reachable from the network. Check the listening ports:
sudo ss -lntp | grep -E ':(80|3000|27017) ' | awk '{print $1, $4}'Only nginx is on a routable address. Chat UI itself and MongoDB are both loopback bound, so every request has to pass the gate:
LISTEN 0.0.0.0:80 LISTEN 127.0.0.1:3000 LISTEN 127.0.0.1:27017 LISTEN [::]:80Administer the database from the VM. Read the password out of the credentials file so that it never lands in your shell history:
MP=$(sudo grep -m1 '^MONGODB_PASSWORD=' /root/chat-ui-credentials.txt | cut -d= -f2-) sudo docker exec chat-ui-mongo mongosh --quiet \
-u chatui_admin -p "$MP" --authenticationDatabase admin chat-ui \
--eval 'print("collections: " + db.getCollectionNames().length)' \
--eval 'print("conversations: " + db.conversations.countDocuments({}))'collections: 14 conversations: 0Back it up with mongodump, writing the archive out of the container:
MP=$(sudo grep -m1 '^MONGODB_PASSWORD=' /root/chat-ui-credentials.txt | cut -d= -f2-) sudo docker exec chat-ui-mongo mongodump --quiet \
-u chatui_admin -p "$MP" --authenticationDatabase admin \
--db chat-ui --archive=/tmp/chat-ui-backup.archive sudo docker cp chat-ui-mongo:/tmp/chat-ui-backup.archive /var/backups/chat-ui-backup.archive
sudo ls -lh /var/backups/chat-ui-backup.archiveThe data directory itself is /var/lib/chat-ui/mongodb on the OS disk, so an Azure disk snapshot of the VM captures it too.
Day-to-day operation
Check the service status and the recent application logs:
sudo systemctl status chat-ui.service --no-pager | head -8 sudo docker compose -f /var/lib/chat-ui/docker-compose.yml logs --tail 15 chat-uiRestart the application after any configuration change, and reload the gateway after any nginx change:
sudo systemctl restart chat-ui.service
sudo nginx -t && sudo systemctl reload nginxUbuntu security updates are applied automatically by unattended-upgrades, which covers the nginx gateway.
The two container images are pinned by digest and are deliberately not updated automatically. To move to a newer Chat UI release, edit the image: digest in /var/lib/chat-ui/docker-compose.yml, then run sudo systemctl restart chat-ui.service.
Security hardening
- Restrict port 80 in the network security group to the networks that actually use the interface. The access gate is a credential, not a substitute for a firewall.
- Put TLS in front. The gate uses HTTP Basic authentication, which sends the credential base64-encoded, not encrypted. On an untrusted network, it must run over HTTPS. Terminate TLS on an Azure Application Gateway, an Azure Front Door, or with certbot on this VM. Then set
COOKIE_SECURE=truein/var/lib/chat-ui/.env.localand restart, so that session cookies are sent only over the encrypted connection. LeavePROTOCOL_HEADERandHOST_HEADERas shipped. They make Chat UI follow whatever scheme actually reached the gateway, so its own links stay correct either way. - Rotate the web credential with
sudo htpasswd -B /etc/nginx/chat-ui.htpasswd chatuiwhenever someone leaves. - Keep MongoDB where it is. It is loopback-bound with authorization enforced. Publishing it on a routable address would expose every conversation on the VM.
- Watch your inference spend at the provider. The gate stops strangers, but anyone with the credential can use your token, so use your provider's own usage limits as a second control.
Troubleshooting
The interface returns 502. Chat UI is not answering on its loopback port. The usual cause is that the VM cannot reach the configured model endpoint. Chat UI reads its model list at startup and refuses to serve until that endpoint answers. Check the logs:
sudo docker compose -f /var/lib/chat-ui/docker-compose.yml logs --tail 20 chat-ui | tail -20Look for [models] Using OpenAI-compatible base URL followed by a non-200 fetch status. Fix OPENAI_BASE_URL (and OPENAI_API_KEY if your endpoint requires one for its model list) in /var/lib/chat-ui/.env.local, then restart.
nginx did not start. The gateway refuses to run without a credential. Check the guard's own verdict:
sudo /usr/local/sbin/chat-ui-preflight.sh --nginx; echo "exit=$?"Messages fail with a provider error. The gate accepted you and the request reached your provider. Set or correct OPENAI_API_KEY. An HTTP 401 from the gate looks quite different, because that request never reaches Chat UI at all.
Conversations disappear after a restart. Confirm that Chat UI is really using this VM's MongoDB rather than falling back to an in-memory database:
sudo docker compose -f /var/lib/chat-ui/docker-compose.yml logs chat-ui 2>/dev/null \
| grep -ci 'in-memory server' || echo 00 is correct. Anything higher means MONGODB_URL is not reaching the container.
Support
SecureAnyCloud provides 24/7 support with a guaranteed 24-hour response SLA for this image. Contact sales@secureanycloud.com with your Azure subscription ID and the VM name.
Upstream project documentation lives at github.com/huggingface/chat-ui.
Deploy on Azure
Launch Hugging Face Chat UI on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com