appgarden

>

Skill file

Preview skill file
---
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:

  ```bash
  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-data` only when preserving the app's `data/` 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:

```text
~/.config/appgarden/config.toml
```

Server config shape:

```toml
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:

```text
/srv/appgarden/garden.json          # app registry
/srv/appgarden/ports.json           # allocated ports
/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`.

## Server setup

A server must be configured, reachable over SSH, and have DNS pointing at it.

```bash
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:

```bash
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:

```bash
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.

```toml
[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:

```bash
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].name` is required. Deployed app names are derived from it: `production` uses the base name, every other environment uses `<name>-<env>`. Example: `myapp` and `myapp-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 `env` and `meta` merge by key; environment/CLI values override earlier keys.
- List fields `exclude` and `volumes` concatenate and deduplicate across layers.
- String values in project/environment config support placeholders: `{app.name}`, `{app.slug}`, `{env.name}`.
- Relative local `source` and `env_file` values resolve relative to the project directory or the directory containing the explicit `--project` file.

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`).

```bash
# 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

```bash
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 list` first when you do not know the exact app name.
- Use `apps status` to inspect URL, routing, method, port, repo/source, timestamps, and metadata.
- Use `apps logs NAME -n 200` before restarting when debugging.
- `apps redeploy` updates source: git apps run `git pull` (with the stored branch if present); local-source apps re-upload with stored `exclude`/`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:

  ```bash
  appgarden apps remove old-name --yes
  appgarden deploy production
  ```

## Metadata

Metadata is arbitrary JSON-compatible key/value data stored with the app.

```bash
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:

```toml
[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`.

```bash
# 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

1. Confirm config and server targeting:

   ```bash
   appgarden config show
   appgarden server list
   appgarden server ping <server>
   ```

2. Inspect app state and logs:

   ```bash
   appgarden apps list --short
   appgarden apps status <app>
   appgarden apps logs <app> -n 200
   ```

3. Redeploy or restart only after reading the error:

   ```bash
   appgarden apps redeploy <app>
   appgarden apps restart <app>
   ```

4. Common local-source upload issues:
   - `rsync` must 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 group` or 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.toml` parsing, 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.

Source

Creator's repository · lukastk/appgarden

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
What this skill can do
Reads your filesConnects to the internetRuns code on your machine
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk