Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Azure-Container-Apps",
"metadata": {
"description": "Azure Container Apps skills marketplace — self-contained skills for coding agents (Copilot CLI, Claude Code).",
"version": "0.1.0"
},
"owner": {
"name": "Microsoft",
"url": "https://github.com/microsoft/azure-container-apps"
},
"plugins": [
{
"name": "sandboxes",
"source": "./plugin",
"description": "Azure Container Apps Sandboxes — hardware-isolated microVMs driven by the `aca` CLI. Future siblings (`jobs`, `apps`, `environments`) will land in this same marketplace.",
"version": "0.0.5-beta"
}
]
}
14 changes: 7 additions & 7 deletions plugin/skills/aca-sandboxes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ folder.
> ⚠️ **There is no `aca sandbox-group` (hyphenated) command group.**
> Every group-level verb is `aca sandboxgroup …` (no hyphen):
> `aca sandboxgroup create / get / list / delete / role create /
> identity assign / secret upsert / disk list / volume create / snapshot …`.
> identity assign / secret set / disk list / volume create / snapshot …`.
> The hyphenated `--sandbox-group <name>` is the *flag* you pass to
> top-level commands to select the default group — it is not a verb.
> If you see `aca sandbox-group …` in a snippet (including for MCP
Expand All @@ -50,7 +50,7 @@ intent.
|---|---|
| **Install the `aca` CLI** (any OS) | (1) The curl/iwr one-liner from [references/install.md](references/install.md) — use the `https://aka.ms/aca-cli-install` (Linux/macOS) and `https://aka.ms/aca-cli-install-ps` (Windows) short URLs. (2) `aca --version` + **auth-aware sign-in** (`az account show -o none 2>$null; if ($LASTEXITCODE -ne 0) { az login }`, then `aca auth login` to acquire an `aca` token — never call `az login` or `aca auth login` unconditionally; gate both on a status check) + `aca doctor`. The CLI surface for auth is **`aca auth login`** (the top-level `aca` binary does not have a bare `login` subcommand). (3) The explicit sentence: **"this same install path is also used inside sandboxes and containers for agent-driven self-installs."** |
| **Bootstrap a sandbox group (one-time setup)** | The 4-step flow: **check auth first** (`az account show -o none 2>$null; if ($LASTEXITCODE -ne 0) { az login }`, then `aca auth login` if `aca` is not yet authenticated — *never* call `az login` or `aca auth login` unconditionally) → `aca sandboxgroup create --name <g> --location <region> --set-config` → `aca sandboxgroup role create --role "Container Apps SandboxGroup Data Owner" --principal-id $(az ad signed-in-user show --query id -o tsv)` → `aca doctor`. **`--set-config` is required** so subsequent `aca sandbox …` commands don't need `--group` on every call. Treat green `aca doctor` as the gate before doing anything else. **Heads-up:** `aca sandboxgroup create` already grants the Data Owner role to the calling principal, so the explicit `role create` is only needed when granting access to *additional* principals (use `--skip-role-check` on `create` if you want to defer). |
| **Create a sandbox (imperative)** | Minimum: `aca sandbox create --disk ubuntu`. Common knobs: `--cpu 2000m`, `--memory 4096Mi`, `--env "K=V"`, `--labels "name=dev,role=worker"`. Capture the printed ID into `SANDBOX_ID=$(aca sandbox create --disk ubuntu -o json \| jq -r .id)` for reuse. For config that should live in source control, use the manifest flow (see the row below) instead. |
| **Create a sandbox (imperative)** | Minimum: `aca sandbox create --disk ubuntu`. Common knobs: `--cpu 2000m`, `--memory 4096Mi`, `--env "K=V"`, `--label name=dev --label role=worker` (repeatable; **not** `--labels`). Capture the printed ID into `SANDBOX_ID=$(aca sandbox create --disk ubuntu -o json \| jq -r .id)` for reuse. For config that should live in source control, use the manifest flow (see the row below) instead. |
| **Apply / deploy a sandbox manifest** | The full 3-command flow: `aca sandbox init` → `aca sandbox validate --file sandbox.yaml` → `aca sandbox apply --file sandbox.yaml`. Always `--file` (no `-f` short flag). State that **the manifest pattern is the recommended path for CI/CD and reproducibility**, in contrast to imperative `aca sandbox create`. If no manifest is present, run `aca sandbox init` — don't ask for a path. |
| **Scaffold / generate a sandbox manifest** | Run (or show) `aca sandbox init` to scaffold a starter `sandbox.yaml`. Mention the commonly edited fields (`disk`, `resources`, `lifecycle.autoSuspendPolicy`, `egressPolicy`, plus `ports`, `env`, `labels` as needed). Mention `aca sandbox schema` as the way to dump the JSON Schema for editor autocomplete. |
| **Run a command or open a shell in a sandbox** | Two distinct verbs: `aca sandbox exec --id "$SANDBOX_ID" -c "<command>"` for one-shot commands (returns stdout/stderr); `aca sandbox shell --id "$SANDBOX_ID"` for an interactive PTY. **Anti-cue:** `ssh` does not work — there is no SSH daemon inside the sandbox. `aca sandbox exec` / `shell` is the only path. |
Expand All @@ -59,7 +59,7 @@ intent.
| **Expose a port — public preview (anonymous)** | The two-step shape: `URL=$(aca sandbox port add --id "$SANDBOX_ID" --port <p> --anonymous -o json \| jq -r .url)`, then hit `$URL`. **State explicitly** that anonymous = anyone with the URL can reach it (public preview only). Remove with `aca sandbox port remove --id "$SANDBOX_ID" --port <p>`. For per-user gating use the Entra row below. |
| **Expose a port with Entra auth (per-user gating)** | `aca sandbox port add --id "$SANDBOX_ID" --port <p> --auth entra --allow-principal <user-object-id>`. Fetch the principal's object id with `az ad signed-in-user show --query id -o tsv` (or `az ad user show --id <upn> --query id -o tsv` for someone else). Pass `--allow-principal` once per principal you want to admit. **Anti-cue:** there is **no `--email` flag** — that's a stale shape; the supported gating is `--auth entra` + `--allow-principal <object-id>`. |
| **Mount a shared volume** | Two-step: (1) at the group: `aca sandboxgroup volume create --name <v> --type AzureBlob` (multi-attach, shared) or `--type DataDisk` (single-attach, high-perf block). (2) at the sandbox: `aca sandbox mount --id "$SANDBOX_ID" --volume <v> --path /mnt/<v>`. State that **the volume lives at the group level**; sandboxes attach it at runtime. |
| **Lock down network egress (deny-default + allow-list)** | The canonical form: `aca sandbox egress set --id "$SANDBOX_ID" --default Deny --host-allow "*.github.com" --traffic-inspection Full`. Multiple `--host-allow "<host>"` flags accumulate. Inspect current policy with `aca sandbox egress show --id "$SANDBOX_ID"`. For production agent code, **always recommend `--default Deny`** with an explicit `--host-allow` list. **Anti-cue:** do not invent flags for individual IPs or block-lists — the only supported shape is `--default Deny|Allow` plus repeated `--host-allow`. |
| **Lock down network egress (deny-default + allow-list)** | The canonical form: `aca sandbox egress set --id "$SANDBOX_ID" --default Deny --rule "*.github.com:Allow" --traffic-inspection Full`. Multiple `--rule "<pattern>:Allow"` flags accumulate. Inspect current policy with `aca sandbox egress show --id "$SANDBOX_ID"`. For production agent code, **always recommend `--default Deny`** with an explicit `--rule` allow-list. **Anti-cue:** do not invent flags for individual IPs or block-lists, and **not** `--host-allow` — the only supported shape is `--default Deny|Allow` plus repeated `--rule "<pattern>:Allow|Deny"`. |
| **List / use a disk image** | The canonical "what disks are available?" verb is **`aca sandboxgroup disk list-public`** (NOT `aca disks list`, which does not exist). Common presets include `ubuntu`, `debian`, `alpine`, `python`, `node`, `dotnet`, and `playwright`. To bake your own from an OCI image: `aca sandboxgroup disk create --image docker.io/library/alpine:3.19 --name <my-disk>`, then `aca sandbox create --disk-id <id>`. **Flag distinction:** `--disk` takes the public preset name; `--disk-id` takes the resource ID of a private/committed disk. |
| **Suspend, resume, or set auto-suspend** | Manual: `aca sandbox stop --id "$SANDBOX_ID"` suspends (preserves memory + disk); `aca sandbox resume --id "$SANDBOX_ID"` does sub-second restore. Idle policy: `aca sandbox lifecycle set --id "$SANDBOX_ID" --auto-suspend <seconds>` (default 300s = 5 min). State that **suspended sandboxes incur storage cost only, no compute** — this is the primary cost lever. |
| **Snapshot / commit a sandbox** | Per-sandbox: `aca sandbox snapshot --id "$SANDBOX_ID" --name <snap-name>`, then boot replicas with `aca sandbox create --snapshot <snap-name>`. Group-level CRUD: `aca sandboxgroup snapshot list / get / delete --selector "name=<snap-name>"`. **Strongly recommend snapshotting BEFORE `aca sandbox delete`** to preserve state. Always use `--name <snap-name>`, **never `--image`** (that is the wrong flag and will be rejected). Disk-only baking is `aca sandbox commit --id "$SANDBOX_ID" --name <disk-name>`. |
Expand Down Expand Up @@ -136,17 +136,17 @@ options.
| # | Capability | What it does | `aca` CLI |
|----|---------------------------|------------------------------------------------------------------------------|-----------|
| 00 | **Sandbox groups** | Provision, list/get, assign Data Owner role, tear down. | `aca sandboxgroup create / list / get / role create / delete` |
| 01 | **Sandboxes** | Create, list, get, delete; cpu/memory/labels/env; parallel. | `aca sandbox create / list / get / delete` (+ `--cpu --memory --labels --env`) |
| 01 | **Sandboxes** | Create, list, get, delete; cpu/memory/labels/env; parallel. | `aca sandbox create / list / get / delete` (+ `--cpu --memory --label --env`) |
| 02 | **Snapshots** | Freeze a running sandbox; boot new ones from that point. | `aca sandbox snapshot --id <id> --name X` · `aca sandbox create --snapshot X` |
| 03 | **Disks** | Public disks, build from container image, commit a running sandbox. | `aca sandboxgroup disk list-public / create --image` · `aca sandbox commit --id <id> --name X` · `aca sandbox create --disk <public-name>` (or `--disk-id <id>` for private/committed disks) |
| 04 | **Volumes** | `AzureBlob` (shared) or `DataDisk` (block); mount at create or post-create. | `aca sandboxgroup volume create --type AzureBlob` · `aca sandbox mount --volume X --path /mnt/x` |
| 05 | **Lifecycle** | Stop/resume; auto-suspend after idle; auto-delete after TTL. | `aca sandbox stop / resume` · `aca sandbox lifecycle set --auto-suspend 60` |
| 06 | **Ports** | Expose an HTTP port; anonymous or Entra-gated (`--auth entra --allow-principal`); revoke. | `aca sandbox port add --port 8080 [--anonymous \| --auth entra --allow-principal <object-id>]` · `port list / remove` |
| 07 | **Files** | write / read / list / stat / mkdir / delete inside the sandbox. | `aca sandbox fs write --file ./local` · `fs cat / ls` · `fs cp <src> <dst>` (positional, `sbx-id:/path` syntax) |
| 08 | **Egress** | Deny-default outbound + host allow-list; audit decisions; YAML transforms. | `aca sandbox egress set --default Deny --host-allow "*.host.com"` · `egress show / decisions / apply` |
| 09 | **Secrets** | Group-scoped key/value, fetched at runtime from inside the sandbox. | `aca sandboxgroup secret upsert --name X --values "K=V"` · `secret list / delete` |
| 08 | **Egress** | Deny-default outbound + host allow-list; audit decisions; YAML transforms. | `aca sandbox egress set --default Deny --rule "*.host.com:Allow"` · `egress show / decisions / apply` |
| 09 | **Secrets** | Group-scoped key/value, fetched at runtime from inside the sandbox. | `aca sandboxgroup secret set --name X --key K --value V` · `secret list / show / remove / delete` |
| 10 | **Managed identity** | System- or User-assigned MI on the group; grant RBAC for cross-group orchestration. | `aca sandboxgroup identity assign --system-assigned` (or `--user-assigned <res-id>`) · `identity show / remove` |
| 11 | **Labels & selectors** | `--labels k=v` at create time; AND-filter on list. Fleet management pattern. | `aca sandbox create --labels role=worker,tenant=t42` · `aca sandbox list -l role=worker` |
| 11 | **Labels & selectors** | `--label k=v` at create time (repeatable); AND-filter on list. Fleet management pattern. | `aca sandbox create --label role=worker --label tenant=t42` · `aca sandbox list -l role=worker` |
| 12 | **Interactive shell** | Real PTY into a running sandbox. | `aca sandbox shell --id <id>` |
| 13 | **YAML spec / `apply`** | Declarative infra-as-code: `init`, `validate`, `apply`, `schema`. | `aca sandbox init > sandbox.yaml` · `validate` · `apply --file sandbox.yaml` |
| 14 | **`aca doctor`** | Diagnose subscription / RG / group / region / role. | `aca doctor` |
Expand Down
4 changes: 2 additions & 2 deletions plugin/skills/aca-sandboxes/references/scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ can only reach the endpoints you allow.
SBX=$(aca sandbox create --disk ubuntu --label task=copilot-run -o json | jq -r .id)
aca sandbox egress set --id $SBX \
--default Deny \
--host-allow "api.githubcopilot.com" \
--host-allow "*.githubusercontent.com"
--rule "api.githubcopilot.com:Allow" \
--rule "*.githubusercontent.com:Allow"

aca sandbox exec --id $SBX -c "curl -fsSL https://github.com/cli/cli/releases/.../gh.tar.gz | tar -xz && ./gh ..."
```
Expand Down