How apps work
What the Supervisor does with your app, from the repository to a running container, and the names and folders you run into along the way.
An app is a container that the Supervisor manages for the user. Knowing what the Supervisor does at each step makes most of the configuration options obvious. This page walks through it, from repository to running app.
- Home Assistant OS runs everything as containers on top of a minimal operating system.
- The Supervisor is the container that manages all the others: Home Assistant Core, a few system containers (called plugins), and every app. It has a REST API that Core, the UI, and apps talk to. What the Supervisor runs lists every container, with its address.
- An app is a folder with a
config.yamland either aDockerfileor a reference to a published image. - A repository is a Git repository with one or more app folders and a
repository.yaml. - The app store shows the apps of every repository the user added, under Settings > Apps > Install app.
The Supervisor keeps a local copy of every repository it knows:
- Local apps: the
local_appsfolder on the device. No Git involved, which makes it the place for development. - Official apps: home-assistant/addons.
- A few built-in community repositories, like the community apps, ESPHome, and Music Assistant.
- Every repository the user added by URL.
When the store reloads, the Supervisor searches each repository for files named config.yaml, config.yml, or config.json, at any depth, skipping hidden folders and folders called rootfs. Every file it finds is one app. It validates the file against the configuration schema; an app with an invalid configuration is skipped, with a warning in the Supervisor logs.
The Supervisor identifies an installed app by its repository and its slug, joined with an underscore: {repository}_{slug}.
- The slug comes from
config.yaml. It may contain letters, digits,-,_, and., and must be unique within its repository. - The repository ID is
localfor local apps andcorefor the official apps. For a repository added by URL, it is the first 8 characters of the SHA-1 hash of the lowercased URL, branch included, likea0d7b954for the community apps.
So hello_world in your local folder becomes local_hello_world. You see this full ID in the API, in the CLI (ha apps info local_hello_world), and in my links. It is also the base of the hostname on the internal network: replace _ with -, and local_hello_world is reachable as local-hello-world.
When the user installs an app, the Supervisor gets its image in one of two ways:
- Pull: if
config.yamlhas animagekey, the Supervisor pulls that image withversionas its tag. This is how published apps should work: fast and reliable. See Publish your app. - Build: without
image, the Supervisor builds the image on the device from yourDockerfile, using Docker BuildKit. This is what happens with local apps during development.
Before it installs anything, the Supervisor checks that the app fits the system: the CPU architecture must be in arch, the machine type must match machine if you set it, and Home Assistant must be at least the version in homeassistant.
When an app starts, the Supervisor:
- Validates the user's options against your
schemaand writes them to/data/options.json. - Creates the container with everything
config.yamlasks for: ports, mapped folders, devices, capabilities, the AppArmor profile, and the environment variables, including aSUPERVISOR_TOKENthat is unique to this app. - Adds the container to the internal network and registers its hostname in the internal DNS.
- Starts the container and waits for it to run.
At boot, apps start in stages, based on their startup value:
startup |
Starts |
|---|---|
initialize |
First, when the Supervisor starts |
system |
Before the services, for things like databases |
services |
Before Home Assistant Core |
application |
After Home Assistant Core. This is the default |
once |
Never at boot. For apps that do a job and exit |
Apps only start at boot if the user left Start on boot on. With boot: manual, it is off by default; with boot: manual_only, the user cannot turn it on at all.
- Everything your app writes to
stdoutandstderrends up in the Log tab. - If the user turns on the Watchdog, the Supervisor restarts your app when the container fails. If you set a
watchdogURL, it also checks that URL and restarts the app when it stops responding. /datais the private, persistent storage of your app. It survives restarts and updates, and it is part of every backup of your app. Everything outside/dataand the mapped folders is lost when the container is recreated.
A backup of your app contains its configuration, its /data folder, and, if you map app_config, its public configuration folder. By default, backups are hot: your app keeps running. If your app needs a consistent state on disk, you have two options:
- Set
backup_preandbackup_postto commands that run inside your container before and after the backup, like flushing a database to disk. - Set
backup: cold, and the Supervisor stops your app during the backup.
Use backup_exclude to leave out files that do not need to be restored, like caches. See the app configuration for the details.
When you raise version in config.yaml, users see an update. Updating pulls (or builds) the new image and recreates the container; /data stays. Users can turn on automatic updates per app. If a release needs their attention, list it in breaking_versions, and the Supervisor never updates across it automatically.
- The guides cover one task each, from the container image to publishing your app.
- Talk to Home Assistant and the Supervisor explains the internal network and the APIs.
- Runtime environment lists everything your container gets from the Supervisor.
- App configuration has every key of
config.yaml.