Cheat Sheets

CasaOS and ZimaOS CLI Cheat Sheet: casaos-cli Commands

Every CasaOS and ZimaOS box ships a command line tool called casaos-cli, and almost nobody documents it. The official README covers about half the commands. Several of the ones it does cover fail on current releases, and the failures are different on CasaOS and ZimaOS because ZimaOS runs a forked backend behind the same client.

Original content from computingforgeeks.com - post 172233

This CasaOS CLI and ZimaOS CLI cheat sheet comes from running the casaos-cli subcommands on real systems and recording what actually happened: app install, apply, update and uninstall, app stores, global variables, message bus subscriptions and the health checks. It also covers the ZimaOS-only commands (zimaos, rauc) and the service names that change between the two. The testing ran in September 2026 on CasaOS v0.4.15 (most of it on Debian 13, with install and uninstall repeated on Debian 12) and on ZimaOS v1.7.1 on a ZimaBoard 2. The matrix below marks anything we did not run.

Which casaos-cli commands work on CasaOS and ZimaOS

Both platforms report casaos-cli 0.4.4, but they are different builds (November 2023 on CasaOS, April 2025 on ZimaOS) talking to different backends. The matrix is what we measured, not what the help text promises:

CommandCasaOS on DebianZimaOS
app-management list appsWorksWorks
app-management list app-storesWorksWorks
app-management searchWorks (633 apps, see the store note below)Fails: 502, backend panics
app-management install / start / stop / restart / logsWorksWorks
app-management show localWorksWorks
app-management applyWorksNot tested
app-management update appWorksWorks
app-management uninstallWorksWorks
app-management set global / show globalWorks (no key argument)show global works
app-management register app-storeWorksNot tested
message-bus list event-typesWorksReturns nothing
message-bus subscribe socketio / websocketWorks (one event name at a time)Not tested
message-bus trigger, subscribe websocket actionsNot tested (no action types registered)Not tested
convert appfile, local-storage set mergeNot testedNot tested
local-storage list mergesFails: 503Fails: 404
user list eventsWorks (empty)Fails: 404
healthcheck services / ports-in-use / logsFails: 502Fails: 404
qrcode, version, completionWorksWorks

The short version: app lifecycle commands are reliable on both, the three healthcheck commands are dead on both, and search is broken on ZimaOS. Here is the same client on each box:

casaos-cli app-management list apps output on CasaOS for Debian 13 with Jellyfin and Uptime Kuma running and the healthcheck 502 error

And on ZimaOS, where search and the health check fail with different codes:

casaos-cli version on ZimaOS v1.7.1 with the search 502 and healthcheck 404 errors on a ZimaBoard 2

casaos-cli quick reference

The commands worth knowing, in one table (the rarely useful and broken ones are covered in the matrix above). Most have aliases (ls for list, up for install, down for uninstall), which are listed where they save typing:

TaskCommand
List installed appscasaos-cli app-management list apps
List app storescasaos-cli app-management list app-stores
Browse the storecasaos-cli app-management search [-c Media] [-t official|by_casaos|community] [-r]
Show an app’s configcasaos-cli app-management show local <appid> [--yaml]
Validate a compose filecasaos-cli app-management install -d -f docker-compose.yml
Install from a compose filecasaos-cli app-management install -f docker-compose.yml (alias up)
Start, stop, restartcasaos-cli app-management start|stop|restart <appid>
Last N log linescasaos-cli app-management logs <appid> -l 50
Change an installed appcasaos-cli app-management apply <appid> -f new.yml [-d]
Update an appcasaos-cli app-management update app <appid> [-f]
Uninstall, keep AppDatacasaos-cli app-management uninstall <appid> -n (alias down)
Set a variable for every appcasaos-cli app-management set global <KEY> <value>
List global variablescasaos-cli app-management show global
Add or remove a storecasaos-cli app-management register|unregister app-store <url|id>
List bus event typescasaos-cli message-bus list event-types
Stream every bus eventcasaos-cli message-bus subscribe socketio
Stream one sourcecasaos-cli message-bus subscribe websocket -s <source> events [-n <event>]
QR code for the web UIcasaos-cli qrcode
Talk to another API addresscasaos-cli -u localhost:18080 ... (host:port, no http://)
Shell completioncasaos-cli completion bash

Get a shell on CasaOS or ZimaOS

On CasaOS you already have one: it is a normal Debian or Ubuntu server (often a small box from our homelab mini PC shortlist), and our CasaOS on Debian guide covers the install. ZimaOS keeps SSH off until you enable it under Settings, General, Developer mode, then SSH Access. The login is your ZimaOS account, not root. On our ZimaBoard 2 that account sits in the wheel group, so sudo works with its password, and the ZimaBoard 2 setup guide shows the toggle.

casaos-cli itself needs no sudo on either platform. Without -u it reads the gateway port from /etc/casaos/gateway.ini (80 on all our boxes) and calls the local API, which accepts any request from loopback. The docker CLI is different: as a normal ZimaOS user, docker ps fails with permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock, so prefix Docker commands with sudo.

Check versions and services

Each platform has its own version command. On CasaOS:

casaos -v
casaos-cli version

The CasaOS release and the CLI build it bundles:

v0.4.15
0.4.4
(build time: 2023-11-20T09:52:33Z, commit: 51949f2cdc71eaec41db443fb93d8178ca6e5192)

ZimaOS replaces casaos with a zimaos binary. zimaos -h lists four flags: -v for the version, -license-info for the licence status, -c for a config address, and -u, described as “update system (optionally specify version, e.g., -u or -u 1.2.3)”. We did not run -u. We updated our board through Settings, Update instead, which is the documented path and shows the release notes first.

zimaos -v

ZimaOS adds the build commit and date:

v1.7.1
git commit: a6e52af55e44e97f9e1284896e85efbcc1dc1cc6
build date: 2026-08-05T09:00:34Z

The service names are where scripts written for one platform break on the other. ZimaOS renamed most units but kept the CasaOS config paths:

RoleCasaOS unitZimaOS unit
Web UI and API gatewaycasaos-gatewayzimaos-gateway
Main servicecasaoszimaos
Apps and storescasaos-app-managementzimaos-app-management
Users and logincasaos-user-servicezimaos-user
Disks and poolscasaos-local-storagezimaos-local-storage
Event buscasaos-message-buszimaos-message-bus
Cloud mountsrclonerclone
Extra on ZimaOSn/azimaos-virt-management, icewhale-files, casaos-installer
Config directory/etc/casaos/etc/casaos
State and databases/var/lib/casaos/var/lib/casaos

Listing both name patterns in one command works on either system:

systemctl list-units 'casaos*' 'zimaos*' rclone.service --no-pager --no-legend

On ZimaOS, rauc status shows which of the two system slots booted. That matters before an update, because a bad update rolls back to the other slot. Our board was running from slot B:

rauc status

The first block names the booted slot, and the slot list shows both kernels marked good. The zimaos-zimacube compatible string is not a typo. It is named after IceWhale’s ZimaCube NAS line, which still ships ZimaOS on the current ZimaCube 2, and our ZimaBoard 2 reports it as well.

=== System Info ===
Compatible:  zimaos-zimacube
Variant:
Booted from: kernel.1 (B)

=== Bootloader ===
Activated: kernel.1 (B)

=== Slot States ===
  [boot.0] (/dev/disk/by-partlabel/casaos-boot, vfat, inactive)

○ [kernel.0] (/dev/disk/by-partlabel/casaos-kernel0, raw, inactive)
      bootname: A
      boot status: good
    [rootfs.0] (/dev/disk/by-partlabel/casaos-system0, raw, inactive)

⏺ [kernel.1] (/dev/disk/by-partlabel/casaos-kernel1, raw, booted)
      bootname: B
      boot status: good
    [rootfs.1] (/dev/disk/by-partlabel/casaos-system1, raw, active)

List, search and inspect apps

list apps is the command you will run most. It shows each app ID, its state and the web UI address:

casaos-cli app-management list apps

The app ID in the first column is what every other command takes:

APPID        STATUS    WEB UI                       IMAGES                                DESCRIPTION
-----        ------    ------                       ------                                -----------
jellyfin     running   http://192.168.1.50:8097/    linuxserver/jellyfin:10.11.10         No description available
uptimekuma   running   http://192.168.1.50:3001/    louislam/uptime-kuma:1.23.10-alpine   It is a self-hosted monitoring tool like "Uptime Robot".

search does not take a search term. It dumps the whole catalogue (633 apps across both stores on our CasaOS box), so filter it with grep, or narrow it with -c for a category, -t for the author type, or -r for the store’s recommended list:

casaos-cli app-management search | grep -i -E '^(nextcloud|immich) '

Both apps are in the store, from different maintainers:

immich                            Media          Cp0204                  alextran1502
nextcloud                         Productivity   ZimaOS Team             Nextcloud

Category names must match the store exactly. The ones in use were Home, Others, Media, Productivity, Developer, Networking, AI, Social and Finance. An unknown name such as -c Utilities returns Error: no compose app found from this store rather than an empty list.

To see how an installed app is wired, show local prints its ports, volumes and containers. Add --yaml for the full Compose file, which is also the starting point for apply:

casaos-cli app-management show local uptimekuma --yaml

The YAML is the stored Compose project, with memory already converted to bytes and the store’s icon label added:

name: uptimekuma
services:
    uptimekuma:
        container_name: uptimekuma
        deploy:
            resources:
                reservations:
                    memory: "134217728"
        image: louislam/uptime-kuma:1.23.10-alpine
        labels:
            icon: https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/UptimeKuma/icon.png
        network_mode: bridge

Install an app from the command line

install takes a Compose file, not an app name. Store apps are Compose files already on disk under /var/lib/casaos/appstore, one folder per store. Find the one you want:

sudo find /var/lib/casaos/appstore -ipath '*/Apps/Syncthing/docker-compose.yml'

Three copies came back on our Debian 13 box, and they are not the same:

/var/lib/casaos/appstore/default/Apps/Syncthing/docker-compose.yml
/var/lib/casaos/appstore/casaos.app/4e6bf64b187e30664ab8e6ed48331e96/build/sysroot/var/lib/casaos/appstore/default.new/Apps/Syncthing/docker-compose.yml
/var/lib/casaos/appstore/github.com/2a1238d53212bfce4e8f861dcb8ef3fe/big-bear-casaos-master/Apps/syncthing/docker-compose.yml

Use the copy under a default.new folder (the live official store, which sits under casaos.app/ on CasaOS and cdn.jsdelivr.net/ on ZimaOS) or the BigBear one, not default/. The default folder is a snapshot the installer unpacked, and it goes stale. It still pinned Uptime Kuma 1.23.10 while the live store had already moved to 2.4.0, and running the update script did not refresh it. Validate the file with a dry run, then install:

APP_FILE=$(sudo find /var/lib/casaos/appstore -path '*/default.new/Apps/Syncthing/docker-compose.yml' | head -1)
casaos-cli app-management install -d -f "${APP_FILE}"
casaos-cli app-management install -f "${APP_FILE}"

The same find pattern returned the official store copy on both CasaOS and ZimaOS. The dry run validates and stops, and the real install returns in under a second because the work happens in the background:

2026/09/28 22:46:28 only validation has been done because `dry_run` is specified - skipping compose app installation
2026/09/28 22:46:28 compose app is being installed asynchronously

Your own Compose files work the same way. On ZimaOS we installed a traefik/whoami file with a minimal x-casaos block (main, title, port_map), and it appeared in list apps and on the dashboard like a store app. If a published port is already taken, the install is rejected up front with 400 Bad Request - there are ports in use.

Wait for asynchronous operations

Every lifecycle command returns “asynchronously” and exits before the work is done: install, apply, start, stop, restart, update and uninstall. Chain them without waiting and they race. We ran update app -f, then apply, then uninstall on the same app back to back. The uninstall finished first and CasaOS forgot the app, and then the still-running apply recreated the container. That left a live container on port 3001 that list apps no longer showed and the UI could not manage. Only sudo docker rm -f removed it.

In scripts, poll list apps until the state you asked for shows up before sending the next command, with a cap so a failed app cannot hang the script. The trailing space in the pattern stops uptimekuma from matching an app called uptimekuma2:

APP=uptimekuma
casaos-cli app-management stop "${APP}"
timeout 120 sh -c "until casaos-cli app-management list apps | grep -qE '^${APP} +exited'; do sleep 2; done"
casaos-cli app-management start "${APP}"
timeout 120 sh -c "until casaos-cli app-management list apps | grep -qE '^${APP} +running'; do sleep 2; done"

For an uninstall, wait until the app disappears from the list: until ! casaos-cli app-management list apps | grep -q "^${APP} "; do sleep 3; done.

Start, stop, restart and read logs

Unlike install, these take the app ID. logs prints the last N lines from every container in the app and does not follow, whatever its help text says:

casaos-cli app-management logs uptimekuma -l 5

The output is Compose-style, prefixed with the container name:

(showing last 5 lines)
...
uptimekuma  | 2026-09-28T22:13:09Z [SERVER] INFO: No user, need setup
uptimekuma  | 2026-09-28T22:13:09Z [SERVER] INFO: Adding route
uptimekuma  | 2026-09-28T22:13:09Z [SERVER] INFO: Adding socket handler
uptimekuma  | 2026-09-28T22:13:09Z [SERVER] INFO: Init the server
uptimekuma  | 2026-09-28T22:13:09Z [SERVER] INFO: Listening on 3001

For a live tail, use sudo docker logs -f <container>. The container name is in the show local output and usually matches the app ID.

Change an installed app with apply

apply replaces an app’s Compose definition and recreates it. It is the CLI equivalent of editing an app’s settings in the UI. Export the current file, edit it, dry-run it, apply it. This moves Uptime Kuma’s host port from 3001 to 3002:

casaos-cli app-management show local uptimekuma --yaml > /tmp/uk.yml
sed -i 's/published: "3001"/published: "3002"/' /tmp/uk.yml
casaos-cli app-management apply uptimekuma -d -f /tmp/uk.yml
casaos-cli app-management apply uptimekuma -f /tmp/uk.yml

Docker confirms the new mapping once the recreate finishes:

uptimekuma 0.0.0.0:3002->3001/tcp, [::]:3002->3001/tcp Up 1 second (health: starting)

The WEB UI column in list apps still said 3001 afterwards. The exported file still carried port_map: "3001" in its x-casaos block, and that key is what the dashboard link uses, so change both when you move a port. If the export step fails (for example because the app ID is wrong, which returns 404 Not Found - compose app `nosuchapp` not found), the shell redirect still leaves an empty file behind, and applying it fails with 400 Bad Request - request body has an error: value is required but missing. Check that the YAML file is not empty before you apply it.

Update and uninstall apps

update app compares the installed app with its store entry and does nothing if they match:

casaos-cli app-management update app uptimekuma

Our app had been installed from the stale snapshot, and the plain update still called it current:

2026/09/28 22:13:39 compose app `uptimekuma` is up to date

Adding -f skips the check and pulls whatever the live store pins. For Uptime Kuma that was a jump from 1.23.10 to 2.4.0, a major release with a database migration. Back up /DATA/AppData/<app> before a forced update, because there is no rollback command.

uninstall removes the containers and the app’s folder in /var/lib/casaos/apps. Without flags it also deletes /DATA/AppData/<app>, which is where the app’s data lives. We confirmed both: Syncthing’s AppData folder was gone after a plain uninstall, and Uptime Kuma’s survived -n. Use -n unless you really want the data gone. If an app has outgrown the store template, the kept AppData also makes it easy to move to a standalone setup such as our Uptime Kuma on Debian guide:

casaos-cli app-management uninstall uptimekuma -n

Set global environment variables for all apps

A global variable is injected into every container CasaOS starts, unless the app’s own Compose file already defines a variable with the same name, in which case the app’s value wins. It is the clean way to set a time zone or an API key once instead of per app:

casaos-cli app-management set global TZ Africa/Nairobi
casaos-cli app-management show global

The list includes a placeholder OpenAI key that every install ships with:

Global Key       Global Value
--------------   ------------
OPENAI_API_KEY   sk-xxxxxx
TZ               Africa/Nairobi   

Three details from testing. The values live in plain text in /etc/casaos/env. A running container only picks up a new value when it is recreated (after an update or apply, our Uptime Kuma container showed TZ=Africa/Nairobi in docker inspect). And show global <Key>, as the help text documents it, fails with Error: accepts 0 arg(s), received 1. Run show global with no argument and grep the output.

Manage app stores

A fresh CasaOS install registers two stores, and ZimaOS points its official store at a jsDelivr URL instead of casaos.app. List them to get the IDs:

casaos-cli app-management list app-stores

On Debian the official store is ID 0 and BigBear is ID 1:

ID   URL                                                                                 STORE ROOT
--   ---                                                                                 ----------
0    https://casaos.app/store/main.zip                                                   /var/lib/casaos/appstore/casaos.app/4e6bf64b187e30664ab8e6ed48331e96/build/sysroot/var/lib/casaos/appstore/default.new
1    https://github.com/bigbeartechworld/big-bear-casaos/archive/refs/heads/master.zip   /var/lib/casaos/appstore/github.com/2a1238d53212bfce4e8f861dcb8ef3fe/big-bear-casaos-master

Add a store by the URL of its zip with register app-store <url> and drop one by ID with unregister app-store <id>. Registering a URL that is already there returns 409 Conflict - appstore source already exists. Re-registering is also the fix for a broken store: after the CasaOS update script ran, our Debian box listed only 177 apps because app-management was looking for BigBear under a folder that did not exist (lstat /var/lib/casaos/appstore/github.com/1dfcdd9e390897b516b1d990c9197b56: no such file or directory in its log). unregister app-store 1 followed by register app-store with the same URL brought the count back to 633. If you only want apps from the official store, unregistering BigBear also removes the duplicate entries from UI search.

Watch events on the message bus

Every install, uninstall, disk hot-plug and dashboard refresh goes over an internal message bus, and the CLI can listen in. That is useful for scripting (react when a USB disk appears) and for debugging an install that hangs. See what exists first:

casaos-cli message-bus list event-types

On CasaOS we counted 44 app-management events, 65 from the web UI, 3 from casaos (including casaos:system:utilization) and 2 from local-storage (local-storage:disk:added and local-storage:disk:removed), plus one from CasaOS-UserService. The action-type list was empty, so message-bus trigger has nothing to trigger on a stock install. On ZimaOS the event-type list came back empty too.

subscribe socketio streams everything. For one source, the websocket form needs -s before the subcommand:

casaos-cli message-bus subscribe websocket -s casaos events -n casaos:system:utilization

A utilization event arrives every five seconds with CPU, memory, disk and network counters as JSON strings:

2026/09/28 22:54:02 subscribed to ws://localhost:80/v2/message_bus/event/casaos?names=casaos:system:utilization via websocket
{
  "name": "casaos:system:utilization",
  "properties": {
    "sys_cpu": "{\"model\":\"intel\",\"num\":2,\"percent\":36.3,\"power\":{\"timestamp\":\"1790636044\",\"value\":\"0\"},\"temperature\":0}",
    "sys_disk": "{\"avail\":18772983808,\"health\":true,\"size\":26206609408,\"used\":6317686784}",
    "sys_mem": "{\"available\":2401153024,\"free\":306978816,\"total\":4112314368,\"used\":1012670464,\"usedPercent\":24.6}",

The help says -n takes names separated by commas. It does not: -n app:install-begin,app:install-end fails with websocket.Dial ... bad status, while each name on its own subscribes fine. Leave -n off to get every event from that source, or run one subscriber per event name.

Run casaos-cli against a remote box

The global -u flag points the client at another API address. It takes a bare host:port. With a scheme it builds http://http//localhost:80/... and fails. Aimed straight at another server’s LAN IP it fails, because the API only skips authentication for loopback, and the CLI has no token option:

Error: 401 Unauthorized - Unauthorized

An SSH tunnel turns your request into a loopback request on the far side, and then it works. We ran this from the ZimaBoard against the Debian 13 CasaOS box:

ssh -f -N -L 18080:localhost:80 [email protected]
casaos-cli -u localhost:18080 app-management list apps

The remote app list came back. One oddity: the WEB UI column showed the local machine’s IP, not the server’s, because the client builds those links from its own address. The same loopback trust means anyone with SSH access to a CasaOS or ZimaOS box has full app control through this API, and so does any container you run with network_mode: host. The security section of our CasaOS install guide has the full test.

CasaOS-only and ZimaOS-only maintenance commands

These are not part of casaos-cli, but they are the other commands people look for:

TaskCasaOS on Debian/UbuntuZimaOS
Update the platformcurl -fsSL https://get.casaos.io/update | sudo bashSettings, Update (zimaos -u exists, not tested by us)
Uninstallsudo casaos-uninstall (interactive)Not available, it is the OS
Forgotten web UI passwordMove /var/lib/casaos/db/user.db aside, restart casaos-user-serviceIceWhale docs: delete /var/lib/casaos/db/user.db and reboot (not tested by us; the board also has user.db-wal and user.db-shm, so move all three aside rather than deleting)
Show the booted system slotn/arauc status
Where user data lives/DATA/DATA (its own partition)
Change the IP addressDebian networkingSettings, Network, or nmcli

The Debian side of that table, with real output for the update, reset and uninstall, is in the CasaOS on Debian 13 / 12 guide. For the ZimaOS network commands, the ZimaOS static IP guide covers both the dashboard and nmcli. IceWhale also publishes a repair script, zimaos-fix.sh, in its ZimaOS GitHub repository. It replaces zero-byte files under /mnt/overlay/etc with the originals from the read-only root, and runs resize2fs on the data partition only when that filesystem is under 2 GB. It does not reboot for you. We did not need or run it, so read it before you do.

Enable shell completion

The subcommand tree is deep enough that tab completion pays for itself. On Debian with bash-completion installed:

casaos-cli completion bash | sudo tee /etc/bash_completion.d/casaos-cli > /dev/null

Open a new shell and casaos-cli app-management <Tab> lists the subcommands. completion zsh, fish and powershell exist too. On ZimaOS, /usr is a read-only overlay (writing there returns Read-only file system), so load the script from your own ~/.bashrc with source <(casaos-cli completion bash). We confirmed that registers the completion for the ZimaOS login shell.

casaos-cli to REST API map

Every casaos-cli command is a thin HTTP call to the gateway on port 80, and the app-management journal logs each one. When a CLI command is broken, or when you want to call the API from a script without the client, these are the requests it sends:

casaos-cli commandHTTP request (as logged)
list appsGET /v2/app_management/compose
install -f <file> [-d]POST /v2/app_management/compose?dry_run=true|false
show local <id>GET /v2/app_management/compose/<id>
apply <id> [-d]PUT /v2/app_management/compose/<id>?dry_run=true|false
update app <id> [-f]PATCH /v2/app_management/compose/<id>?force=true|false
start / stop / restart <id>PUT /v2/app_management/compose/<id>/status
uninstall <id> -nDELETE /v2/app_management/compose/<id>?delete_config_folder=false
set global <key> <value>PUT /v2/app_management/global/<key>
show globalGET /v2/app_management/global
register app-store <url>POST /v2/app_management/appstore?url=<url>
list app-storesGET /v2/app_management/appstore

From the box itself, curl -s http://localhost/v2/app_management/compose returns the same data as list apps as raw JSON, which is easier to feed to jq than the table output. From anywhere else the same URL answers 401 unless you go through a tunnel.

Keep reading

Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) AI Claude Code Cheat Sheet: Commands, Shortcuts and Flags (PDF) OpenAI Codex CLI Cheat Sheet: Shortcuts, Commands, Flags (PDF) AI OpenAI Codex CLI Cheat Sheet: Shortcuts, Commands, Flags (PDF) macOS Keyboard Shortcuts Cheat Sheet (Golden Gate and Tahoe) Cheat Sheets macOS Keyboard Shortcuts Cheat Sheet (Golden Gate and Tahoe) wslc Cheat Sheet: WSL Containers Commands vs Docker (PDF) Cheat Sheets wslc Cheat Sheet: WSL Containers Commands vs Docker (PDF) Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) Cheat Sheets Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) Podman Cheat Sheet: Rootless, Pods and Quadlet Commands (PDF) Cheat Sheets Podman Cheat Sheet: Rootless, Pods and Quadlet Commands (PDF)

Leave a Comment

Press ESC to close