Platform Architecture
ApexOS is delivered as a complete appliance platform: a purpose-built Linux operating system that hosts ApexOS Core and its add-ons (shown as Apps in the UI) as containers. This page describes the layers of that stack and how they fit together.
The operating system layer
ApexOS Operating System is not a general-purpose Linux distribution. It is built with Buildroot and optimized to do one thing: run ApexOS reliably on small devices. Key properties:
- systemd init with a minimal userland (BusyBox individual binaries).
- Read-only root filesystem using EROFS with LZ4HC compression. The system partitions are never written at runtime.
- ZRAM compressed memory backing for
/tmp,/var, and swap, minimizing flash wear and I/O. - Docker Engine as the container platform for everything above the OS.
- AppArmor kernel security module confining the managed containers; the active profile set is fetched per release channel.
- NetworkManager for network configuration, with connection profiles persisted across updates.
- Bootloaders: GRUB on UEFI systems, U-Boot on boards without UEFI, and the Raspberry Pi 5's native
trybootmechanism (driven through a custom RAUC backend).
A/B system partitions and RAUC
The OS updates atomically using RAUC with a dual-slot (A/B) layout. The disk carries these partitions (GPT or hybrid MBR/GPT depending on the board):
| Partition label | Purpose |
|---|---|
apexos-boot | Shared FAT boot partition (bootloader config, RAUC status database rauc.db) |
apexos-kernel0 / apexos-system0 | Slot A: kernel + root filesystem |
apexos-kernel1 / apexos-system1 | Slot B: kernel + root filesystem |
| overlay | Persistent overlay for host configuration under /etc (hostname, hosts, network connections, sysctl, udev rules, module options, …), mounted at /mnt/overlay and bind-mounted over the read-only root |
apexos-data | The data partition (see below) |
An update is written to the inactive slot and the bootloader is pointed at it; if the new slot fails to come up, the bootloader falls back to the previous slot after three failed boot attempts. Because the running system is never modified in place, an interrupted update cannot brick the device.
RAUC bundles use the verity format and are verified against a keyring shipped in the OS (/etc/rauc/keyring.pem) before installation. Each board has its own compatibility string (for example apexos-rpi4-64), so a bundle built for one board cannot be installed on another.
The data partition
All mutable state lives on the ext4 data partition, mounted at /mnt/data. It contains:
- the Docker data root (all container images and containers),
- the Supervisor's data directory (
/mnt/data/supervisor), including its updater configuration and AppArmor profiles.
On first boot the OS expands the data partition to fill the disk. The data partition can also be relocated to a separate disk. A swapfile on the data partition backs the ZRAM swap hierarchy.
From the point of view of the containers, the important mounts are:
/config— ApexOS Core's configuration directory./apexos— the ApexOS configuration mount that add-ons can request (declared asapexos_configin an add-on'sconfig.yaml), giving an add-on access to the Core configuration tree./share,/addon_configs, per-add-onaddon_config— additional shared and per-add-on storage that add-ons opt into through theirmapconfiguration.
The container layout
The OS itself starts exactly one managed container: the Supervisor. A host service (apexos-supervisor.service) pulls, verifies, and runs the Supervisor image (registry.apexinfosys.in/apexos/<arch>-apexos-supervisor), and includes self-healing logic — if the Supervisor container is found corrupted at startup, the image is discarded and re-pulled at the version advertised by the device's release channel.
The Supervisor in turn uses the Docker engine to manage every other container:
| Container | Role |
|---|---|
apexos | ApexOS Core — the application itself, serving the web UI |
apexnet_supervisor | The Supervisor |
apexnet_dns | DNS plug-in for the internal container network |
apexnet_audio | Audio plug-in (sound server for containers) |
apexnet_cli | Command-line plug-in backing the apex console command |
apexnet_multicast | Multicast/mDNS plug-in |
apexnet_observer | Observer plug-in — an independent watchdog endpoint for checking Supervisor health |
| add-ons | One container per installed add-on (App) |
The plug-in set (supervisor, dns, audio, cli, multicast, observer) plus a Core image is preinstalled into the data partition of every OS image, so a device is fully functional on first boot even before it has network access to the container registry.
The console CLI
On the device console (TTY), the OS runs a small CLI wrapper instead of a login shell. It waits for the apexnet_cli container and drops the user into the apex command environment; if startup takes too long, it offers an emergency shell for troubleshooting (docker ps -a, journalctl -e, …).
First boot and the landing page
OS images do not bundle the full multi-hundred-megabyte Core image. Instead, the preinstalled "core" container is a lightweight landing page build. On first boot:
- The Supervisor starts and serves the landing page on the Core port.
- In the background, the Supervisor downloads the real ApexOS Core image for the device's channel from the container registry.
- When Core is ready, it replaces the landing page and onboarding begins.
The web UI: port 1702
ApexOS Core serves its web interface on TCP port 1702 (the built-in default server port). The first-boot landing page is served on the same port, so http://<device>:1702 works from the moment the device boots.
Update channels
Every device follows one of three release channels:
| Channel | Purpose |
|---|---|
dev | Development builds |
beta | Pre-release / staging builds |
stable | Production releases |
Channel state is resolved from https://version.apexinfosys.in/<channel>.json, which advertises the current Supervisor, Core, plug-in, and per-board OS versions for that channel. The OS build stamps its own channel into the image (DEPLOYMENT in /etc/machine-info), and the Supervisor's updater configuration is seeded with the same channel at image build time and re-seeded at boot if missing — so a dev or beta build never accidentally follows the stable channel. AppArmor profiles are likewise fetched per channel (apparmor_<channel>.txt).
See Updates for the full update flow, and the Glossary for term definitions.