Tag: Docker

Setting Up a Local Chat AI with Ollama and Open Web UI

As a software developer and architect, I’m always excited to explore new technologies that can revolutionize the way we interact with computers. AI is taking the technology world by storm, and for good reason, it can be a very powerful tool. Sometimes however, using a public service like ChatGPT or Microsoft’s Co-Pilot doesn’t work for a number of reasons (usually privacy related).

In this article, I’ll guide you through setting up a chat AI using Ollama and Open Web UI that you can quickly and easily run on your local, Windows based machine . We’ll use Docker Compose to spin up the environment, and I’ll walk you through the initial launch of the web UI, configuring models in settings, and generally getting things up and running.

Prerequisites

Before we dive into the setup process, make sure you have:

  • Docker installed on your machine (you can download it from the official Docker website)
  • A basic understanding of Docker Compose and its syntax. (Not necessarily required, but helpful if you run into issues or want to tweak things)
  • A compatible graphics card (GPU) to run Ollama efficiently. While this is not strictly required, your experience will not be very good without one. My example is configured to use an Nvidia graphics card.

Step 1: Create a Docker Compose File

Create a new file named docker-compose.yml in a directory of your choice. Copy the following content into this file:

services:
  openWebUI:
    image: ghcr.io/open-webui/open-webui:main
    restart: unless-stopped
    ports:
      - "3000:8080"
    environment:
      OLLAMA_BASE_URL: http://host.docker.internal:11434
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - c:\tmp\owu:/app/backend/data

  ollama:
    image: ollama/ollama:latest    
    environment:
      NVIDIA_VISIBLE_DEVICES: all
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: ["gpu"]
              driver: nvidia
              count: all    
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - c:\tmp\ollama:/root/.ollama

This Docker Compose file defines two services:

  • openWebUI: runs the Open Web UI container with the necessary environment variables and port mappings.
  • ollama: runs the Ollama container with NVIDIA GPU support, exposes a port for communication between the containers, and mounts a volume to store model data.
A couple things to note here: 
Lines 12 and 29 are mapping local directories into the containers as volumes. That allows the data from your sessions to persist, even if the docker images are restarted or your machine is rebooted. C:\tmp\ollama and C:\tmp\owu can be changed to any empty directory you choose, but remember that in the following steps if you choose to change them.

Lines 16-24 configure the container to take advantage of your Nvidia GPU. If you don't have one, or don't want to use it, you can remove these lines and everything should still work, albeit much slower. If you have another GPU, this is where you will want to look into making changes to use your GPU - In particular lines 17 & 23 will likely need to change.

Lines 4 & 25 configure the containers to automatically restart unless they were manually stopped. That means they should restart if you reboot your machine, they crash, or you update docker and it restarts.

Step 2: Create Data Directories

As mentioned above, the directories C:\tmp\ollama and C:\tmp\owu will be mapped into the running containers and used for data storage. You will want to create these directories ahead of launching the containers to avoid any potential issues.

Step 3: Launch the Environment

Open your terminal or command prompt and navigate to the directory where you saved the docker-compose.yml file. Run the following command:

docker-compose up -d

This will start both containers in detached mode (i.e., they’ll run in the background).

Step 4: Launch the Web UI

Once the environment is set up, navigate to http://localhost:3000 in your web browser. This should open the Open Web UI interface.

You should be presented with a screen to log in, but you won’t have an account yet. Just click the “Don’t have an account? Sign up” link under the “Sign in” button. Since this will be the first account, it will automatically become the administrator. Simply enter your name, email address and a password and create your account.

Once you account is created you will be logged in and should now she the Chat Interface, which should look pretty familiar if you have been using ChatGPT.

Step 5: Setting/Adding up Models

Note: This has been changed in the latest version of the Open Web UI interface. There is a new post : “Local Chat AI With Ollama – Update” which outlines how to achieve this in the new version of the user interface.

Before you can start chatting it up with your new application, you’ll need to install some models to use. To get started I would install the llama3.1 model. To do this click on your name in the lower left corner of the UI, select “Settings” and then in the dialog, select “Admin Settings” on the left.

Now select “Models” in the Admin Panel and enter llama3.1 in the “Pull a model from Ollama.com” field and click the download button on the right of the field. (You can see a list of available models on Ollama.com: https://ollama.com/library)

You should see a small progress bar appear. Wait for the progress bar to get to 100%, then it will verify the hash and eventually you should see a green pop-up notifying you that it has successfully been added. Now you can click “New Chat” on the far top-left and select llama3.1 from the “Select a model” dropdown.

Next Steps

At this point you should now have a functioning Chat AI interface!

Going forward, I’ll be playing with this configuration and attempting to add on more functionality and potentially convert the docker compose above into kubernetes manifests so that I can run my service on a local kind cluster.

Resources

Source Code

https://github.com/DotNet-Ninja/DotNetNinja.Docker.Ollama

Running a Multi-Node Kubernetes Cluster on Windows with Kind

There are lots of ways to run Kubernetes on your Windows development machine, for example minikube. Even Docker Desktop for Windows now ships with the ability to run a single node Kubernetes cluster, but the simplest and most flexible way I’ve found to run a multi-node Kubernetes cluster locally on Windows is using Kind or Kubernetes in Docker.

Note

All commands should be run from an elevated (administrator) powershell or command prompt
Also, commands listed are for powershell. If using a command prompt you may need to make the appropriate modifications to the paths etc.

Prerequisites

In order to run Kind locally you’ll need to have or install Docker Desktop for Windows (you’re going to be running the nodes of your cluster in docker containers), kubectl (the Kubernetes command line utility), and of course kind. The easiest way to install them is using the Chocolatey package manager. (Strictly speaking you don’t have to have kubectl to set up a kind cluster, but it will make the cluster far more useful once you have it up and running! 🙂 )

choco install docker-desktop
choco install kubernetes-cli
choco install kind

If you would prefer not to use chocolatey, please see the installation documentation for each component

Creating Your First Cluster

Once everything is installed you’re ready to create your first cluster.

kind create cluster 

Note this will download a decent sized docker image to run your node(s) and may take a couple of minutes if you have a slower connection, but you should only have to do it once for each version of Kubernetes that you run as the image will be cached by Docker.

This gives us a single node cluster running the latest version of Kubernetes (at the time of this writing that’s 1.20). It also gives our cluster the default name of kind. You can control the version of Kubernetes by specifying the image to use for your nodes using the –image switch on your create cluster command.

kind create cluster --image=kindest/node:v1.19.7@sha256:a70639454e97a4b733f9d9b67e12c01f6b0297449d5b9cbbef87473458e26dca

You can get a list of the images available for a particular kind release on the Kind GitHub Releases page. For example the current release of Kind (0.10.0) supports the following images:

1.20: 
kindest/node:v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab
1.19: 
kindest/node:v1.19.7@sha256:a70639454e97a4b733f9d9b67e12c01f6b0297449d5b9cbbef87473458e26dca
1.18: 
kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
1.17: 
kindest/node:v1.17.17@sha256:7b6369d27eee99c7a85c48ffd60e11412dc3f373658bc59b7f4d530b7056823e
1.16: 
kindest/node:v1.16.15@sha256:c10a63a5bda231c0a379bf91aebf8ad3c79146daca59db816fb963f731852a99
1.15: 
kindest/node:v1.15.12@sha256:67181f94f0b3072fb56509107b380e38c55e23bf60e6f052fbd8052d26052fb5
1.14: 
kindest/node:v1.14.10@sha256:3fbed72bcac108055e46e7b4091eb6858ad628ec51bf693c21f5ec34578f6180

You can also set the name of your cluster using the –name switch

kind create cluster --name my-cluster

Removing your Cluster

Eventually you will want to delete/remove/destroy your cluster and reclaim the resources it is using. You can easily accomplish this using the kind delete command.

kind delete --name my-cluster

Going to Multiple-Nodes

So initially I said that we could set up a multi-node cluster using kind, but so far all of our clusters have been a single node. In order to get a multi-node cluster we’ll need to create a configuration file. Here’s a simple example that creates a 3 node cluster (1 control plane node and 2 worker nodes)

# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker

We can apply our config file by using the –config switch with our create cluster command as follows

kind create cluster --name my-cluster --config .\my-cluster-config.yaml

And there we have it, a multi-node Kubernetes cluster running on your Windows desktop. Kind makes it pretty quick and simple so you can spin one up, play with it while doing some training or deploy test workloads locally to test your entire system locally. Once your done, or you mess things up, delete the cluster and spin up a new one when you need it.

Resources

Adjusting Resources for Docker Desktop for Windows from PowerShell

Last week I was searching high and low for documentation of any kind on how to script a change in memory allocated to Docker Desktop for Windows. Unable to find anything online, and failing in all my attempts to piece together a way to make it happen, I opened an issue on GitHub and asked for advice. The fine folks in the Docker Desktop for Windows community on GitHub jumped in quickly to assist (Thank you again!). The suggestion was to change the Docker Desktop settings file directly. From that I was able to put together a process that works.

First we need to stop the docker services, there are two of them: com.docker.service and docker. To stop those is pretty straight forward.

Stop-Service com.docker.service
Stop-Service docker

or shorter:

Stop-Service *docker*

Next we’ll need to read the settings file which is located @ ~\AppData\Roaming\Docker\settings.json. We’ll want to use the $env:APPDATA variable to get the actual path to the ~\AppData\Roaming folder on the current system for the current user, and we’ll pipe the file contents to ConvertFrom-Json to give us a nice object to work with.

$path = "$env:APPDATA\Docker\settings.json"
$settings = Get-Content $path | ConvertFrom-Json

Now we can easily change the memory value by manipulating the memoryMIB property of our settings object.

$settings.memoryMiB = 4096

Then we can save the file again by piping our settings object to ConvertTo-Json and then to Set-Content.

$settings | ConvertTo-Json | Set-Content $path

Now we just need to restart the docker services.

Start-Service *docker*    

There’s one last crucial step. It turns out we need to give the Docker daemon a little nudge to get things responding to our docker commands again. According to this article on stack-overflow we do that using:

& $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon
& $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon

Yes, twice. I later derived by looking at the DockerCli help that I could just use:

&$Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchLinuxEngine

(I’m running Linux containers. If you are running Windows containers use -SwitchWindowsEngine instead.)

So here is the whole thing all in one go.

Stop-Service *docker*
$path = "$env:APPDATA\Docker\settings.json"
$settings = Get-Content $path | ConvertFrom-Json
$settings.memoryMiB = 4096
$settings | ConvertTo-Json | Set-Content $path
Start-Service *docker*        
&$Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchLinuxEngine

Resources

Running PostgreSql in a Container on Windows 10

Today at work we were setting up a development environment for a .Net Core project using PostgreSql as it’s datastore. We decided that we set up the database server running in a container in the same way I have been running SQL Server (See recent article: Running Microsoft SQL Server in a Container on Windows 10) for the local development environment. Using the docker-compose file from this article as a basis and referring to the documentation for the postgres docker image on Docker Hub we put together a docker-compose file for PostgreSQL that looked similar to this:

version: "3"
services:
  postgres:
    image: "postgres"
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: "MyUser"
      POSTGRES_PASSWORD: "Password!23"
      POSTGRES_DB: "example"
    volumes: 
      - C:\Docker\PostgreSql\data:/var/lib/postgresql/data

Upon running docker-compose we were greeted with the following output containing an error message:

Creating postgresql_postgres_1 ... done
Attaching to postgresql_postgres_1
postgres_1  | The files belonging to this database system will be owned by user "postgres".
postgres_1  | This user must also own the server process.
postgres_1  |
postgres_1  | The database cluster will be initialized with locale "en_US.utf8".
postgres_1  | The default database encoding has accordingly been set to "UTF8".
postgres_1  | The default text search configuration will be set to "english".
postgres_1  |
postgres_1  | Data page checksums are disabled.
postgres_1  |
postgres_1  | fixing permissions on existing directory /var/lib/postgresql/data ... ok
postgres_1  | creating subdirectories ... ok
postgres_1  | selecting dynamic shared memory implementation ... posix
postgres_1  | selecting default max_connections ... 20
postgres_1  | selecting default shared_buffers ... 400kB
postgres_1  | selecting default time zone ... Etc/UTC
postgres_1  | creating configuration files ... ok
postgres_1  | running bootstrap script ... 2020-02-25 02:38:12.326 UTC [80] FATAL:  data directory "/var/lib/postgresql/data" has wrong ownership
postgres_1  | 2020-02-25 02:38:12.326 UTC [80] HINT:  The server must be started by the user that owns the data directory.
postgres_1  | child process exited with exit code 1
postgres_1  | initdb: removing contents of data directory "/var/lib/postgresql/data"
postgresql_postgres_1 exited with code 1

Notice line 19: “FATAL: data directory “/var/lib/postgresql/data” has wrong ownership”. After reading the error message we noted on line 12 it reads “fixing permissions on existing directory /var/lib/postgresql/data … ok”. Also near the top of the output on line 3 it reads “The files belonging to this database system will be owned by user “postgres”.” followed by “This user must also own the server process.”. Interesting…

So after digging around a bit we found that indeed the user “postgres” must own the files in order for the db system to read them and that the container starts up as root. It appears that line 12 is trying to fix the issue, and from what we found online it will… If the data directory is on a Linux file system. Since we are attempting to mount these files from a Windows file system, it appears that “fixing the permissions” fails. No major surprise there. So what is the work around for us poor developers working on Windows machines?

Named Volumes to the Rescue

In order to get this to work we set up a named volume. In this scenario, Docker takes care of handling the files and where they are actually stored, so we don’t readily have access to the files, but we don’t really care all that much. We just want our data to persist and not get blown away when the container gets deleted.

Here is the new (working) docker-compose file with the named volume:

version: "3"
services:
  postgres:
    image: "postgres"
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: "MyUser"
      POSTGRES_PASSWORD: "Password!23"
      POSTGRES_DB: "example"
    volumes: 
      - psql:/var/lib/postgresql/data

volumes:
  psql:

Using this approach you may want to keep an eye on the named volumes on your system and clean them up when you are no longer using them. To get a list of the volumes on your machine use the following command:

docker volumes ls

That will dump out a list of volumes on your machine that looks something like:

DRIVER              VOLUME NAME
local               600de9fcef37a60b93c410f9e7db6b4b7f9966faf5f6ba067cc6cb55ee851198
local               ae45bfac51d4fb1813bd747cc9af10b7d141cf3affa26d79f46f405ebfa07462
local               b94806ba697f79c7003481f8fd1d65599e532c0e2223800b39a2f90b087d5127
local               d02adf9ab33dfa22e154d25e13c5bb383a5969c19c1dd98cfa2ac8e560d87eb4
local               postgresql_psql

Notice the last entry named “postgresql_psql”? That is the one we just created above. To remove it use the following command (Note: It will not allow you to remove the volume if it is referenced by a container, running or not, so you’ll want to stop and remove the container first):

docker volume rm postgresql_psql