--- name: umbrel-package-app description: Use when packaging a new app for the Umbrel App Store, including upstream discovery, manifest and compose authoring, Umbrel runtime rules, persistence, and readiness for testing. --- # Umbrel Package App Create an Umbrel App Store package for a self-hosted app. Keep changes scoped to the requested app unless the task explicitly requires shared changes. ## App Store Gates - The package must open to a useful web UI, setup page, or status/connection page. If upstream is headless or CLI-only, add a browser page that shows service state, connection details, QR codes, or next steps. Users must not need SSH or CLI access for normal use. - The app must have maintained images that support `linux/amd64` and `linux/arm64`. - The package must be able to persist user data and required config under `${APP_DATA_DIR}`. - Host access should be exceptional, minimal, and product-essential. App Store packages must not mount the host Docker socket or otherwise control Umbrel's Docker daemon. Privileged mode, host networking, broad host mounts, device mounts, and extra capabilities need careful justification, especially when combined. Host Docker socket access is effectively host-root access: a container with the socket can control other containers and mount host paths. Other host privileges can also turn a packaging mistake or app compromise into a device-wide problem, especially because Umbrel runs on a user's personal server alongside their data and other apps. ## Upstream Discovery Before writing files, understand the upstream app well enough to package it deliberately: - Find the canonical project, website, repo, docs, license, support channel, and current stable release. - Read the upstream Docker or self-hosting docs, including any example compose files. - Identify the service topology: web service, internal listen port, sidecars, persistent paths, required config, and generated secrets. - Understand the first-run experience: setup flow, login, default credentials, external accounts, companion clients, hardware, or domain/HTTPS assumptions. - Surface packaging risks early, especially unclear licensing, stale images, fixed public URL assumptions, manual-only setup, or broad host access. ## Package Files Create one top-level directory named after the app ID: ```text / umbrel-app.yml docker-compose.yml exports.sh # only when needed hooks/ # only when needed data/.../.gitkeep # only for empty bind-mount source dirs ``` After creating the package shell, inspect similar apps in this repo for naming, app_proxy wiring, persistence paths, generated secrets, templates, hooks, widgets, and framework-specific env vars. Follow established Umbrel patterns where they fit. ## Manifest `umbrel-app.yml` is the app manifest. umbrelOS reads it into the app-store registry, checks `manifestVersion` compatibility before install, exposes its metadata in the UI, and uses `port` plus `path` to build the app launch URL. Use current app packages in this repo as the manifest contract. Some fields are required by umbrelOS at runtime, while others are expected for App Store metadata, review, or publishing. For new packages, use this field order: ```yaml manifestVersion: id: category: name: version: tagline: description: releaseNotes: developer: website: dependencies: repo: support: port: gallery: path: defaultUsername: defaultPassword: submitter: submission: ``` - `manifestVersion` is Umbrel app-framework compatibility, not the app's upstream version. Use `1` by default. For example, if a package relies on app-framework behavior introduced in umbrelOS 1.3, use `manifestVersion: 1.3` so older umbrelOS versions refuse the install. - `id` is the stable app package identifier. It must exactly match the top-level directory name, use lowercase kebab-case, and be recognizable, for example `home-assistant`. umbrelOS uses it for app data, dependency references, and generated container names, so do not change it after release. - If the app implements another Umbrel app's dependency contract, put `implements:` immediately after `id`. Use it only for real drop-in providers, for example a Bitcoin node package that can satisfy apps depending on `bitcoin`. See Dependencies for the required exports contract. - `name` is the user-facing app name shown in the App Store and on the Umbrel home screen. Use the upstream/product name users recognize. - `tagline`, `category`, and `description` are App Store copy. Use the current store taxonomy and make sure the wording makes sense for someone installing the app on Umbrel. - Put important first-run setup or security notes near the top of `description`. - Use folded YAML with `>-` for multi-line `description` and `releaseNotes`. Single line breaks are folded into spaces. To create a Markdown paragraph break, use two blank lines in the YAML source. For Markdown bullet lists, use a blank line before the list and between list items so YAML preserves the line breaks. ```yaml description: >- First paragraph. Second paragraph. - Added a faster setup flow - Fixed login after restart ``` - `version` is the upstream app/package version users recognize. Use the released upstream version in upstream's format. If upstream has no release version, use the short commit SHA for the exact upstream commit being packaged. Do not use `latest` or a Docker image digest. - `port` is the host-facing browser port umbrelOS uses for the app URL. For normal `app_proxy` web apps, it is the port assigned to `app_proxy`, not the app container's internal listen port, and must be unique across the App Store. - Put the app container's real listening port in `app_proxy.environment.APP_PORT`. Example: if the web container listens on `8080` and the app should open at `http://umbrel.local:3456`, use manifest `port: 3456` and `APP_PORT: 8080`. - `path` is appended to the app URL. Use `path: ""` for apps that launch at the root. For subpath apps, start with a leading slash and do not include the host or port, for example `path: "/admin"`. - Use `releaseNotes: ""` for new packages. - Include accurate `developer`, `website`, `repo`, and `support` values. In the App Store, `developer` links to `website`. - Use `dependencies:` only for other Umbrel apps the package requires at runtime. Values are app IDs, for example `bitcoin` or `electrs`. Do not list same-package services such as Postgres, Redis, workers, or optional integrations. - Use `permissions:` only for recognized platform or shared-storage access the app actually needs. See Permissions. - Use `gallery: []` for new packages. The Umbrel team adds gallery images before merge; official App Store image assets are hosted in a separate assets repo. - Omit `icon` for official App Store packages. Official icons are hosted with the other App Store image assets outside this package repo. Community app stores may use `icon` in the manifest. - `defaultUsername` and `defaultPassword` are displayed to the user in Umbrel when non-empty. They do not configure the app. - Use real credentials that work after install. Use `""` for values that do not exist, such as apps with first-run account creation or no login. - `deterministicPassword: true` makes Umbrel display the per-install `APP_PASSWORD` value as the app password. Set it only when the package actually configures the app login/admin password to `${APP_PASSWORD}`. - Use `widgets:` for optional cards on the Umbrel home screen that show live app status, progress, recent activity, or quick actions. Add them only when the app has a real server-side JSON endpoint; see Widgets. - Use `backupIgnore:` only for app data that should be excluded from Umbrel backups; see Backups. - Set `submitter` to the contributor name as it should appear in the App Store metadata. - Set `submission` to the pull request URL. ### Dependencies Manifest `dependencies:` are runtime dependencies on other Umbrel apps. - Umbrel requires the dependency app, or a selected app that `implements` it, to be installed before installing the dependent app. - The dependency value remains the app ID being depended on, even when alternatives exist. For example, an app that needs an Electrum server depends on `electrs`; Umbrel can let the user satisfy it with another installed app that implements `electrs`. - Use `implements:` only when an app can stand in for another Umbrel app's dependency contract. Any app can be implemented this way, but the implementing app must satisfy the same exported variables, endpoints, credentials, paths, protocols, and optional capabilities that dependent apps expect from the original app. - The implementing app should export the canonical variables for the app it implements, not only its own app-specific variables. For example, an app that implements `bitcoin` must export the `APP_BITCOIN_*` contract used by dependent apps, and an app that implements `electrs` must export the `APP_ELECTRS_*` contract used by dependent apps. - Existing examples in this repo include Bitcoin node alternatives that implement `bitcoin` by exporting node IP, data dir, RPC user/password/port, P2P port, network, Electrs-compatible network name, and any supported ZMQ, Tor, IPC, or hidden-service values. - Existing Electrum server alternatives implement `electrs` by exporting `APP_ELECTRS_NODE_IP` and `APP_ELECTRS_NODE_PORT`, and by providing an Electrum protocol endpoint compatible with apps that depend on `electrs`. - Test `implements:` with representative dependent apps before relying on it. Do not use it as a category, tag, or loose similarity marker. - During lifecycle commands, Umbrel sources `exports.sh` from selected direct and transitive dependencies before the app's own exports. - Apps selected as dependencies cannot be uninstalled while dependent apps are installed. - Do not use manifest dependencies for services inside the same `docker-compose.yml`; model those as compose services instead. ### Permissions Manifest `permissions:` declares platform or shared-storage access the app needs. Permission values used in this repo include: - `GPU` requests GPU device access. When the device has `/dev/dri`, umbrelOS adds `/dev/dri` to every service in the app, so use it only when the app has a real GPU acceleration or hardware transcoding path. - `STORAGE_DOWNLOADS` indicates the app needs access to Umbrel's shared Downloads storage. Use it only when the compose file mounts Downloads or one of its subdirectories. - Do not add empty or speculative permissions. The manifest should match what the package actually uses. ### Backups Users can enable backups in umbrelOS. App packages should assume persisted app data is backed up by default. - `backupIgnore:` is a package-level exclusion list for files inside `${APP_DATA_DIR}` that should not be restored from backup. - Use `backupIgnore:` when restoring the data is unnecessary or unsafe: regenerated/redownloadable data, high-churn output that bloats backups, or state that upstream says is dangerous to restore stale. - Examples include caches, logs, thumbnails, indexes, blockchain data, model downloads, temporary worker state, and protocol state such as Lightning channel databases when stale restore can be dangerous. - Entries are relative to `${APP_DATA_DIR}`. Use simple paths or `*` globs only, for example `data/cache/*` or `data/logs/*`. - Do not exclude primary user content, uploads, ordinary app databases, wallet seeds or keys, required config, encryption keys, or anything needed to restore the app to the user's expected state. - Do not use `backupIgnore:` to exclude an entire app from backups. Users control whole-app backup exclusion in umbrelOS. ### Widgets Widgets are glanceable cards on the Umbrel home screen for app status, progress, recent activity, or quick actions. Manifest `widgets:` registers optional widgets users can add to the Umbrel home screen. - Do not add placeholder widgets. Add a widget only when the package exposes useful live status or controls through JSON. - Each widget needs a local `id`, `type`, `refresh`, `endpoint`, `link`, and `example`. Umbrel prefixes the manifest ID at runtime as `:`, so keep the manifest `id` local to the app, for example `status` or `sync`. - Supported app widget types are `text-with-buttons`, `text-with-progress`, `two-stats-with-guage`, `three-stats`, `four-stats`, `list`, and `list-emoji`. - The `endpoint` must be `service:port/path` with no scheme. The host must exactly match a service key in `docker-compose.yml`. Umbrel builds `http://`, resolves the service to the container IP, and fetches it server-side. - The endpoint must return JSON matching the widget `type`, including a `refresh` duration such as `5s`, `30s`, or `1m`. The live response drives the rendered widget; the manifest `example` is sample data for the widget selector. - The endpoint is not fetched through `app_proxy` and does not receive browser cookies or an app login session. Use a small unauthenticated internal endpoint or widget sidecar when the app UI/API requires auth. - Keep `link` relative to the app path, or use `""` to launch the app root. - Widget-only services usually do not need raw host `ports:`. ## Compose `docker-compose.yml` defines the containers umbrelOS installs and runs for the app. umbrelOS patches the file before install to inject container names, rewrite compatibility storage mounts, and apply platform permissions such as GPU access. ### app_proxy Use `app_proxy` for normal browser-based apps. - Define an `app_proxy` service with environment only. - Treat manifest `port` and app_proxy `APP_PORT` as different values: manifest `port` is the host-facing app_proxy port; `APP_PORT` is the internal web service port. - Set `APP_HOST` to the Umbrel-injected container name: `__1`. - Set `APP_PORT` to the internal port the web service listens on. - Do not publish the web UI with raw `ports:` when app_proxy is sufficient. - Keep app_proxy auth enabled by default. Do not add `PROXY_AUTH_ADD: "true"` because that is already the framework default. - With app_proxy auth enabled, users already signed in to Umbrel can open the app without another Umbrel login prompt. Users who are not signed in must authenticate with Umbrel first. - Umbrel auth protects the route with the user's Umbrel login, including Umbrel 2FA when enabled. - Umbrel auth can also protect an app before the user has created the app's own account during first-run setup. - Set `PROXY_AUTH_ADD: "false"` only when the whole app must bypass Umbrel auth, such as an app with its own login that Umbrel auth would break or an app that is intentionally public. - For companion apps, mobile clients, webhooks, federation, or protocol endpoints that cannot send Umbrel auth cookies, keep Umbrel auth enabled and use `PROXY_AUTH_WHITELIST` only for the required paths. - Common whitelist examples are `/api/*`, `/webhook/*`, `/.well-known/*`, `/public/*`, `/assets/*`, or a narrow protocol route such as `/api/lnurl/*`. - Treat whitelisted paths as public. Keep them as narrow as possible and make sure the app's own auth, token, signature, or protocol rules protect anything sensitive. - Use `PROXY_AUTH_BLACKLIST` to protect sensitive paths inside a broader whitelist. ### Services - Model each long-running process as one compose service: web app, worker, database, cache, search, queue, etc. - Use sidecars when upstream expects separate databases, caches, workers, search, or queues; do not collapse them into one container just to reduce service count. - Let Umbrel inject `container_name`; set it only when the app cannot work with Umbrel's injected name, and leave a short comment explaining why. - Use `depends_on` and healthchecks when a service must wait for a database, cache, or init job to be ready. - Use `restart: on-failure` by default for long-running services. One-shot init, migration, or bootstrap services may omit `restart:` when automatic restart would be wrong, or use `restart: on-failure` when retrying is safe. Use another restart policy only when upstream needs it and the reason is clear. - Use `init: true` when the process needs a real init process for signal handling or child-process cleanup. - Umbrel installs committed package directories under `${APP_DATA_DIR}` owned by UID/GID `1000:1000`. Running services as `user: "1000:1000"` keeps runtime-created files writable across restarts and updates when the image supports arbitrary UIDs. - Do not force `user: "1000:1000"` on images that need their bundled user, root entrypoint, or permission-fixing startup. If upstream exposes `PUID`/`PGID`, `UID`/`GID`, or similar settings, use that supported path and verify the app can write to its mounted data after first start and restart. - Treat the shared Docker network as untrusted. Do not rely on "no host-published port" as the only protection for databases, caches, admin APIs, or framework secrets. Generate stable per-install secrets; see `APP_SEED`, `APP_PASSWORD`, and `derive_entropy` under Umbrel Environment Variables. - Add public URL, trusted proxy, CSRF/CORS, or root path settings only when the app otherwise redirects to the wrong host, rejects proxied requests, or serves broken asset/API paths behind `app_proxy`. - When a canonical browser URL is required, point it at the Umbrel launch origin, usually `http://${DEVICE_DOMAIN_NAME}:${APP_PROXY_PORT}`. Keep trusted origins narrow; do not disable CSRF/CORS or use `*` unless the upstream protocol requires it. ### Persistence Containers are recreated on restart and update. Anything the user expects to keep must be bind-mounted from app data. - Use bind mounts under `${APP_DATA_DIR}/data/...` for app-owned mutable state: databases, uploads, user content, config, generated keys/secrets, plugins, and indexes that should survive restart. - Do not leave upstream Docker named volumes for durable state. Convert them to `${APP_DATA_DIR}/data/...` bind mounts. - Do not rely on files written only inside the container filesystem. If losing a path would reset accounts, config, uploads, wallets, databases, or app identity, bind-mount it. - Keep runtime-created state under `data/`. Use the app-data root for package/lifecycle files and top-level rendered templates, not user data or databases. - Use `${UMBREL_ROOT}/data/storage/downloads...` only for intentional shared Downloads access, and include `STORAGE_DOWNLOADS` in `permissions:`. - Keep that compatibility Downloads mount path in app packages. Current umbrelOS rewrites it to `${UMBREL_ROOT}/home/Downloads...` when patching compose, while older umbrelOS versions expect the old path. - Commit every host-side `${APP_DATA_DIR}/data/...` bind-mount source directory the app needs on first start. If the directory would otherwise be empty, keep it in git with `data/.../.gitkeep`; umbrelOS removes `.gitkeep` before runtime, so the container sees an empty directory. - Be careful with file bind mounts such as `${APP_DATA_DIR}/data/config.yml:/app/config.yml:ro`. The host source file must exist before `docker compose up`; otherwise Docker may create a directory at that path and break the app. Commit the file, render it from a top-level template, or create it in a hook before start. ### Networking And Ports Umbrel injects the external `umbrel_main_network` as the compose `default` network at runtime. Do not add a top-level `networks:` block for ordinary packages; use service-level `networks: default:` only when a tested static IP or alias is needed. - Use Docker DNS names for same-app traffic, not container IPs. For `app_proxy`, set `APP_HOST` to the Umbrel-injected container name `__1`; for other sidecar URLs, use the service name or injected container name used by nearby apps. - Do not publish the web UI with raw `ports:` when `app_proxy` can front it. - Publish raw `ports:` only for non-HTTP protocols, companion-client endpoints, server-to-server protocol ports, or integrations that must connect without `app_proxy`. - Use explicit host mappings for raw ports, including protocol when needed, for example `"9735:9735"` or `"8448:8448/tcp"`. Do not use short syntax that lets Docker choose a random host port. - Manifest `port` and raw host-published compose ports share the host port space. Keep them from colliding with other app ports or umbrelOS public ports such as `80`, `443`, and `2000`. - The linter catches literal ports and simple static same-app `exports.sh` port values. If it flags an unresolved host port, verify the port manually and call it out in the PR when relevant. - Internal container ports and app_proxy `APP_PORT` values do not need to be unique. - Use `network_mode: host` only when required for LAN discovery, multicast/broadcast, low-level networking, or an upstream image that cannot work behind bridge networking. Host-network apps cannot use normal `app_proxy` routing; manifest `port` must match a host listener. - Do not mount the host Docker socket, for example `/var/run/docker.sock`, or proxy access to Umbrel's Docker daemon. - Do not use `privileged: true`, broad host mounts, device mounts, or extra capabilities to work around ordinary app configuration. If host access is genuinely required, keep it as narrow as the app allows. ### Images - Every runtime image must be a prebuilt, maintained image that supports both `linux/amd64` for x86_64 PCs/servers and `linux/arm64` for 64-bit ARM devices such as Raspberry Pi 4/5. Do not use compose `build:` in App Store packages. - Images must be publicly pullable without registry credentials, private registry access, GitHub package permissions, or local build context. - Pin every image as `registry/repo:version-or-commit@sha256:`. Keep the human-readable tag and digest together; the tag should identify the upstream release, commit, or wrapper build. - Use the multi-arch manifest-list/index digest for the tag, not an architecture-specific image digest. Verify with `docker buildx imagetools inspect :` and confirm both `linux/amd64` and `linux/arm64` are present. - Do not use `latest`, moving branch tags, unversioned distro tags when a versioned tag exists, or digest-only references without a tag. If upstream only publishes `latest`, first look for a commit/date tag or a better image source; if none exists, use `latest@sha256:` only as a last resort and document why a stable tag is unavailable. - Pin every image used by every service, including databases, caches, workers, migration/init jobs, widget helpers, and one-shot utilities. - Prefer official upstream images when they are maintained and multi-arch. Use a wrapper image only when packaging requires Umbrel-specific glue that cannot live in compose, templates, or hooks, or when upstream does not publish an acceptable multi-arch image. ## Umbrel Environment Variables Umbrel sources app/dependency env, renders top-level templates, and runs compose through the app lifecycle. Use Umbrel-provided env instead of hardcoded install paths, device hostnames, or generated secret files. Values available after app env is sourced: - `APP_ID`: app ID from the manifest and directory name. - `APP_VERSION`: manifest `version`. - `APP_MANIFEST_FILE`: installed `umbrel-app.yml` path for the app. - `APP_DATA_DIR`: installed app data root, `${UMBREL_ROOT}/app-data/`. - `APP_DOMAIN`: local `.local` domain for the Umbrel device; use with a port when upstream needs a browser-facing app URL. - `APP_HIDDEN_SERVICE`: app Tor hidden-service hostname when Umbrel remote Tor access is enabled. It may be a placeholder such as `not-enabled.onion` or `notyetset.onion` before a hidden service exists. - `APP_PROXY_HOSTNAME`: internal hostname of the generated `app_proxy` service. - `APP_PROXY_PORT`: manifest `port`, the host-facing app URL port. - `APP_SEED`: stable per-install derived value for the app; see Generated Secrets. - `APP_PASSWORD`: stable per-install derived value for local app credentials when the package wires the app login/admin password to this value; see Generated Secrets. - `DEVICE_HOSTNAME`: device hostname without `.local`. - `DEVICE_DOMAIN_NAME`: device `.local` domain, usually used for browser-facing URLs such as `http://${DEVICE_DOMAIN_NAME}:${APP_PROXY_PORT}`. - `UMBREL_ROOT`: Umbrel data root on the host. - `NETWORK_IP`: Umbrel Docker network base IP; use `${NETWORK_IP}/16` when upstream needs the Umbrel app subnet, for example trusted proxy or RPC allowlist config. - `TOR_PROXY_IP`: Umbrel Tor SOCKS proxy IP on the Docker network. - `TOR_PROXY_PORT`: Umbrel Tor SOCKS proxy port. - `TOR_DATA_DIR`: Umbrel Tor data directory on the host. Generated secrets: - `derive_entropy