Skip to main content

Getting started with add-ons

Add-ons (shown as Apps in the ApexOS UI) are containerized services that run next to ApexOS on the same device, managed by the Supervisor. An add-on is defined by a small directory of files: a metadata file that tells the Supervisor what the add-on needs, a Dockerfile that describes how the container image is built, and whatever scripts and assets your service requires at runtime.

This page walks through the anatomy of an add-on, the base images you build on, and how to develop an add-on locally against a real device.

Anatomy of an add-on

A complete add-on directory in the official store looks like this (taken from the real samba and ssh add-ons):

my_addon/
├── config.yaml # Required. Add-on metadata and Supervisor contract
├── build.yaml # Optional. Build-time configuration (base image, args)
├── Dockerfile # Required for locally built add-ons
├── rootfs/ # Files copied into the container image
│ └── etc/... # init scripts, service definitions, templates
├── DOCS.md # End-user documentation, rendered in the UI
├── CHANGELOG.md # Version history, rendered in the UI
├── README.md # Store/long description
├── icon.png # Square icon shown in the store
├── logo.png # Wide logo shown on the add-on page
├── apparmor.txt # Optional. Custom AppArmor profile
└── translations/
└── en.yaml # UI labels/descriptions for configuration options

config.yaml

The heart of every add-on. It declares identity (name, slug, version, description), supported architectures, requested privileges, port mappings, folder mounts, user-facing options and their validation schema. The Supervisor validates this file against a strict schema and silently drops unknown keys. YAML (.yaml/.yml) and JSON (.json) are both accepted. See the configuration reference for every key.

A minimal example:

name: Example add-on
version: "1.0.0"
slug: example
description: My first add-on
arch:
- aarch64
- amd64
startup: services
options:
message: Hello world
schema:
message: str

build.yaml

Optional build-time configuration, validated separately from config.yaml. It supports four keys:

KeyTypeMeaning
build_fromstring, or map of arch → imageBase image passed to the Dockerfile as BUILD_FROM
argsmap of string → stringExtra --build-arg values
labelsmap of string → stringExtra image labels
squashbool (default false)Ignored on modern builders (BuildKit); a warning is logged

The per-architecture form is what the official add-ons use:

---
build_from:
aarch64: ghcr.io/apexinfosysindia/base:3.23-2026.06.1
amd64: ghcr.io/apexinfosysindia/base:3.23-2026.06.1
args:
MY_TOOL_VERSION: 1.2.3

If build.yaml exists but sets no build_from, the Supervisor defaults it to ghcr.io/apexinfosysindia/base:latest. If there is no build file at all, your Dockerfile must specify its own complete FROM.

Dockerfile

The Supervisor builds local add-ons with docker buildx build, passing:

  • BUILD_FROM — the resolved base image (only when a build file exists)
  • BUILD_ARCH — the target CPU architecture (aarch64 or amd64)
  • BUILD_VERSION — the add-on version from config.yaml
  • every entry from build.yaml args

So the canonical opening is:

ARG BUILD_FROM
FROM $BUILD_FROM

RUN apk add --no-cache my-package

COPY rootfs /

You can also provide an architecture-specific Dockerfile.aarch64 or Dockerfile.amd64; when present, it is preferred over the plain Dockerfile for that architecture. The build is labeled automatically with io.apexos.version, io.apexos.arch, io.apexos.type, and — when set — io.apexos.name, io.apexos.description, and io.apexos.url.

rootfs/

By convention, everything under rootfs/ is copied into the image root with COPY rootfs /. This is where init scripts, service supervision trees (the official add-ons use s6-overlay directories such as etc/services.d/<name>/run and etc/cont-init.d/*.sh), and configuration templates live. For example, the ssh add-on ships rootfs/etc/services.d/sshd/run and rootfs/etc/cont-init.d/keygen.sh.

DOCS.md, CHANGELOG.md, README.md

  • DOCS.md is the user manual, shown in the add-on's Documentation tab.
  • CHANGELOG.md is shown to users when updates are available.
  • README.md is used as the add-on's long description in the store.

All three are read from fixed paths in the add-on directory — the filenames are not configurable. See presentation.

icon.png and logo.png

Fixed filenames read from the add-on directory: icon.png (square, shown in listings) and logo.png (wide, shown on the add-on page). See presentation for the sizes used by the official store.

translations/

Per-language YAML files (translations/en.yaml, etc.) providing human-readable names and descriptions for your configuration options and network ports. The Supervisor reads every file in this directory and keys the content by filename stem (the language code). See presentation.

The base images

Official add-ons build from the ApexOS base-image family published at ghcr.io/apexinfosysindia:

ImageFlavorExample tag in the official store
ghcr.io/apexinfosysindia/baseAlpine Linux3.23-2026.06.1 (ssh, samba-family, zigbee2mqtt, cec_scan, configurator, spotify)
ghcr.io/apexinfosysindia/base-debianDebiantrixie-2026.06.1 (mosquitto, whisper, piper, vscode, plex, aircast, ...)
ghcr.io/apexinfosysindia/addon-baseCommunity-style add-on base15.0.4 (samba, apex-cloud-link)
ghcr.io/apexinfosysindia/base-pythonPython variant
ghcr.io/apexinfosysindia/base-ubuntuUbuntu variant

These images ship the plumbing add-ons rely on — including the bashio helper library and the s6 init/service supervision used by the official add-ons. When the Supervisor builds an add-on without any base image declared, it falls back to ghcr.io/apexinfosysindia/base:latest.

An add-on may also build from any other image, but third-party bases used by the official store are mirrored into the ApexOS registry first so devices and CI never depend on external registries at build time (see publishing).

Prebuilt images vs. local builds

An add-on either:

  • builds locally on the device — no image: key in config.yaml; the Supervisor runs the Docker build using your Dockerfile, or
  • pulls a prebuilt image — the image: key names a published image, with {arch} expanded to the device architecture at install time:
image: registry.apexinfosys.in/apexos/{arch}-addon-samba

The image is pulled with the tag equal to the add-on version. All official store add-ons use prebuilt images; the publish pipeline that produces them is described in publishing. During development you will almost always use local builds.

Local development against a device

Local add-ons live in the Supervisor's local store repository on the device (repository slug local). The workflow:

  1. Get the add-on directory onto the device. The easiest path is the Samba share add-on, which exposes an addons share ("This is for your local apps"); the Terminal & SSH add-on works too, since it mounts the local add-ons folder at /addons. Copy your add-on directory (with config.yaml, Dockerfile, etc.) into it.
  2. Reload the store. In the UI open the add-on store and refresh, or call the Supervisor store reload API. Your add-on appears under the local repository.
  3. Install it. The Supervisor builds the image on the device using docker buildx build with the BUILD_FROM/BUILD_ARCH/BUILD_VERSION arguments described above.
  4. Iterate. After changing files, bump nothing — rebuild the add-on from its page and restart it. Use the log view to debug; log to stdout/stderr (or use bashio::log.*).

Things worth knowing while iterating:

  • Validation problems in config.yaml are logged by the Supervisor with the reason; unknown keys are silently removed rather than rejected.
  • Invalid map: entries are skipped with a warning, not fatal.
  • The container is connected to the internal apexnet Docker network (172.30.32.0/23) unless you request host_network.
  • Every add-on container gets SUPERVISOR_TOKEN and TZ in its environment; the token authenticates calls to the Supervisor API at http://supervisor (see bashio and security).
  • /data is always mounted read-write as the add-on's private, persistent storage. Everything else must be requested via map:.

Once the add-on works locally, see publishing to get it into the official store.