SmartOS: The illumos Way of Thinking About Servers

SmartOS: The illumos Way of Thinking About Servers

16 min read

As I often say, I like simplicity and predictability. My servers are generally built around that principle: the server itself (physical or virtual) is just a coordinator of containers. When it's based on FreeBSD, those containers take the form of jails and VMs. In case of disaster, restoring is simple, and I talked about it at EuroBSDCon 2026 in Brussels.

But there's another operating system that I appreciate and have been using for quite some time, and it's perfect for this purpose: illumos.

Born as a fork of OpenSolaris, it incorporates everything the brilliant minds at Sun designed and built. Many of those minds still work on illumos (and its distributions) today, making it a modern and extremely mature operating system.

ZFS was born at Sun, and illumos is the proof: it's not just its file system, everything is designed around it, with a level of integration that goes beyond even FreeBSD's. There are also technologies like Crossbow (network virtualization), DTrace, very advanced resource controls and, above all, something very dear to me: zones. Inspired by FreeBSD jails, zones strictly separate all the components and create secure and isolated compartments. There's also good support for LX (Linux) zones, so it can run software compiled for Linux inside them. I'll come back to this in detail later. On top of that, it supports virtualization with both bhyve and KVM, giving the (practically unique, in the open source world) possibility of using both the efficiency of bhyve and the broad compatibility of KVM. SMF, five years older than systemd, is extremely powerful, even if not particularly intuitive for those who don't know it.

As you can guess, there are many similarities between illumos and the BSDs, and it's no coincidence: the licenses and the minds are compatible, and so is the philosophy, and there's a lot of exchange of ideas, code and solutions. bhyve, born on FreeBSD, performs excellently on illumos and, in some areas, has even surpassed the FreeBSD implementation. Tests I ran myself about a year ago showed that a nearly idle VM still used some (little) host CPU on FreeBSD, while it used zero (ZERO!) on illumos.

illumos has several distributions. Among them there's OpenIndiana, general purpose and comparable to Linux distributions, and Tribblix, which "blends a retro style with modern components", developed and maintained by Peter Tribble with constancy and regularity. Then there's OmniOS, a traditional server distribution, and SmartOS, the one I use the most and the one this post is mainly about.

SmartOS, in fact, was born for servers. Its concept is simple and powerful: the hardware (or the VM) is a pure "hypervisor" of zones or VMs. VMs themselves run inside dedicated zones, one per VM, so even in case of a VM escape, you still end up inside a zone, so there's another layer. This "onion" approach increases security. LX zones are considered very important in SmartOS, so they get the best possible support. Today it's possible to run complete Debian zones because, unlike FreeBSD jails (but this is changing), they can run systemd too. I've given clients entire Debian systems in LX zones and they never noticed the difference. Also, unlike jails, a zone only sees the resources assigned to it: while a FreeBSD jail will still see all the host's RAM (and gets blocked when it exceeds the assigned RAM), illumos zones (native or LX) will only see the assigned resources as their limit. An Alpine LX zone on illumos with 512 MB of RAM assigned will show, with the free command, only 512 MB total.

SmartOS is also designed as a live, mostly read-only operating system. It's normally booted from external media, although it can also boot directly from the ZFS pool. Upgrading is simply... booting a new platform image. On first boot, it will ask to create a ZFS pool to store configurations, logs and zones, but the whole OS is immutable. This increases both security and reliability. It can also be installed but, in practice, the approach doesn't change: a ZFS dataset will be created on the local pool and used to boot and to read the OS files. For a traditional installation, other distributions are a better fit.

In this post I'll show a basic SmartOS installation (on a physical host or a VM) and the creation of a couple of services. The goal is to show some of its peculiarities and hopefully spark some curiosity about this fantastic OS (and about our illumos.cafe).

The first thing to do is download the SmartOS ISO (or USB image, depending on how you'll boot it). On average there's a release every two weeks, with its changelog (only if there's something to release - nobody here ships updates just to shout that there's been an update). So go to https://docs.smartos.org/download-smartos/ and:

  • Download the ISO if you're booting from a CD-ROM (physical or virtual)
  • Download the USB image if you're booting from it or from a USB stick. This USB image only contains the read-only OS, so you'll need another disk for the ZFS pool.

Once booted, the first question will be the IP address of the "admin" interface. SmartOS normally expects to live in a datacenter, with one interface dedicated to administration and another one to workloads. In this example we'll use the same one, assuming we're on a LAN. It will then ask for the DNS servers and the NTP server, and complete the network setup. Next it will propose a disk for the ZFS pool - careful, make sure it's the right disk, because it will wipe everything. At that point it will ask whether you want to boot from a pool (like that one) or keep booting from the device you just booted from. When possible, I keep booting from a separate device. When my SmartOS runs in a bhyve VM on FreeBSD, I have a config like this:

loader="uefi"
cpu="8"
memory="16G"
network0_type="virtio-net"
network0_switch="public"
disk0_type="nvme"
disk0_name="smartos.img"
disk1_type="nvme"
disk1_name="disk0.img"
[...]

That is, one disk for SmartOS (the downloaded "USB image") and one for the zones pool. Upgrading SmartOS means downloading a new image from the website, replacing smartos.img and rebooting the VM. Profit.

Next it will ask whether we want to install pkgsrc, NetBSD's package manager which, yes, is native here too. It's not strictly needed on the hypervisor itself, but I usually install it anyway. Then it will ask for the root password and the system hostname. Yes, the root password: no sudo, etc. The idea is that, in theory, root should only log in from the dedicated interface and only to manage the hypervisor. Because, remember, workloads aren't supposed to run on the "base" SmartOS, but inside zones.

The installation will be very fast because it's basically the creation of a ZFS pool, some swap space, a few small configuration files and, if requested, pkgsrc. You can then reboot and the system will start.

SmartOS doesn't boot fast. It's a server OS, it's not expected to reboot often, but it performs all the necessary checks.

illumos isn't merely Unix-like. It is Unix by lineage, derived directly from Unix, although it isn't UNIX-certified. And some commands aren't even remotely comparable to the Linux or BSD ones. That's one of the reasons why, in the late '90s and early 2000s, I looked sideways at the Sun SPARCstations at university: I didn't know the commands and considered them "weird". It took me years to understand their power.

There's no top, there's prstat. prstat -Z 1, for example, will also show the per-zone load, very useful to understand what's going on.

SmartOS provides native tools to list, download and install ready-made disk images (for zones or VMs), and to create, start and access the console of zones and VMs - and the commands are the same for both. So whether it's a zone or a VM, there isn't much difference. There's also a web UI, which can help (at least partially) with creating and managing zones and VMs, easy to install following the instructions on its page: https://docs.smartos.org/web-interface/

The goal of SmartOS is to provide, in the "global zone" (the hypervisor itself), only the tools to manage the host and to create more zones. Everything should go into zones. Even a simple DHCP server. A concrete example, which gives an idea of how everything is supposed to work, is this one: https://docs.smartos.org/nat-using-etherstubs/

With imgadm avail, for example, you can see all the disk images available on the servers. They're divided into zone-dataset (native illumos zones), lx-dataset (LX, that is Linux, zones) and zvol (actual VMs). For example, I want to see all the ready-made Alpine Linux images:

[root@sos01 ~]# imgadm avail|grep alpine
f48e3bf4-6d02-11e5-9a1f-53a8ab833767  alpine-3                        20151007      linux    lx-dataset    2015-10-07
96bb1fac-c87d-11e5-b5bf-ff4703459205  alpine-3                        20160201      linux    lx-dataset    2016-02-01
712f1436-0311-11e6-b4d4-139262136691  alpine-3                        20160415      linux    lx-dataset    2016-04-15
d8830f1e-3680-11e6-be72-2ba188e02d31  alpine-3                        20160620      linux    lx-dataset    2016-06-20
77bc3f50-8f4c-11e6-90b6-f7b69b9dcf20  alpine-3                        20161011      linux    lx-dataset    2016-10-11
e7b557c2-b2a8-11e6-b2c1-7f681aa61371  alpine-3                        20161125      linux    lx-dataset    2016-11-25
0c8b0b40-c0ce-11e6-81ce-f74ad6815a5d  alpine-3                        20161213      linux    lx-dataset    2016-12-13
4d3ed29a-c851-11e6-b5a9-639aada4a9c8  alpine-3                        20161222      linux    lx-dataset    2016-12-22
212c8962-ec06-11e6-8cee-5b0a09ebef00  alpine-3                        20170206      linux    lx-dataset    2017-02-06
19aa3328-0025-11e7-a19a-c39077bfd4cf  alpine-3                        20170303      linux    lx-dataset    2017-03-03
b5d89e30-128d-4ea4-824f-8e36fd7e3703  alpine-3                        20230721      linux    lx-dataset    2023-07-21
029a1752-71c8-4d30-a2aa-dcdc4ee32008  alpine-3                        20231121      linux    lx-dataset    2023-11-21
632a25ad-15dc-42f0-a23b-743b37f62cbb  alpine-3                        20240726      linux    lx-dataset    2024-07-26
0b7d45d9-be8c-439f-9a24-bd6223199c2b  alpine-3                        20250120      linux    lx-dataset    2025-01-20
c38b1b62-6d89-41f1-bd1d-5342b2d783d7  alpine-3.21.3                   20250407      linux    lx-dataset    2025-04-07
2ab26a8c-616b-4b2f-9a30-b121c044c278  alpine-3.23-nocloud             20260311      linux    zvol          2026-03-26
95766d4b-a225-4755-b630-1eae6b34b447  alpine-3.23-nocloud             20260501      linux    zvol          2026-05-01

The latest Alpine Linux LX image available is 3.21 - it's possible to create custom ones, but that's outside the scope of this guide.

First, let's use imgadm to import the image we need, using its UUID:

imgadm import c38b1b62-6d89-41f1-bd1d-5342b2d783d7

In a short time, the image will be installed and will populate its dataset.

I usually keep configurations in a dedicated dataset. It's not necessary, but it's useful to find them later, remember what I did and, if needed, create similar zones.

I won't cover IPv6 in this example, since its setup depends heavily on the host, the network topology, and the addressing model provided by the upstream network.

To keep things simple and reproducible, I'll stick to a basic IPv4 setup with the zone behind the existing NAT on the LAN. IPv6 can certainly be added, but it deserves to be treated separately rather than squeezed into an example whose purpose is simply to show the fundamentals.

zfs create zones/jsons
cd /zones/jsons

In that directory, create a file called lx_alpine.json:

{
  "brand": "lx",
  "resolvers": [
    "8.8.8.8",
    "8.8.4.4"
  ],
  "kernel_version": "5.10.0",
  "ram": 1024,
  "alias": "alpine01",
  "customer_metadata": {
  },
  "nics": [
    {
      "nic_tag": "admin",
      "ips": [
        "192.168.1.2/24"
      ],
      "gateway": "192.168.1.1"
    }
  ],
  "image_uuid": "c38b1b62-6d89-41f1-bd1d-5342b2d783d7",
  "quota": 10
}

Network settings will obviously vary, but here I've basically created an Alpine Linux LX zone with 1 GB of RAM assigned and a 10 GB disk quota. The image_uuid matches the one we downloaded with imgadm - just run imgadm list to see all the images on the system.

The command to create the zone is:

vmadm create -f lx_alpine.json

The system will create an LX zone based on the downloaded Alpine 3.21 image.

To access the zone, once it's ready (check with vmadm list), use the zlogin command. Careful: zlogin doesn't want the alias but the zone name, which is a UUID. But it supports tab completion, so just type zlogin alp and press tab - it will first complete to alpine01 and then, at the second tab, replace it with the correct UUID of the zone.

Once inside, you can update packages, install software, etc. Just one caveat: Alpine Linux changed some things with apk 3.x (so from 3.23 on) and it seems that, without IPv6, updates can cause problems. The workaround that worked for me was adding a line like 151.101.66.132 dl-cdn.alpinelinux.org to /etc/hosts (it's a CDN address, so it may change over time) and, at that point, you can move directly to Alpine 3.24, the latest currently available.

Commands like top, free, df, etc. will work and return the assigned values, almost as if it were a VM.

In this example I used Alpine, but many distributions are available and supported.

But LX zones are a compatibility layer. SmartOS also has native zones, usually created from the base images, and we can easily add one:

imgadm avail | grep base | tail
85d0f826-0131-11ed-973d-2bfeef68011c  base-64-lts                     21.4.1        smartos  zone-dataset  2022-07-11
93bdf06a-01ef-11ed-81ff-bf0efad842c7  base-64-lts                     20.4.1        smartos  zone-dataset  2022-07-12
e44ed3e0-910b-11ed-a5d4-00151714048c  base-64-lts                     22.4.0        smartos  zone-dataset  2023-01-10
e6b8e342-c19d-11ed-b9d1-00151714048c  base-64-trunk                   20230313      smartos  zone-dataset  2023-03-13
0b13305e-4fe1-11ee-b322-00151714048c  base-64-trunk                   20230910      smartos  zone-dataset  2023-09-10
db382b98-822e-11ee-992e-00151714048c  base-64-trunk                   20231113      smartos  zone-dataset  2023-11-13
8adac45a-aca7-11ee-b53e-00151714048c  base-64-lts                     23.4.0        smartos  zone-dataset  2024-01-06
8925400d-ff66-441e-ab5c-340505636ff8  base-64-trunk                   20240116      smartos  zone-dataset  2024-01-16
2f1dc911-6401-4fa4-8e9d-67ea2e39c271  base-64-lts                     24.4.1        smartos  zone-dataset  2025-01-06
b933df4b-b8f4-4bef-ad1e-236a20304496  base-64-lts                     25.4.0        smartos  zone-dataset  2026-01-09

So:

imgadm import b933df4b-b8f4-4bef-ad1e-236a20304496

That's the latest one, 25.4.0. Then we need a proper JSON for native zones (the brand is joyent, a legacy of the company that created SmartOS). Let's make one for nginx, for example - nginx.json:

{
  "brand": "joyent",
  "image_uuid": "b933df4b-b8f4-4bef-ad1e-236a20304496",
  "ram": 512,
  "hostname": "nginx",
  "resolvers": [
    "9.9.9.9",
    "1.1.1.1"
  ],
  "alias": "nginx",
  "nics": [
    {
      "nic_tag": "admin",
      "ips": [
        "192.168.1.254/24"
      ],
      "gateway": "192.168.1.1"
    }
  ],
  "customer_metadata": {
  },
  "quota": 5
}

Then:

vmadm create -f nginx.json

Once the zone is ready, just use zlogin as usual. Once inside, we can install nginx (and useful packages like certbot, etc., if needed) with pkgin (pkgin search certbot will show the exact package names):

pkgin in nginx py314-certbot-nginx

At the end, the banner will tell us how to start nginx at zone boot:

/usr/sbin/svcadm enable -r svc:/pkgsrc/nginx:default

At this point we'll have two zones - one native, one with Alpine Linux - ready to run our workloads. The same principle can be used to create VMs, with both bhyve and KVM, but that will be the topic of a future article.