原始内容
name: appgarden description: > Deploy and manage web applications on remote servers with AppGarden. Use when the user asks about deploying apps, configuring appgarden.toml, managing AppGarden servers/apps, viewing logs/status/metadata, or exposing local work through AppGarden tunnels. user-invocable: false
AppGarden CLI Skill
Use this skill when deploying or operating web applications with the appgarden CLI.
AppGarden is an agentless deploy tool: the local CLI connects over SSH, prepares the remote server, uploads or clones app source, writes Caddy/systemd/Docker files, and records state under the server's AppGarden root (default /srv/appgarden).
Before running commands
Prefer running from the AppGarden repo checkout when developing/testing AppGarden itself:
cd /path/to/appgarden && uv run appgarden ...If AppGarden is installed in the environment,
appgarden ...is also fine.Read-only/safe discovery commands are fine to run without confirmation:
appgarden --help,appgarden version,appgarden config show,appgarden server list,appgarden server ping,appgarden apps list,appgarden apps status,appgarden apps logs,appgarden tunnel list.Ask before commands that create, mutate, deploy, restart, stop, delete, or expose services:
server add,server remove,server default,server init,deploy,apps start|stop|restart|redeploy|remove,apps meta set|replace|remove,tunnel open|close|cleanup.Be especially careful with:
appgarden server init --include ssh: hardens SSH configuration and reloads sshd.appgarden server init --include firewall: enables UFW and changes firewall policy.appgarden apps remove NAME --yes: deletes app resources; add--keep-dataonly when preserving the app'sdata/directory is intended.appgarden deploy: may overwrite uploaded source for an existing app and may update Caddy/systemd/Docker resources.appgarden tunnel open: publicly exposes a local port/file/directory through the configured server until stopped.
Configuration locations
Local config is stored at:
~/.config/appgarden/config.toml
Server config shape:
default_server = "myserver"
[defaults]
method = "dockerfile"
container_port = 3000
[servers.myserver]
host = "203.0.113.10" # or hcloud_name + hcloud_context
ssh_user = "root"
ssh_key = "~/.ssh/id_rsa"
domain = "apps.example.com"
app_root = "/srv/appgarden" # optional; defaults to /srv/appgarden
[servers.myserver.init]
skip = ["upgrades"] # optional persistent init skips
Remote state and resources are under app_root, usually:
/srv/appgarden/garden.json # app registry
/srv/appgarden/apps/<name>/ # per-app files/source/app.json/.env/data
/srv/appgarden/caddy/apps/*.caddy # deployed app Caddy snippets
/srv/appgarden/caddy/tunnels/*.caddy
/srv/appgarden/tunnels/active.json
appgarden config show prints the local config. Use --server <name> / -s <name> or APPGARDEN_SERVER on commands that target a server; otherwise AppGarden uses default_server.
Multiple gardens on one host: you can run several AppGarden gardens on the same box by giving each a distinct app_root (e.g. /srv/appgarden and /srv/appgarden-proto). Per-garden state (garden.json), Caddy snippets, and the /etc/caddy/Caddyfile managed block are all keyed per app_root, so server init for one garden won't disturb another. Ports, however, are a box-global resource — every garden shares the host's TCP port space — so port allocations live in a single host-level registry at /var/lib/appgarden/ports.json, shared by all gardens under one lock. This is what prevents two gardens handing out the same host port; you don't configure port ranges. Caveats: systemd unit and Docker container names are box-global (appgarden-<name>), so app names must be unique across gardens on one host. If you're upgrading a host that pre-dates the shared registry, run appgarden server init once per existing garden — init reconciles each garden's already-deployed app ports into the shared registry (idempotent).
Server setup
A server must be configured, reachable over SSH, and have DNS pointing at it.
appgarden server add myserver \
--host <server-ip-or-hostname> \
--ssh-user root \
--ssh-key ~/.ssh/id_rsa \
--domain apps.example.com
# Hetzner alternative: resolve IP through hcloud instead of --host
appgarden server add myserver \
--hcloud-name <hetzner-server-name> \
--hcloud-context <hcloud-context> \
--domain apps.example.com
appgarden server list
appgarden server ping myserver
appgarden server default myserver
For subdomain deployments and tunnels, create wildcard DNS such as *.apps.example.com pointing at the server IP. Explicit custom hostnames also need DNS pointing at the server.
Initializing a server
Run once per server:
appgarden server init myserver
Current server init behavior:
- Optional steps on by default; skip with
--skip:update,docker,caddy,upgrades. - Opt-in steps off by default; enable with
--include:firewall,ssh,fail2ban,group. - Essential steps always run: configure the AppGarden Caddyfile import block, create directories, install the privileged helper/sudoers entry, set ownership for the deploy user, initialize state files, and start Docker/Caddy where available.
Examples:
appgarden server init myserver --minimal # essential steps only
appgarden server init myserver --skip upgrades # skip unattended-upgrades
appgarden server init myserver --include group # create/use appgarden group for non-root deploy users
appgarden server init myserver --include firewall # configure UFW; confirm first
appgarden server init myserver --include ssh # harden sshd; confirm first
For non-root SSH users, server init installs /usr/local/bin/appgarden-privileged and a sudoers rule restricted to AppGarden-scoped privileged operations.
Preferred deployment flow: appgarden.toml
Create appgarden.toml in the project root and deploy named environments.
[app]
name = "myapp"
slug = "my-app" # optional; used by {app.slug}; falls back to name
server = "myserver"
method = "dockerfile" # static | command | dockerfile | docker-compose | auto
source = "." # local path or git URL
container_port = 3000
subdomain = "{app.slug}" # or path/url; supports placeholders
exclude = ["node_modules", ".git"]
gitignore = true # local uploads honor .gitignore by default
meta = { team = "frontend" }
[environments.production]
branch = "main"
subdomain = "{app.slug}"
env = { NODE_ENV = "production" }
[environments.staging]
branch = "staging"
subdomain = "{app.slug}-staging"
env_file = ".env.staging"
meta = { visibility = "internal" }
Deploy:
appgarden deploy production
appgarden deploy staging
appgarden deploy --all-envs
appgarden deploy --project /path/to/project production
If appgarden.toml is present and no environment/name is provided, appgarden deploy deploys all environments. If a positional environment is provided, it must exist.
appgarden.toml semantics
[app].nameis required. Deployed app names are derived from it:productionuses the base name, every other environment uses<name>-<env>. Example:myappandmyapp-staging.- Values cascade in this order: hardcoded defaults < global
[defaults]in~/.config/appgarden/config.toml<[app]defaults <[environments.<name>]< CLI flags /APPGARDEN_*environment variables. - Dict fields
envandmetamerge by key; environment/CLI values override earlier keys. - List fields
excludeandvolumesconcatenate and deduplicate across layers. - String values in project/environment config support placeholders:
{app.name},{app.slug},{env.name}. - Relative local
sourceandenv_filevalues resolve relative to the project directory or the directory containing the explicit--projectfile.
Useful fields in [app] or [environments.<name>]:
| Field | Meaning |
|---|---|
server |
Configured server name. |
method |
static, command, dockerfile, docker-compose, or auto. Default is static. |
source |
Local directory/path or git URL. Required for static, dockerfile, docker-compose, auto; optional for command. |
url |
Explicit public hostname/path, e.g. example.com or example.com/docs. |
subdomain |
Prefix combined with domain or the server domain, e.g. docs -> docs.apps.example.com. |
path |
Path under domain or server domain, e.g. docs -> apps.example.com/docs. |
domain |
Override the server's base domain for subdomain/path. |
port |
Host port; omitted means AppGarden auto-allocates from its port state. |
container_port |
Port exposed inside Dockerfile/auto containers. Default is 3000. |
cmd |
Start command for command and auto methods. Required for both. |
setup_cmd |
Install/build command for auto; overrides the detected runtime default. |
branch |
Git branch for git sources. |
env |
Inline environment variables. |
env_file |
Local dotenv file. |
meta |
Arbitrary metadata stored with the app and shown in status. |
exclude |
Rsync exclude patterns for local uploads; also reused by apps redeploy. |
gitignore |
Whether local uploads pass rsync's .gitignore filter. Default true; CLI override is --no-gitignore. |
volumes |
Docker volume mounts (host:container[:opts]) for dockerfile and auto. |
created_at, updated_at, repo |
Optional metadata overrides. Dates accept short date, ISO string, or TOML dates; repo is otherwise auto-detected from local git origin when possible. |
URL resolution order is: explicit url; otherwise subdomain + domain; otherwise path + domain. For subdirectory routing (domain/path), multiple apps on the same domain share a generated Caddy config and AppGarden checks URL conflicts.
Ad-hoc deployment without appgarden.toml
Use --name and flags. Most flags also have APPGARDEN_* environment variable equivalents (APPGARDEN_NAME, APPGARDEN_SERVER, APPGARDEN_METHOD, APPGARDEN_SOURCE, APPGARDEN_URL, APPGARDEN_SUBDOMAIN, APPGARDEN_PATH, APPGARDEN_DOMAIN, APPGARDEN_PORT, APPGARDEN_CONTAINER_PORT, APPGARDEN_CMD, APPGARDEN_SETUP_CMD, APPGARDEN_BRANCH, APPGARDEN_ENVVAR_FILE, APPGARDEN_PROJECT, APPGARDEN_ALL_ENVS).
# Static site / SPA
appgarden deploy --name mysite --method static --source ./dist --subdomain mysite
# Dockerfile app
appgarden deploy --name myapp --method dockerfile --source . --container-port 3000 --subdomain myapp
# Auto-generated Dockerfile; runtime is detected from package.json, requirements.txt,
# pyproject.toml, Gemfile, go.mod, or Cargo.toml. --cmd is required.
appgarden deploy --name myapp --method auto --source . --cmd "npm start" --subdomain myapp
# Bare process managed by systemd. --source is optional; --cmd is required.
appgarden deploy --name myapi --method command --source ./api --cmd "python app.py" --subdomain myapi
# Docker Compose stack; your compose file is used directly.
appgarden deploy --name mystack --method docker-compose --source ./project --subdomain mystack
# Explicit path routing instead of subdomain routing
appgarden deploy --name docs --method static --source ./docs --url apps.example.com/docs
# Env, metadata, excludes, and volumes
appgarden deploy --name myapp --method dockerfile --source . --subdomain myapp \
--envvar NODE_ENV=production \
--envvar-file .env.production \
--meta team=backend --meta visibility=internal \
--exclude node_modules --exclude .git \
--volume ./data:/app/data \
--no-gitignore
Deployment method notes:
| Method | Use for | Required inputs | Runtime behavior |
|---|---|---|---|
static |
HTML/CSS/JS/SPAs | source |
Upload/clone source and serve directly with Caddy. |
command |
A local process without Docker | cmd (source optional) |
Creates a systemd service with PORT set to allocated/explicit port. |
dockerfile |
Projects with a Dockerfile | source |
Builds image remotely, writes AppGarden compose file, runs through systemd. |
docker-compose |
Existing compose projects | source |
Runs your docker compose up/down through systemd; volumes config is not injected. |
auto |
Simple Node/Python/Ruby/Go/Rust projects | source, cmd |
Detects runtime, writes Dockerfile, builds image, writes compose file. |
For local uploads, AppGarden uses rsync with --delete, honors .gitignore by default, and applies any --exclude/exclude patterns. For git sources, it clones the repo on the remote and optionally checks out the requested branch.
Environment variable precedence for deployed app .env files is: appgarden.toml env < env_file content < CLI --envvar values.
Managing deployed apps
appgarden apps list [-s server] [--short]
appgarden apps status <name> [-s server]
appgarden apps logs <name> [-s server] [-n 100]
appgarden apps restart <name> [-s server]
appgarden apps redeploy <name> [-s server]
appgarden apps stop <name> [-s server]
appgarden apps start <name> [-s server]
appgarden apps remove <name> [-s server] [--keep-data] [--yes]
Operational guidance:
Use
apps listfirst when you do not know the exact app name.Use
apps statusto inspect URL, routing, method, port, repo/source, timestamps, and metadata.Use
apps logs NAME -n 200before restarting when debugging.apps redeployupdates source: git apps rungit pull(with the stored branch if present); local-source apps re-upload with storedexclude/gitignore; Dockerfile/auto apps rebuild images and preserve stored volumes; static apps reload Caddy.If changing an app name in
appgarden.toml, remove the old app first to avoid URL/Caddy conflicts:appgarden apps remove old-name --yes appgarden deploy production
Metadata
Metadata is arbitrary JSON-compatible key/value data stored with the app.
appgarden apps meta get myapp [-s server]
appgarden apps meta set myapp --meta team=backend --meta tier=premium [-s server]
appgarden apps meta replace myapp --json '{"team":"frontend"}' [-s server]
appgarden apps meta remove myapp tier visibility [-s server]
In appgarden.toml, meta dictionaries merge like env dictionaries:
[app]
name = "myapp"
meta = { team = "backend", visibility = "internal" }
[environments.production]
meta = { visibility = "public" }
# result: { team = "backend", visibility = "public" }
Localhost tunnels
Use tunnels to expose a local dev server, file, directory, or command through the remote server with HTTPS. A tunnel blocks until Ctrl+C unless --close-on-cmd-exit is used with --cmd/--serve.
# Expose an existing local server. URL omitted => random three-word subdomain.
appgarden tunnel open 3000
# Use server domain subdomain shorthand or an explicit URL
appgarden tunnel open 3000 --subdomain preview
appgarden tunnel open 3000 --url preview.apps.example.com
# Run a local command while the tunnel is open
appgarden tunnel open 3000 --cmd "npm run dev"
appgarden tunnel open 3000 --cmd "npm run dev" --close-on-cmd-exit
# Serve a local file or directory. LOCAL_PORT is optional with --serve.
appgarden tunnel open --serve ./dist --subdomain docs-preview
appgarden tunnel open --serve ./dist --include "*.html" --exclude "node_modules"
appgarden tunnel list [-s server]
appgarden tunnel close <tunnel-id> [-s server]
appgarden tunnel cleanup [-s server]
--cmd and --serve are mutually exclusive. --include/--exclude only apply when --serve points to a directory. Tunnels allocate a remote port, write a Caddy snippet under caddy/tunnels, and register state in tunnels/active.json; cleanup removes those resources.
Troubleshooting workflow
Confirm config and server targeting:
appgarden config show appgarden server list appgarden server ping <server>Inspect app state and logs:
appgarden apps list --short appgarden apps status <app> appgarden apps logs <app> -n 200Redeploy or restart only after reading the error:
appgarden apps redeploy <app> appgarden apps restart <app>Common local-source upload issues:
rsyncmust be installed locally.- Encrypted SSH keys need an agent:
eval $(ssh-agent) && ssh-add ~/.ssh/id_rsa. - Permission errors on non-root deploys usually mean rerun
appgarden server init --include groupor fix remote ownership.
Reference files in this repository
The generated src/appgarden/*.py files say "AUTOGENERATED"; implementation edits should usually be made in the corresponding pts/appgarden/*.pct.py source files. For skill/use questions, read these files as needed:
README.md— user-facing guide and CLI reference.src/appgarden/cli.py/pts/appgarden/10_cli.pct.py— command definitions and options.src/appgarden/config.py/pts/appgarden/00_config.pct.py— local config model and default config path.src/appgarden/environments.py/pts/appgarden/08_environments.pct.py—appgarden.tomlparsing, merge semantics, placeholders.src/appgarden/server.py/pts/appgarden/04_server.pct.py— server init steps.src/appgarden/deploy.py/pts/appgarden/05_deploy.pct.py— deployment behavior and remote state.src/appgarden/apps.py/pts/appgarden/06_apps.pct.py— app lifecycle and redeploy behavior.src/appgarden/routing.py/pts/appgarden/03_routing.pct.py— Caddy routing and URL conflict behavior.src/appgarden/tunnel.py/pts/appgarden/09_tunnel.pct.py— tunnel behavior.