Docker-native PaaS control plane built around Docker Compose. Treats Compose as the application model and augments it with traits, scoped variables, conditional overlays, and namespace isolation.
# Install (Linux/macOS, Docker or Podman required)
curl -fsSL https://raw.githubusercontent.com/kundeng/appbay-cli/main/scripts/install.sh | sh
# Check prerequisites
appbay doctor
# Initialize
appbay init
# Start the web UI
appbay server start
# Open http://localhost:3000
# Browse available apps
appbay catalog list
# Install and deploy an app
appbay install uptime-kuma
appbay up uptime-kuma
# Or deploy a system app directly
appbay up whoami
appbay ps whoamiBuild from source
# Prerequisites: Node.js 22+, pnpm, Bun
git clone https://github.com/kundeng/appbay-cli.git
cd appbay-cli
pnpm install
pnpm turbo build
./apps/cli/dist/appbay doctorBun is required — the CLI compiles to a single binary with bun build --compile, and the
sources use .js specifiers for .ts files, so Node cannot run them directly.
Appbay takes your Docker Compose apps and adds:
- Namespace isolation — run multiple apps with identical service names without conflicts
- Traits — declarative capabilities (ingress, GPU, auth, hooks, backup) attached to apps
- Conditional overlays — automatic cross-app wiring within a project (e.g., "when ollama is in this project, inject its URL into webui")
- Scoped variables —
${{ns:KEY}}references resolved at compile time from the deployment's namespace values - Secret URI references —
vault://in manifests, resolved at deploy time (the backend is an installation choice, not a manifest one) - Config overrides —
.env.localfor catalog-installed apps, upstream.envstays frozen - Plan/diff — see exactly what will change before deploying, with secrets redacted
Your upstream Compose files are never modified. appbay.yaml is a policy layer beside them,
and appbay eject gives you a standalone Compose file that runs without Appbay at all.
CLI (bun binary) ←→ packages/core (compiler) ←→ Docker / Podman Compose
↑
Web control plane ←→ tRPC API ←→ SQLite (metadata cache)
(container) ↑
Filesystem (source of truth)
- Filesystem is the source of truth —
appbay.yaml+docker-compose.ymlon disk - SQLite is a cache — delete
appbay.db, runappbay rebuild-cache, everything recovers - The CLI is complete on its own — compile, validate, deploy, eject all work with no server running
- The web control plane is optional —
appbay server startpulls and runs it as a container (ghcr.io/kundeng/appbay-server). It is a separate component and is not part of this repository; the CLI consumes it as a published image and never needs its source.
Docker and rootful Podman are both supported, selected at init:
appbay init --container-runtime docker # default
appbay init --container-runtime podmanRuntime choice is configuration, not a code path — there is no if (runtime === "podman")
in the codebase. Rootless Podman has documented limitations (it cannot bind :80/:443, so it
has no edge); see the quickstart guide.
appbay init Scaffold ~/.appbay, seed system apps
appbay init-system Host bootstrap: service account, ACLs, systemd (sudo)
appbay setup Guided first run: vault, edge, TLS, first deploy
appbay doctor Check prerequisites (runtime, Compose, GPU)
appbay validate [apps] Validate appbay.yaml + compose schemas
appbay compile [apps] Render final compose from traits/overlays
appbay apply [apps] Compile, review plan, deploy
appbay up [apps] Compile + deploy
appbay down [apps] Stop apps
appbay restart [apps] Down + up
appbay list List discovered apps
appbay status [app] Show app details, traits, overlays
appbay ps [apps] Container status
appbay logs [app] Stream logs
appbay eject <app> Export standalone compose (no Appbay needed)
appbay catalog list Browse the app catalog
appbay catalog search Search catalog by name/tag/category
appbay install <app> Install an app from the catalog
appbay pull [apps] Pull latest images
appbay delete <app> Remove app definition (--keep-volumes, --force)
appbay secrets check Verify all secret URIs resolve
appbay config <app> Get/set appbay.yaml values
appbay env <app> Manage app environment variables
appbay presets Manage app selection presets
appbay open <app> Open app URL in browser
appbay edge migrate Switch the ingress provider with validation and rollback (--to caddy|traefik)
appbay url <app> Print app URL
appbay size [app] Show disk usage for apps
appbay fixfs <app> Fix filesystem permissions for volumes
appbay server start Start the web control plane
appbay update Self-update CLI binary
appbay rebuild-cache Regenerate SQLite from files
appbay home Print APPBAY_HOME path
appbay info System info (OS, runtime, GPU, apps)
appbay completion Generate shell completions
appbay --help is authoritative, and
the CLI reference documents every flag.
AppBay has two independent passwords — see the credentials guide.
appbay edge users reset-password <user> Signs in to your deployed apps — AND to AppBay itself
appbay secrets vault rotate-password The master password (unlocks vault.enc)
There used to be three. RFC-001 §1 removed the separate control-plane account: the web UI
sits behind the Caddy Security edge like any other app, so the edge account is the account.
Reaching the control plane additionally requires the authp/admin role — its edge route carries
an admin-only policy, because it is the one stack that can reach the container runtime socket.
appbay authelia, appbay auth and appbay admin are retired; running any of them explains
what replaced it and exits non-zero.
| Concept | Controls | Cardinality |
|---|---|---|
| Namespace | Deployment identity — container, network, DNS-alias and default-host names — and the deployment's values file | Single per app |
| Project | Which apps run together: what when: can see, and the start order in etc/projects.yaml |
Single per app |
| Collection, tags | Labels for selection (up --collection) |
Multi per app |
namespace replaced project + environment in v0.0.1-alpha.12; a non-default value for
either is now a parse error naming the migration.
One value scope: ${{ns:KEY}} reads etc/namespaces/<namespace>.yaml layered over
default.yaml, which init seeds with the system's DOMAIN. An ingress trait that omits
host: is routed at <app>.<domain> (or <ns>.<app>.<domain>), so two instances of one app
get two hosts. See the scope model reference.
packages/
core/ Compiler pipeline, Zod schemas, trait registry, secret resolution
db/ Drizzle ORM + SQLite schema, cache store
apps/
cli/ CLI binary (bun build --compile)
system-apps/ Bundled app definitions. One host runs ONE edge:
traefik/ Edge: ingress only
caddy/ Edge: ingress + identity (Caddy Security). Required for auth traits
ollama/ Local LLM inference
open-webui/ Chat UI for Ollama
vaultwarden/ Bitwarden-compatible password manager
homeassistant/ Home automation
nextcloud/ File sync and collaboration
jellyfin/ Media server
homepage/ Dashboard
keeweb/ KeePass web UI for secrets
sysinfo/ Operator diagnostics
whoami/ Protocol fixture / starter demo
docs/ Quarto documentation website
system-apps/ is the source; packages/core/src/system-apps.ts is generated from it by
scripts/generate-system-apps.mjs. Edit the directory, then run the generator — a test
fails if the committed output drifts.
pnpm install
pnpm turbo build # Build all packages
pnpm turbo test # Run all tests
pnpm check:system-apps # Verify generated system apps match the source
pnpm check:straddle # Verify the open-core boundary holdsThe documentation site is built with Quarto from docs/:
- Quickstart — install through first deploy
- Concepts — apps, traits, overlays, scopes
- Traits and Overlays
- Secrets and Credentials
- Catalog — installing curated apps
- Reference —
appbay.yamlschema, CLI, API, scope model
Contributions are welcome — see CONTRIBUTING.md for the development setup and the sign-off requirement.
MIT — see LICENSE.

