Running Large Language Models (LLMs) Locally using Ollama

Over the weekend I was reading this post on the Oracle Linux Blog.

It looked pretty simple, so I thought I would give it a go, and that lead me down a rabbit hole for the next few days. 🙂

Why would anyone want to run LLMs locally?

There are potential issues with using a cloud-based LLM.

  • Security: Passing sensitive data or your company intellectual property to the cloud could be a problem. Using a local LLM means you can do pretty much what you want with it. It also means your prompts are not being stored and available to the outside world when the provider of your cloud-based LLM inevitably gets hacked, or sells your details.
  • Consistency: The cloud vendors update their models from time to time, and you may find a “new and improved” version of a model works less well for what you are doing. Just look what the “upgrade” to ChatGPT 5 broke. Using a local LLM means you are in control of if/when you update models.
  • Cost: You have the cost of the local kit, but after that it’s all “free”. Running a model requires a lot less kit than training a model. Depending on your needs, maybe running something locally will make more sense than paying for a cloud subscription. It can get really expensive if you require a large call volume on a cloud subscription.

Apart from anything else, it’s just fun, which is why I’m doing it. 🙂

My (Bad) Kit

I don’t have any suitable kit for this, and I don’t like installing things directly on my laptop if I can help it, so I decided to use a minimum installation of Oracle Linux 9 on VirtualBox. I built it using Vagrant, but more about that later. I subsequently tried OL10 and Fedora 43 with similar results.

In the end the VM specs were as follows.

  • OS: OL9
  • vCPUs: 4
  • RAM:10240M (or 16384M for for gpt-oss:20b and codestral)

During the Ollama installation I got this message.

WARNING: No NVIDIA/AMD GPU detected. Ollama will run in CPU-only mode.

Suffice to say, this was not going to perform well, but I didn’t care. I just wanted to see it running locally. Clearly if you have better kit, and can throw your GPU and loads of memory at it, it’s going to work better!

Basic Installation

Ollama is a tool that allows you to work with a range of open Large Language Models (LLMs). It’s not a model itself. Just a tool that allows you to work with them in a consistent manner.

Ollama is available for Linux, Windows and Mac, so you can install it on just about anything. If you want to download it for Windows and Mac, you can get it from here.

On Linux we can install it using a single command. The basic installation worked fine, but there were some subsequent issues. This command does the whole installation, and creates a Linux service called “ollama”.

curl -fsSL https://ollama.com/install.sh | sh

I set up port forwarding in the VM for port 11434, so I expected to be able to access the Ollama API from my host machine, but that didn’t work. After a little digging I found out that the “ollama” service is limited to localhost by default, so you can’t access it externally. To fix this I went nuclear and added the following to the “ollama” service config file (/etc/systemd/system/ollama.service).

Environment="OLLAMA_HOST=0.0.0.0:11434"

That means any network can access it, so you would need to protect it with a local or network firewall if you were doing this for real.

With Ollama in place, it was time to pick a model. I started by using llama3, as they did in the blog post.

ollama pull llama3

It took a while to download, but as soon as it was, it was visible in my list of models using this API URL from curl in a terminal, or from a browser on the host machine.

http://localhost:11434/api/tags

There are a couple of ways to interact with the model. You can use the command line interface to speak to it by running this command.

ollama run llama3

Alternatively, you can issue the questions through the API, in streaming or non-streaming mode.

# Streaming.
curl http://localhost:11434/api/generate -d '{
"model": "llama3",
"prompt": "In less than 50 words, what is Star Trek about?"
}'

# Non-streaming.
curl http://localhost:11434/api/generate -d '{
"model": "llama3",
"prompt": "In less than 50 words, what is Star Trek about?",
"stream": false
}'

The API works fine, but the output is not ideal from a readability perspective, as they are expecting you to interact with it programmatically and format the output. That lead to the next rabbit hole.

Open WebUI

The blog post mentioned using Open WebUI as an interface. I tried the suggested installation, but got into the weeds with conflicting package versions. Instead I decided to use the Open WebUI container, which made life a lot easier. I installed Podman and started the container like this.

dnf install -y podman

podman pull ghcr.io/open-webui/open-webui:main
mkdir -p /u01/open-webui
podman run -d -p 8080:8080 \
           -v /u01/open-webui:/app/backend/data \
           --name open-webui \
           -e OLLAMA_HOST=host.containers.internal \
           -e OLLAMA_PORT=11434 \
           --add-host host.containers.internal:host-gateway \
           ghcr.io/open-webui/open-webui:main

Notice I had to expose the host machines localhost to the container, where it could be referenced as “host.containers.internal”. This was then set in the OLLAMA_HOST environment variable inside the container.

The combination of exposing the host localhost to the container and opening up Ollama’s API to external access took far too much messing about to sort out. As always, it’s easy when you know how. 🙂

I now had a neat web interface to access any models using this URL.

http://localhost:8080/

Which models?

As mentioned previously, I began with the llama3 model, but then I started to compare several other models.

# General models.
ollama pull llama3
ollama pull deepseek-r1
ollama pull gpt-oss:20b

# Code-focussed models.
ollama pull codellama
ollama pull deepseek-coder
ollama pull codestral

This gave me the choice of 3 general models, and 3 code-focussed models. I picked the smaller ChatGPT model, and it still required more memory than the others. I don’t think I could have run the gpt-oss:120b model.

Vagrant

As mentioned previously, I built my playground using Vagrant, and you can see what I did here.

The VM itself is just a simple shell. There are two setup scripts you can run once connected as root. I left them as manual calls so I could mess around with them during each build. Their calls are commented out in the “root_setup.sh” file, but you could put them back in if you prefer.

ssh vagrant@localhost -p 2222
sudo su -

sh /vagrant/scripts/setup_ollama.sh
sh /vagrant/scripts/setup_open_webui.sh

If I included them in the build directly, you would be waiting a long time for the VM build to complete, and I didn’t want that. 🙂

I’ve left this as simple as possible. It’s not meant as an example of the best way to build this. You have been warned. 🙂

What did I do with it?

I was speaking to a colleague about it, and he said Claude Code was baked into his IDE (JetBrains) and he had used it recently to help with some C# code. He used to be a C# developer, but hadn’t done much over the last few years, so he was asking it some basic questions to remind himself. Since his was a “real” example of what someone might ask, I thought it might be a good idea to compare the results. You can see the comparison for a simple question in the section at the end if you are interested.

Overall Thoughts

Ollama works fine. It’s easy to setup and gives access to a whole bunch of models to play around with. After a bit of fiddling, Open WebUI worked fine and gave a much nicer interface than using the command line, or the API directly. It was worth the hassle.

Clearly running LLMs locally on my old laptop inside a VM was terribly slow, and would drive you crazy if you were using it for something real, but at least it allows you to get your hands dirty. If you have some more modern kit, with a compatible GPU and more memory to throw at it, it’s going to be a lot more useable. And remember, running a model takes a lot less kit than training a model. As you can see here, I’m able to run an existing model on junk kit.

I don’t know what I’m going to do with this going forward. Maybe nothing, but it was fun to play around with.

That’s it really. If you want to look at a bunch of output from varying models for the same simple question, you can see that below, but I won’t be offended if you skip the rest of the post. 🙂

And before you ask, I’m not trying to pivot into being an AI guru, but if Zuck wants to pay me $300M to play around with this stuff, I might consider it. 🙂

Cheers

Tim…

Basic Comparison

Some things to remember before we start.

  • The format of the output varies a lot when I am pasting it as text here, but typically it is formatted neatly on screen, usually like a markdown page, so ignore the formatting issues here.
  • The time it took to return an answer varied wildly. I’m not sure how much of that was due to the models, and how much of that was due to my laptop going on strike. As a result I ignored the timings and just looked at the output. With some better kit it would be nice to compare timings between models.
  • The runs of the local LLMs took so long I did very limited testing. 🙂

This was one of his questions.

“how do you join 2 lists in C#”

Claude Code is the cloud solution my colleague used, so it was pretty quick and gave him this.

In C#, there are several ways to join (concatenate) two lists:
1. Using Concat() (LINQ)
The most common and readable approach:
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
var combined = list1.Concat(list2).ToList();
// Result: { 1, 2, 3, 4, 5, 6 }

2. Using AddRange()
Modifies the original list (no new list created):
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
list1.AddRange(list2);
// list1 is now: { 1, 2, 3, 4, 5, 6 }

3. Using Collection Expression (C# 12+)
The newest and most concise syntax:
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
var combined = [..list1, ..list2];
// Result: { 1, 2, 3, 4, 5, 6 }

4. Using Union() (removes duplicates)
If you want to avoid duplicate values:
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var combined = list1.Union(list2).ToList();
// Result: { 1, 2, 3, 4, 5 } - note: 3 appears only once

Quick Comparison
Method	       Creates New List	      Preserves Duplicates   Performance
Concat()       Yes                    Yes                    Good
AddRange()     No (modifies original) Yes                    Best
[..list1, ..list2] Yes	              Yes                    Good
Union()	       Yes                    No                     Slower
Choose based on your needs:
Need to preserve both original lists? Use Concat() or collection expressions [..]
Want to modify the first list? Use AddRange()
Need to remove duplicates? Use Union()

The llama3 model produced the following.

There are several ways to join two lists in C#. Here are a few examples:
Using the Concat method:
List<string> list1 = new List<string> { "a", "b" };
List<string> list2 = new List<string> { "c", "d" };
List<string> joinedList = list1.Concat(list2).ToList();
// joinedList now contains: "a", "b", "c", "d"
The Concat method takes two sequences (in this case, lists) and returns a new sequence that contains the elements from both lists.

Using the Union method:
List<string> list1 = new List<string> { "a", "b" };
List<string> list2 = new List<string> { "c", "d" };
List<string> joinedList = list1.Union(list2).ToList();
// joinedList now contains: "a", "b", "c", "d"
The Union method takes two sequences and returns a new sequence that contains all the unique elements from both lists.

Using LINQ:
List<string> list1 = new List<string> { "a", "b" };
List<string> list2 = new List<string> { "c", "d" };
var joinedList = (from e in list1 select e).Concat(from e in list2 select e).ToList();
// joinedList now contains: "a", "b", "c", "d"
This uses LINQ to concatenate the two lists.

Using a simple loop:
List<string> list1 = new List<string> { "a", "b" };
List<string> list2 = new List<string> { "c", "d" };
List<string> joinedList = new List<string>();
foreach (string s in list1)
{
    joinedList.Add(s);
}
foreach (string s in list2)
{
    joinedList.Add(s);
}
// joinedList now contains: "a", "b", "c", "d"
This method is straightforward, but less elegant than the other options.

Note that these examples assume you want to concatenate two lists of strings. If you have lists of different types or more complex structures, you may need to use a different approach.

The deepseek-r1 model produced the following.

To join two lists in C#, you can use the following methods depending on your needs:
 
1. **Concatenate Two Lists (Combine into One List)**
Use **`List<T>.AddRange()`** to merge two lists:

List<int> list1 = new List<int> { 1, 2 };
List<int> list2 = new List<int> { 3, 4 };
List<int> combined = new List<int>();
combined.AddRange(list1);   // Add elements from list1
combined.AddRange(list2);   // Add elements from list2
// Result: combined = [1, 2, 3, 4]

### 2. **Join Lists into a Single String (Element + Separator)**
Use **`string.Join()`** to concatenate elements with a separator:

List<int> list1 = new List<int> { 1, 2 };
List<int> list2 = new List<int> { 3, 4 };
List<int> combinedList = list1.Concat(list2).ToList(); // Combine into one list
string result = string.Join(", ", combinedList);
// Result: "1, 2, 3, 4"

3. **Combine Elements from Two Lists (Element-by-Element)**

Use **`Enumerable.Zip()`** to merge elements from two lists (requires same length):

List<int> listA = new List<int> { 1, 2 };
List<int> listB = new List<int> { 3, 4 };
var combined = listA.Zip(listB, (a, b) => $"{a}-{b}").ToList();
// Result: combined = ["1-3", "2-4"]

4. **Merge Lists with Distinct Elements**
Use **`Enumerable.Concat()`** + **`Distinct()`** to avoid duplicates:

List<int> list1 = new List<int> { 1, 2 };
List<int> list2 = new List<int> { 2, 3 };
var merged = list1.Concat(list2).Distinct().ToList();
// Result: merged = [1, 2, 3]

Key Notes:
- **`AddRange()`** is ideal for merging **`List<T>`** objects directly.
- **`string.Join()`** requires elements to be **`IEnumerable<T>`** (e.g., strings, numbers).
- **`Zip()`** works only if both lists have the same number of elements.
- **`Concat()`** creates an **`IEnumerable<T>`**; use `.ToList()` to convert to a `List<T>`.
 
Choose the method based on whether you need a combined list or a joined string.

The gpt-oss:20b model produced the following.

Joining two lists in C#
What you probably mean by “join” is concatenating (or appending) the elements of two lists into a single list.
(If you meant a relational join by key, that’s a different story – see the section at the bottom.) 

Below are the most common ways to do it, with code snippets and a few gotchas.

1. Using AddRange (in?place)

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };
list1.AddRange(list2);   // list1 now contains 1,2,3,4,5,6
// list2 stays unchanged

Pros
Very fast – no allocations beyond the destination list.
Modifies the original list, so you don’t need a temporary variable.

Cons
list1 will grow; if you need a new list you must create one first.

2. Using LINQ Concat

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };

var combined = list1.Concat(list2).ToList();   // new list

Pros
Concise and expressive.
Works with any IEnumerable<T> – not just List<T>.
You get a new list, leaving the originals untouched.

Cons
Allocates a new list ( ToList()) – may be overkill if you just want a quick append.
Slight overhead from LINQ plumbing (but negligible for typical list sizes).

3. Using List<T>.AddRange on a freshly?created list
If you want a new list and prefer the imperative style:

var combined = new List<int>(list1.Count + list2.Count);
combined.AddRange(list1);
combined.AddRange(list2);

This pre?allocates the correct capacity, so the list won’t have to grow as you add elements.

4. Using Enumerable.Append in a loop (rare)
If you only have a few items or need to do some processing per item:

var result = list1.Select(x => x);   // start with list1
foreach (var item in list2)
{
    result = result.Append(item);
}
var combined = result.ToList();

Generally not recommended unless you have a very specific reason.

5. Union vs. Concat
Concat: Keeps all items, including duplicates.
Union: Returns distinct items, preserving the order of the first list then the second.

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var union = list1.Union(list2).ToList();   // 1,2,3,4,5

If you only need to avoid duplicates, Union is the way to go.

6. “Join” by key (relational join
Sometimes people use the term join to mean combining two collections based on a common key, just like a SQL join. In C#, that’s usually done with LINQ’s Join, GroupJoin, or SelectMany. Here’s a quick example:

var customers = new[]
{
    new { Id = 1, Name = "Alice" },
    new { Id = 2, Name = "Bob" }
};

var orders = new[]
{
    new { Id = 101, CustomerId = 1, Amount = 50m },
    new { Id = 102, CustomerId = 1, Amount = 25m },
    new { Id = 103, CustomerId = 2, Amount = 75m }
};

var customerOrders = from c in customers
                     join o in orders on c.Id equals o.CustomerId
                     select new { c.Name, o.Id, o.Amount };

foreach (var co in customerOrders)
{
    Console.WriteLine($"{co.Name} ordered #{co.Id} for {co.Amount:C}");
}

Output:
Alice ordered #101 for $50.00
Alice ordered #102 for $25.00
Bob ordered #103 for $75.00
Inner join ( join … on … equals …) – only matching pairs.
Left outer join ( GroupJoin + SelectMany with DefaultIfEmpty) – keeps all left items.
Full outer join – requires a bit more plumbing (you typically need to union the two sides).
If you’re after a relational join, choose the appropriate LINQ method ( Join, GroupJoin, SelectMany, DefaultIfEmpty, etc.).

Quick Reference Table
Scenario
Method
Result
Append two lists into one new list
list1.Concat(list2).ToList()
New list, duplicates kept
Append in-place
list1.AddRange(list2)
list1 grows, list2 unchanged
Append without duplicates
list1.Union(list2).ToList()
New list, duplicates removed
Relational join by key
`list1.Join(list2

The codellama model produced the following.

There are several ways to join two lists in C#, depending on the specific context and requirements. Here are a few common methods:

1. Using the `Concat` method:

List<int> list1 = new List<int>() { 1, 2, 3 };
List<int> list2 = new List<int>() { 4, 5, 6 };
var joinedList = list1.Concat(list2).ToList();

This will concatenate the two lists and create a new `List<int>` object that contains all the elements from both lists.

2. Using the `Join` method:

List<int> list1 = new List<int>() { 1, 2, 3 };
List<int> list2 = new List<int>() { 4, 5, 6 };
var joinedList = list1.Join(list2, i => i, j => j, (i, j) => new { First = i, Second = j });

This will join the two lists based on a common property and create a new `IEnumerable<T>` object that contains all the elements from both lists, with each element containing the properties of the two lists.

3. Using the `Union` method:

List<int> list1 = new List<int>() { 1, 2, 3 };
List<int> list2 = new List<int>() { 4, 5, 6 };
var joinedList = list1.Union(list2).ToList();

This will combine the two lists and create a new `List<int>` object that contains all the unique elements from both lists.

4. Using the `Intersect` method:

List<int> list1 = new List<int>() { 1, 2, 3 };
List<int> list2 = new List<int>() { 4, 5, 6 };
var joinedList = list1.Intersect(list2).ToList();

This will find the common elements in both lists and create a new `List<int>` object that contains only those elements.

These are just a few examples of how you can join two lists in C#. There are many other ways to do this, depending on your specific needs and requirements.

The deepseek-coder model produced the following.

In C#, the Join method can be used for combining two collections into one string or another collection type that implements IEnumerable<T> (like List). Here is an example of how it works on a list. Let's say we have these following code snippets in your project and you want to join them:

1) Using the Join method from LINQ(Language Integrated Query): 

List<string> names = new List<string> { "John", "Alice" };
var joinedNames = string.Join(" ", names); // Outputs: John Alice
Console.WriteLine(joinedNames);

2) Using the Join method manually (Without LINQ): 
If you want to join two collections without using any Linq, then it'll be a bit more manual operation and requires knowledge of looping mechanism in C# like for-each loops or foreach. Here is an example:  

List<string> names1 = new List<string> { "John", "Alice" };  // First list   
List<string> names2=new List<string>  {"Bob","Charlie"} ;// Second list to join the first one.    
names1.AddRange(names2);  
Console.WriteLine("Joined Result: {0}", string.Join (" ", names1)); // Outputs : John Alice Bob Charlie 
  
In both cases, this operation will generate a new List that contains all items from the two lists (one after another), and then it'll join these elements with spaces between them using `string.Join` method which returns final joined string/collection of strings in case if you want to print out combined list as output for instance console application or web API controller response header content values, etc..

The codestral model produced the following.

You can join two lists in C# by using the `AddRange()` method of the `List<T>` class. This method adds a collection to the end of the existing list.

Here is an example:

// Declare and initialize the first list
List<string> list1 = new List<string> { "one", "two", "three" };

// Declare and initialize the second list
List<string> list2 = new List<string> { "four", "five", "six" };

// Use AddRange to join the two lists
list1.AddRange(list2);

After executing this code, `list1` will contain all elements from both `list1` and `list2`.

There was a lot of similarity between the output from the models, but there were some differences. Specifically, the only model that suggested a solution using a Collection Expression was Claude Code, which was interesting.

Fedora 43 and Oracle

Fedora 43 was released a few days ago. Let’s start with the standard warning.

Here are the usual suspects.

As explained in the first link, I just do this for fun, and to see what is coming to RHEL/OL in the future.

Vagrant & Packer

I pushed Vagrant builds to my GitHub.

If you want to try these you will need to build a Fedora 43 box. You can do that using Packer. There is an example of that here.

What’s New?

What’s new with Fedora 43 you ask? You can read about it here.

There are newer versions of most of the packages, GNOME is now Wayland-only and they moved on to RPM 6.0. If your just a regular user like me, you won’t notice much of a difference. 🙂

Cheers

Tim…

Getting Started with Oracle

This morning I did a talk to some MSc students at Birmingham City University (BCU), and I said I would give them some useful links for playing about with Oracle. Rather than send it in a email, I figured it might be useful to write a post about it, so it was available to a wider audience. So here goes…

Live SQL (Free SQL)

If all you want to do is play about with writing SQL scripts to create database objects and query them, an easy starting point is Live SQL (Free SQL).

It’s web-based, so you don’t need to install anything to start playing with SQL on Oracle. You have a choice between using classic 19c, or a newer release.

APEX

If you want to learn how to use APEX, probably the easiest way to start is to request a workspace on apex.oracle.com.

Get yourself a free Oracle account and request a free workspace. In a few minutes you will be ready to start your APEX journey at no cost and with nothing to install.

VirtualBox Appliance

The two previous tools are web-based and only available if you are online. What if you want something on your local machine? Fortunately Oracle provide an Oracle VirtualBox Appliance, which you can download and import into VirtualBox on your server. It comes with a database and a bunch of development tool preinstalled in the virtual machine. You can get there appliance here.

Oracle AI Database 26ai Free

If you want to try your hand at a manual installation you can get your hands on Oracle AI Database 26ai Free here.

There are versions for Oracle Linux, Windows, ARM and Docker/Podman, so you should find something you can play with. Keep an eye out for new versions that are posted regularly.

Oracle Cloud Free Tier

Oracle Cloud has a free tier. You start off with some free credits, and when those run out, if you decide not to pay you can continue to use the free tier resources. You can find out about this here.

You can fire up a small Autonomous Database and be playing around with the latest and greatest in no time.

What about documentation?

All the documentation is online and available for free here.

There is a lot of it!

But what do I do once I’m connected?

That’s actually a difficult question to answer, because everyone’s journey is unique. It depends on what you are trying to learn.

If you want a basic primer on SQL, you can try my introductory posts here. It includes a link to a YouTube playlist.

https://oracle-base.com/articles/sql/articles-sql#getting-started

Once you’ve got to grips with the basics, you can try some of the more complicated stuff, like analytic functions.

For budding DBAs, you could start with the 2 Day DBA manual for 19c. It will cover some of the basics. Unfortunately this book hasn’t been updated for later releases, but most of the information is still relevant. I also have a basic roadmap, but it is really top-level.

You could also keep an eye out for free training and certification offers at Oracle MyLearn. We’ve just come to the end of one such period.

Hopefully this will give you some idea of how to take your first steps.

Cheers

Tim…

Automation: Perfection is the enemy of good

I was chatting to a colleague about automation and during the conversation it was clear we were falling into the trap of searching for perfection. It’s really easy to do, and as you know perfection is the enemy of good. 🙂

I don’t want to speak about specifics, but essentially there was a task they found really annoying, and I suggested we automate it, so it would just be a click of a button in Teamcity. They could then hand off the task to someone else, and they would never have to do it again. That was a promising start, but then we started to go down a rabbit hole, and the list of blockers grew, making it seem daunting. That’s when I tried to jump in and bring us back to basics.

  • Define the process you are currently doing.
  • Consider how to make each step as simple as possible.
  • Write the command(s) to complete each of the steps.
  • Thread all the steps together, as best you can.
  • If possible, call the script(s) from an automation tool, so you can allow someone else to run it.

It’s all baby steps, but each step makes things a little easier, more reliable and less annoying. If you get to the last step where you have a truly automated process that’s great, but if you only get part way, you’ve still made your life better. I described a similar progress path for automating DBA tasks here.

The problem with thinking too much about the final goal is you can get paralyzed by complexity. I think of it like trying to write a book. When you think about writing a book it’s really overwhelming, but if you think about just completing one page, that feels possible. You could argue a book is just a collection of pages, so just start writing some pages… 🙂

I understand if you are a cloud provider you need to deliver complete solutions for automation, but when you are working in your company you have a bit more flexibility as you are building automations. Each little improvement makes life better and saves you time.

So when you feel yourself descending into the depths of defining perfection, remember the mantra of “perfection is the enemy of good”, and just do something positive. If you get to perfection, that’s great, but often something is better than nothing!

Cheers

Tim…

Joel Kallman Day 2025 : It’s a Wrap! (#JoelKallmanDay)

Yesterday was an Oracle community day called the Joel Kallman Day (#JoelKallmanDay) 2025.

I would like to say a big thank you to everyone who took the time to join in. Here is the list of posts I saw. If I missed you out, give me a shout and I’ll add you. 

Along the way I noticed Jonathan Dixon posted a link to an APEX blog aggregator, which you might find handy.

https://apps.cloudnueva.com/ords/r/apps/apex_blogs/home

Thanks to everyone who got involved with blog posts, tweets and retweets. It was tough to find all the posts this year. The microblogging space has been fragmented over the last few years. I found many of the posts on LinkedIn and BlueSky, as well as the usual Tweets on Twitter (not X). 🙂

I know I say this every year, but I want to say a big thankyou to first timers. You are the future. Also, a special thanks to those who represented the non-English speaking communities. There is a demand for this, so don’t feel pressured to publish in English unless you want to!

Thanks again, and remember you’ve got a year to get ready for the 2026 event! 🙂

Cheers

Tim…

Oracle AI Database 26ai Released

If you were on the internet yesterday you will have seen that Oracle announced Oracle AI Database 26ai at this year’s Oracle AI World. Mike Dietrich wrote about it here, which should answer some of the obvious questions.

Here are some comments.

Installations

The first thing I did was download the Oracle AI Database 26ai Free version and do some installs. Here are the posts.

Here are the vagrant builds.

What is it?

There are a couple of different answers to this, depending on who you are employed by.

  • I think it’s very easy to argue this is really the release update 23.10. If you look at the new features in this version, it is actually like a regular release update. You can see the list here (Release Update 23.26.0). Compare that to the previous release updates and you will see some of them included more new features than this. That doesn’t feel worthy of a name change to me.
  • They’ve added so much stuff to 23ai in the quarterly updates since it’s release, it is practically a new version…

I’ll leave it up to you to decide which one best suits this release.

What is happening with version numbers?

As Mike describes, the version numbering has changed. What we should see going forward is the second number is the year and the third number is the quarter. For example.

  • 23.26.0 – October 2025 (special case to start the ball rolling)
  • 23.26.1 – January 2026
  • 23.26.2 – April 2026
  • 23.26.3 – July 2026
  • 23.26.4 – October 2026
  • 23.27.1 – January 2027

I actually prefer this numbering system. I think the old version numbering confused people outside of the Oracle space. On more than one occasion people thought for example that 19.5 was a higher version than 19.10, as in there mind they were reading 19.1.0. I think this current numbering is far less ambiguous, and I’m told it matches the cloud version numbering, so I guess that is good.

But what about the name?

I don’t care anymore. I was annoyed by the switch from 23c to 23ai, and here we go again. I think I’m done with getting annoyed by this now. The only way I can stay sane is just concentrate on the version number. So 23c, 23ai and 26ai are all the same thing, because their version numbers all begin with 23. Similar to what happened with 12.2 code base which was named 12c, 18c and 19c. I’m sure someone at Oracle will try to convince me different, but don’t bother trying, because we both know the score.

Upgrades?

As Mike points out, applying the October RU to 23ai turns it into 26ai, so there isn’t a full upgrade for this. It’s almost like it is just 23.10…

When will it come out on-prem?

This is just my opinion and I have no inside information, but I don’t think we will ever see a normal on-prem version of 23ai/26ai. I think that ship has sailed.

If you need the 23ai/26ai functionality, then buy an engineered system (Exadata or ODA), move to the cloud, or get Cloud@Customer.

Alternatively, wait and see what gets backported to 19c for us regular folks.

If none of those alternatives suffice, then I guess you need to start looking for an alternative on-prem database. There are plenty available.

What are you going to do with the website categories?

When 23c switched to 23ai I went through and rebranded all my posts. I’m not going to do that this time. I’ve added a new 26ai page here. I’ll post links back to the 23ai articles, but add new stuff to this page for features that are released or enhanced in 23.26.0 onward.

Overall

Yeah. It happened. Deal with it…

Cheers

Tim…

Thinking about community #JoelKallmanDay

Several recent events have made me reflect about community, so I thought it might be worth writing about it for #JoelKallmanDay.

Creators

Communities require people who actually produce something. In the case of the Oracle community that includes bloggers, YouTubers and people who help answer questions on the internet. That’s not an all encompassing list, but you get the idea.

There has to be something for people to gather around, but this group is typically very small compared to the total size of the community.

Active Consumers

These are the folks who engage with the content and get involved by providing feedback, and sharing the content to others. This group of people is typically larger than the creators, but still relatively small.

Passive Consumers

The vast majority of the community are passive consumers. They read/watch the content, but never interact. If you didn’t have viewing statistics, you wouldn’t know they exist.

It’s easy to be a bit dismissive about these folks, but as I said, they are the vast majority of the community, so they are equally important, as page views are great for massaging the fragile egos of your typical creator types. 🙂

Community Shepherds

An often overlooked group of people are those that help to organise the community. They could be user group and event organisers, or groups like the Oracle ACE Program.

This is a really small section of the community, who have the unenviable task of trying to handle the egos of the creators, and the demands of the community. This is a near impossible task in my opinion, as each group tends to think they are the most important group, and I guess they both are in their own way. 🙂

I’ve been in the Oracle ACE Program a long time, and I’ve seen many faces come and go. The recent departure of Jennifer Nicholson is tough because it came as a shock to all of us, including her. I don’t want to say anything more about this, other than it was a mistake in my opinion. It can take a long time to build trust between people, and that trust can easily be broken.

The impact of AI on communities

I wrote about the impact of AI on content creators here.

Just looking at my own feeds, I see a constant increase in AI generated slop, which makes me want to disconnect. Life is a little easier for established people like myself, because we got into the game before the current wave of slop. I do worry about the next generation though. Trying to stand out amongst the sea of generated content will be a hard job. We’ve just got to hope people aren’t put off.

Balance

For a community to function it needs a good balance of these people. Too many of one type of people can create problems. Too many bad actors can quickly spoil a community, and it’s always a shame when you see a community fail.

You will often see variations of the 1-9-90 rule (1% post and create; 9% share, like, comment; 90% view-only). This is mentioned in the Wikipedia post about online communities. It’s worth a look if you have a few minutes.

So when you are part of a community, regardless of the part you play, keep the idea of balance in mind. You are part of that balance, so please don’t rock the boat too much. 🙂

Cheers

Tim…

Perspective is everything

Something happened to me yesterday and it has been bugging me, so I thought I would write about it to try and get it out of my head…

What happened?

I was driving on a two lane stretch of a motorway (freeway) with no hard shoulder (shoulder, breakdown lane). I was doing about 60 mph in a 70 mph zone. There were cars behind me and a bit of a gap in front of me before the next car, but we were all doing a similar speed in my lane, so I wasn’t holding up traffic. Just leaving a reasonable amount of breaking room. The road was busy, but moving consistently. A lorry was in the overtaking lane, but because they are speed limited, he wasn’t really getting anywhere, so we stayed like that for a while. Then he started to indicate to pull into my lane. My car is really small and grey, so I started to think I maybe I was in his blind spot and I was going to get killed.

I put my foot down and got in front of him, but this left me a little too close to the car in front, so I indicated and pulled into the overtaking lane in front of the lorry. He instantly started to flash his lights and hammer on his horn, so I guess he was not happy, thinking I was too close, which IMHO I was not. I had no intention of finding myself under a truck, so despite what he might think, I was good. I was now doing 70 mph (the speed limit) so I left him behind, but I saw him pull into the gap I had vacated, which I guess was his original plan…

His perspective

I’ve watched those “f*ck around and find out” videos on YouTube where crazy people cut up trucks, so I’m guessing this truck driver was thinking I was some douchebag car driver that doesn’t respect him and his vehicle, hence his road rage display.

My perspective

I’m in a tiny car with a massive truck next to me, and no hard shoulder to pull over to if the trucker does something stupid. I’m normally pretty chilled out when I’m driving, but this did spook me. If he hadn’t indicated I would happily have stayed like this for a few miles and would probably eased ahead of him over time, but the fact is he did indicate and that made me nervous. What were my options?

  • Slow down and annoy the people behind me, as we were already doing about 10 mph below the speed limit.
  • Do nothing and hope he doesn’t kill me.
  • Speed up and tailgate the car in front, putting the two of us at risk.
  • Do what I did and get the hell out of Dodge!

I’ve thought about this a number of times and I still think I made the best choice, but then I guess I would. 🙂

Perspective is everything

This is a classic case where perspective is everything. I can imagine seeing a dashcam clip of this and thinking the car driver was a bit of a dick, and being on the truck driver’s side. But I can tell you my intention was to stay in my lane and chill, and the truck driver spooked me into doing this. The same scene from two different perspectives feels very different.

So what?

I thought this was very interesting, because we have no doubt both come away from that situation insisting we were in the right, and from our perspectives we both were. I would love to talk to this trucker and explain how things looked from my side, not because I think I would change their mind, but just so they could see where I was coming from…

There is a bigger lesson here. It’s easy to be on the outside looking in and think you have all the answers, but you never really know how you would react in the other person’s situation. As the saying goes, don’t judge a person until you have walked a mile in their shoes. I’m not always successful, but I try. 🙂

Cheers

Tim…

PS. I’m kind-of annoyed at myself for still giving a crap about this… 🙂

Joel Kallman Day 2025 : Announcement

Since 2016 we’ve had an Oracle community day where we push out content on the same day to try and get a bit of a community buzz. The name has changed over the years, but in 2021 it was renamed to the “Joel Kallman Day”. Joel was big on community, and it seems like a fitting tribute to him.

When is it?

The date is Wednesday October 15th. That’s just over two weeks away from today!

How do I get involved?

Here is the way it works.

  • Write a blog post. The title should be in the format “<insert-the-title-here> #JoelKallmanDay“.
  • The content can be pretty much anything. See the section below.
  • Tweet out the blog post on social media using the hashtag #JoelKallmanDay.
  • Publishing the posts on the same day allows us to generate a buzz. In previous years loads of people were on social media retweeting, making it even bigger. The community is spread around the world, so the posts will be released over a 24 hour period.
  • Oracle employees are welcome to join in. This is a community day about anything to do with the Oracle community.

Like previous years, it would be really nice if we could get a bunch of first-timers involved, but it’s also an opportunity to see existing folks blog for the first time in ages! 

The following day I write a summary post that includes links to all the posts that were pushed out through the day. You can see examples here.

What Should I Write About?

Whatever you want to write about. Here are some suggestions that might help you.

  • My favourite feature of {the Oracle-related tech you work on}.
  • What is the next thing on your list to learn.
  • Horror stories. My biggest screw up, and how I fixed it.
  • How the {a specific piece of tech} has affected my job.
  • What I get out of the Oracle Community.
  • What feature I would love to see added to {the Oracle-related tech you work on}.
  • The project I worked on that I’m the most proud of. (Related to Oracle tech of course)

It’s not limited to these. You can literally write about anything Oracle or community related. The posts can be short, which makes it easy for new people to get involved. If you do want to write about something technical, that’s fine. You can also write a simple overview post and link to more detailed posts on a subject if you like. In the previous years the posts I enjoyed the most were those that showed the human side of things, but that’s just me. Do whatever you like. 

Do I have to write in English?

No! It’s great to see people contributing to their own community. Google Translate does a pretty good job of translating them, so we can still read them.

Do I need to write about Joel or APEX?

I’m sure people would be happy to read stories about Joel, or content about APEX, but you don’t have to write about that. You can write about whatever you want, so long as it has an Oracle and/or community spin…

So you have a little over two weeks to get something ready!

Cheers

Tim…

A little story of Oracle APEX (Low Code) for the win

As the undisputed worlds worst APEX developer, I just wanted to share a little story that illustrates why I love APEX when trying to deliver value as quickly as possible.

Background

We have an interface that uploads data from an on-prem service to a 3rd party product running on a cloud service. It does this by pushing files to a S3 bucket on AWS. It’s a scheduled task and it works fine.

Occasionally the end users need to send some different data through to the bucket for the 3rd party to pick up. The first thought was we could give the users access to the bucket and let them use a tool like Cyberduck to push their file to it. This doesn’t sit well with me as there are no guard rails, and if they break something, it’s up to us to fix it.

So after chatting with my boss I suggested I knock up a quick POC in APEX to allow them to upload the files.

What I did

We already had an APEX environment for this on-prem system, so I didn’t have to worry about any APEX or ORDS setup. I could just piggy back on the existing infrastructure. We use Azure AD, so it is really easy to enable MFA for APEX applications using the APEX social sign-on functionality (see here).

I created a single page app that allowed the users to upload a file to the server, and then call a script on the server to transfer the file to the S3 bucket.

It was a few minutes and we had something that was working and ready for the users to try.

I talked one of the users through it, and he successfully uploaded a file. I told him to hold off until I had squared things on my end, as my boss was away.

When my boss got back we discussed it, and couldn’t think of anything more we needed to do, so basically we were ready to go live with it.

Why low code works

The reason why I like low code is it only takes minutes to go from idea to solution for little things like this. You can be a pretty mediocre APEX developer, like me, and still deliver value quickly, and more importantly safely.

Another big plus for APEX is this isn’t a solution that will go out of date when a new framework appears. I’ve got apps I created over a decade ago that are still up to date and running fine. The database is patched on a regular basis, and we apply APEX patches as they are released. It just works. If we compare that to some of our other software, the effort of trying to keep them in a supported state is considerable, and in a few cases it’s nearly impossible.

Conclusion

For any APEX developers out there, you already understand why APEX is so useful for anything from quick prototypes to complex enterprise developments. For people that haven’t used low code tools before, you will be surprised how much of your existing development workflow could be replaced by them.

Remember, our job is to get value to the customers in a timely manner. If we are being held back by our development environment, that is our problem, not theirs.

This is just another example of me cosplaying as a developer. 🙂

Cheers

Tim…

PS. I’ve written a small number of articles about APEX over the years, as well as some surrounding technologies.