diff --git a/templates/coolify-ops/commands/setup.md b/templates/coolify-ops/commands/setup.md new file mode 100644 index 0000000..d8efbe9 --- /dev/null +++ b/templates/coolify-ops/commands/setup.md @@ -0,0 +1,82 @@ +--- +description: Set up the coolify-ops workflow for your Coolify instance (interview, personal state, smoke test) +--- + +# Setup: coolify-ops + +You are the agent running this setup. Interview the user, build their +personal state, and smoke-test the connection. Work through the sections +in order. This file assumes only file access and the ability to make HTTP +requests; it works the same inside gcontext and standalone in any agent. + +## 1. Read first + +Read the workflow's `index.md` and `steps/index.md` before asking +anything, so the interview is informed. Skim `runs/example/` to see what +a correct run looks like. + +## 2. Bind the parameter + +Ask for `instance-url`: the base URL of the user's Coolify instance, for +example `https://coolify.example.com`. All API calls go to +`/api/v1`. + +## 3. Map the connection + +The workflow needs one connection: the Coolify API, authenticated with a +bearer token. + +- Ask the user for an API token of the instance (created in the Coolify + UI under Keys & Tokens, API tokens). +- Store it as a secret named `COOLIFY_API_KEY` in whatever secret + mechanism the environment has (in gcontext: the instance's secrets; + standalone: an environment variable or the agent's secret store). + Never write the token value into any file of this workflow. + +## 4. Generate the personal state + +Create these files in the workflow folder (they are the user's own state; +the template never ships them): + +- `overview.md`: the mirror. Start it empty, with exactly these section + headings, in this order, and a `Last sync: never` line at the top: + `## servers`, `## projects`, `## applications`, `## services`, + `## databases`. Step 1 of the first run fills it. +- `scripts/sync.py`: the sync script. Write it read-only, to this + specification: + - Takes the instance URL as a command line argument, defaulting to the + bound `instance-url` value (bake the bound value into the script as + the argument default; that is where it is persisted); reads the + token from the + `COOLIFY_API_KEY` environment variable and fails with a clear + message if the variable is unset. + - Calls `GET /servers`, `/projects`, `/applications`, `/services`, + `/databases` under `/api/v1` with the header + `Authorization: Bearer `. Each endpoint returns a bare JSON + array of resource objects. + - Prints one line per resource in the mirror format + `kind | name | uuid | status | domain`, grouped under the five + section headings. Print `?` when a resource has no status field + (servers and projects have none) and `-` when it has no domain. For + applications the domain is the `fqdn` field; services carry domains + on their inner applications' `fqdn`; databases have none. + - Standard library only, or a common HTTP library if one is already + available. No write calls of any kind in this script. + +Leave `runs/example/` in place; the user's own runs get their own folders +next to it, named `-`. + +## 5. Smoke test + +First verify the secret is retrievable (the `COOLIFY_API_KEY` variable +is set and non-empty). Then run the sync script once (read-only). If it +prints the resource lines, write the first real `overview.md` from its +output and show the user a short summary of their fleet. If it fails: +a 401 means the token is wrong, a DNS error or 404 means the URL or the +`/api/v1` path is wrong. Fix it with the user before declaring setup +done. + +## 6. Never edit the procedure + +Setup personalizes state only. Do not edit anything in `steps/`, +`playbooks/`, or `runs/example/`. diff --git a/templates/coolify-ops/index.md b/templates/coolify-ops/index.md new file mode 100644 index 0000000..996b3db --- /dev/null +++ b/templates/coolify-ops/index.md @@ -0,0 +1,63 @@ +--- +id: coolify-ops +name: Coolify Ops +description: > + Operate a Coolify instance with a synced mirror of what is deployed and + playbooks that accumulate as operations are performed and repeated. +parameters: + - name: instance-url + description: Base URL of the Coolify instance to operate (for example https://coolify.example.com) + required: true +connections: + - kind: http-api + description: The Coolify API of the instance, authenticated with an API token +tags: [ops, infrastructure] +--- + +# coolify-ops + +Operations workflow for a Coolify instance. It is a mirror plus playbooks +loop: a synced map of what is deployed on the instance, and playbooks for +the operations performed on it again and again. Every run starts with a +sync, so the mirror never drifts from reality, and every run ends by +writing what it learned back into the playbooks. The workflow gets better +the more operations it performs. + +## Parameters in practice + +- `instance-url`: the base URL of the Coolify instance, bound once at + setup. All API calls go to `/api/v1`. The API token that + authenticates the calls is stored as a secret during setup, never in + these files. +- Each run additionally starts from one requested operation (redeploy an + application, investigate a failing service, change an env var). The + operation is a per-run input, recorded in the run's `0-parameters.md`, + never bound at setup. + +## Run naming + +Run folders are named `-`, for example +`2026-08-02-redeploy-website`. The date anchors the run in time; the +operation slug says what the run did. Use the plain ISO date only if a run +has no single nameable operation. + +## Files + +- `steps/`: the loop, executed in order on every run. Read + `steps/index.md` first. +- `playbooks/`: one file per recurring operation, written from real runs. + Ships with one seed playbook (redeploy an application); grows with use. +- `runs/`: one folder per run. `runs/example/` is a fabricated + demonstration run with fake names; read it before the first real run. +- `commands/setup.md`: the install interview. Run it once before the + first run. +- `overview.md` (created at setup): the mirror, the synced state of the + instance. Not part of the template; every install builds its own. + +## Hard rules + +- Never delete anything on the instance. No delete endpoints, ever. +- Ask the user before every operation that changes the instance (deploy, + restart, stop, start, env changes). Classify by effect, not by HTTP + verb: the Coolify deploy trigger is a GET that writes. +- Read operations (GET without side effects) need no confirmation. diff --git a/templates/coolify-ops/playbooks/redeploy-app.md b/templates/coolify-ops/playbooks/redeploy-app.md new file mode 100644 index 0000000..2e7ea87 --- /dev/null +++ b/templates/coolify-ops/playbooks/redeploy-app.md @@ -0,0 +1,37 @@ +# Playbook: redeploy an application + +Written from a real run. Improve this file after every run that uses it. + +## Steps + +1. Get the application uuid from `overview.md` (sync first, step 1 of the + loop). Confirm the app name and uuid with the user before the call. +2. Trigger the deploy. It is a GET request, not a POST, even though it is + a write operation: `GET /deploy?uuid=`. Ask the user before + this call; it is a write operation against the instance. +3. The response contains `deployment_uuid`. Keep it; all follow-up goes + through it. +4. Poll `GET /deployments/` until `status` is `finished` + or `failed`; every 15 to 30 seconds is enough, and report to the user + instead of polling on past 10 minutes. The `logs` field is a + JSON-encoded string; parse it and read the `output` field of each + entry. +5. Verify: `GET /applications/` for status, then an HTTP GET on + the app's domain. Expect 200. + +## Known behavior + +- The deploy trigger is a GET endpoint. Every other write API is usually + POST; do not let the verb fool you, it queues a real deployment. +- A deploy with an unchanged git commit SHA finishes in seconds: Coolify + skips the build and reuses the image; only the rolling update runs. A + deploy with a new commit takes minutes, not seconds. +- The logs contain a scary-looking line early on: `Error response from + daemon: No such container: `. It is harmless; the + helper checks for a leftover container before creating it. +- The rolling update can warn about an orphan container from the previous + deployment. Also harmless; Coolify removes the old container itself + right after. +- App status can stay `running:unknown` even after a successful deploy; + the `unknown` part is the health check, not a problem by itself. Verify + with an HTTP request to the domain instead. diff --git a/templates/coolify-ops/runs/example/0-parameters.md b/templates/coolify-ops/runs/example/0-parameters.md new file mode 100644 index 0000000..5e7c4c1 --- /dev/null +++ b/templates/coolify-ops/runs/example/0-parameters.md @@ -0,0 +1,7 @@ +# Parameters + +- Operation: redeploy the application `acme-website` (the marketing site + was updated in git; the running container still serves the old build). +- Target: application `acme-website`, project Acme. +- Instance: bound at setup (`instance-url`), Coolify API v1. +- Requested by: the user, 2026-08-02. diff --git a/templates/coolify-ops/runs/example/1-sync/results.md b/templates/coolify-ops/runs/example/1-sync/results.md new file mode 100644 index 0000000..e45ad93 --- /dev/null +++ b/templates/coolify-ops/runs/example/1-sync/results.md @@ -0,0 +1,15 @@ +# Sync results + +Ran the saved sync script against the instance. Fleet: 1 server, 2 +projects, 3 applications, 1 service, 1 database. + +Drift against `overview.md` since the last sync (2026-07-28): + +- `application | staging-portal | pk4n8r2v6z0c3m7q1w5e9t3y` changed + status: `running:healthy` to `exited:unhealthy`. Reported to the user; + not this run's target, noted for a separate investigation. +- No other changes. The target `acme-website` is + `mk3v7q9x2c5b8n1z4f6h0j2l`, status `running:unknown`, domain + `https://acme-website.example.com`. + +`overview.md` updated to match, "Last sync" set to 2026-08-02. diff --git a/templates/coolify-ops/runs/example/2-operate/results.md b/templates/coolify-ops/runs/example/2-operate/results.md new file mode 100644 index 0000000..6618b76 --- /dev/null +++ b/templates/coolify-ops/runs/example/2-operate/results.md @@ -0,0 +1,24 @@ +# Operation results: redeploy acme-website + +Followed `playbooks/redeploy-app.md`. + +1. Confirmed the target with the user from the mirror: `acme-website`, + uuid `mk3v7q9x2c5b8n1z4f6h0j2l`, project Acme. +2. User approved the write call. Triggered + `GET /deploy?uuid=mk3v7q9x2c5b8n1z4f6h0j2l`. Response: + `deployment_uuid: d4g8s2w6e0r4t8y2u6i0o4p8`, status `queued`. +3. Polled `GET /deployments/d4g8s2w6e0r4t8y2u6i0o4p8`: + - Poll 1 (10 s): `in_progress`. Logs showed the harmless + `Error response from daemon: No such container: + d4g8s2w6e0r4t8y2u6i0o4p8` line early on, as the playbook predicts. + - Poll 2 (95 s): `in_progress`, build running (new commit SHA, so no + image reuse this time). + - Poll 3 (240 s): `finished`. Rolling update warned about an orphan + container from the previous deployment; Coolify removed it itself. +4. Verified: `GET /applications/mk3v7q9x2c5b8n1z4f6h0j2l` returned status + `running:unknown` (health check unknown, expected per the playbook). + HTTP GET on `https://acme-website.example.com` returned 200 and the + new content. + +Surprise worth keeping: a deploy with a new commit took about 4 minutes; +the playbook's note about same-SHA instant finishes held in reverse. diff --git a/templates/coolify-ops/runs/example/done/info.md b/templates/coolify-ops/runs/example/done/info.md new file mode 100644 index 0000000..6a9611d --- /dev/null +++ b/templates/coolify-ops/runs/example/done/info.md @@ -0,0 +1,21 @@ +# Run closed: 2026-08-02-redeploy-website + +## Achieved + +Redeployed `acme-website` after a content update. Deployment +`d4g8s2w6e0r4t8y2u6i0o4p8` finished; the site serves the new build and +answers 200 on `https://acme-website.example.com`. + +## Learned + +- A new-commit deploy took about 4 minutes (build plus rolling update); + only same-SHA deploys finish in seconds. Added the timing note to the + playbook. +- The `staging-portal` application drifted to `exited:unhealthy` since + the last sync. Not this run's scope; the user opened a separate + investigation for it. + +## Playbook effect + +`playbooks/redeploy-app.md` improved: added the new-commit timing +expectation next to the same-SHA note. diff --git a/templates/coolify-ops/runs/example/index.md b/templates/coolify-ops/runs/example/index.md new file mode 100644 index 0000000..c15b666 --- /dev/null +++ b/templates/coolify-ops/runs/example/index.md @@ -0,0 +1,15 @@ +# Run: 2026-08-02-redeploy-website (example) + +Fabricated demonstration run with fake names. It shows what one +coolify-ops run looks like: a redeploy of the application `acme-website` +after a content update, on the instance of the fictional company Acme. + +## Status + +| Step | State | Result | +|---|---|---| +| 1-sync | done | `1-sync/results.md`: one status drift found | +| 2-operate | done | `2-operate/results.md`: redeploy finished, site verified | +| 3-close | done | `done/info.md`: playbook improved | + +Run closed. See `done/info.md` for the summary. diff --git a/templates/coolify-ops/steps/1-sync.md b/templates/coolify-ops/steps/1-sync.md new file mode 100644 index 0000000..b9e01a4 --- /dev/null +++ b/templates/coolify-ops/steps/1-sync.md @@ -0,0 +1,57 @@ +# Step 1: sync the mirror + +## Purpose + +Keep the mirror (`overview.md`) matching the real state of the Coolify +instance, so every operation starts from trusted facts and drift is +noticed before it surprises anyone. This step runs first on every run, +never optional. + +## Input + +- The saved sync script at `scripts/sync.py`, generated at setup. It + takes the instance URL as a command line argument (defaulting to the + bound value) and reads the API token from the `COOLIFY_API_KEY` + environment variable. If the script or `overview.md` is missing, setup + was never run: stop and run `commands/setup.md` first (it contains the + full script specification). +- The current `overview.md` in the workflow folder, the last trusted state. + +## Output + +`1-sync/results.md` in the run folder: the drift found, one line per +changed resource, or "no drift" when the mirror already matched. Update +`overview.md` itself so it matches what the API returned. + +The mirror format, one line per resource: + +``` +kind | name | uuid | status | domain +``` + +Sections: servers, projects, applications, services, databases. + +## How to execute + +1. Run the saved sync script. It calls, read-only, the Coolify API + endpoints `/servers`, `/projects`, `/applications`, `/services`, + `/databases` under `/api/v1` with the bearer token, and + prints one line per resource in the mirror format. +2. Diff the output against `overview.md`. +3. Update `overview.md` so it matches the API output. Keep the "Last + sync" date at the top current. +4. Write the drift summary to `1-sync/results.md` in the run folder and + tell the user what changed since the last sync. + +Known Coolify API facts: + +- The servers and projects endpoints return no status field; the mirror + shows `?` there. +- An application status of `running:unknown` means the health check is + unknown, not that the app is broken. Verify with an HTTP request to the + app's domain when it matters. + +## Done when + +`overview.md` matches the live API state, the drift is recorded in +`1-sync/results.md`, and the user has been told what changed. diff --git a/templates/coolify-ops/steps/2-operate.md b/templates/coolify-ops/steps/2-operate.md new file mode 100644 index 0000000..873619b --- /dev/null +++ b/templates/coolify-ops/steps/2-operate.md @@ -0,0 +1,47 @@ +# Step 2: do the operation + +## Purpose + +Perform the operation this run was started for, using accumulated +playbook knowledge before investigating from scratch. + +## Input + +- The requested operation and its target, from `0-parameters.md` in the + run folder. +- The synced mirror (`overview.md`), for names, uuids, statuses, and + domains. Never call the API with a uuid that was not confirmed against + the mirror. +- `playbooks/`: the library of procedures from previous runs. + +## Output + +`2-operate/results.md` in the run folder: what was done, every API call +made, what the API returned, and what surprised you. When a script was +generated and is worth keeping, save it next to the results as +`script.py`. + +## How to execute + +1. Check `playbooks/` first. If a playbook matches the requested + operation, follow it before investigating from scratch. +2. If no playbook matches, investigate through read-only API calls, then + propose the write operations to the user. Keep notes; step 3 turns + them into a new playbook. +3. Confirm the target with the user (name plus uuid from the mirror) + before any write call. + +## Hard rules + +- Never delete anything on the instance. No delete endpoints, ever. +- Ask the user before every write operation against the API (deploy, + restart, stop, start, env changes, anything non-GET). Beware: in the + Coolify API some write operations are GET endpoints, for example the + deploy trigger `GET /deploy?uuid=`. Classify by effect, not + by verb. +- Read operations (GET without side effects) need no confirmation. + +## Done when + +The operation is completed and verified (or explicitly stopped by the +user), and `2-operate/results.md` records what happened. diff --git a/templates/coolify-ops/steps/3-close.md b/templates/coolify-ops/steps/3-close.md new file mode 100644 index 0000000..95d19e3 --- /dev/null +++ b/templates/coolify-ops/steps/3-close.md @@ -0,0 +1,36 @@ +# Step 3: close the run + +## Purpose + +Turn the run into durable knowledge: close the run folder and fold what +was learned back into the playbooks. This is how the workflow improves +with use. + +## Input + +- The run folder so far: `0-parameters.md`, `1-sync/results.md`, + `2-operate/results.md`. +- The playbook that was followed, if any. + +## Output + +- `done/info.md` in the run folder: what was achieved, what surprised + you, and what changed in the playbooks because of this run. A run + without `done/` is open. +- A written or improved playbook in `playbooks/`. + +## How to execute + +1. Update the run folder's `index.md` status table: every step's state, + so a later session can see the run is closed. +2. Write `done/info.md`: what was asked, what was done, what the API + returned that was unexpected, and the playbook effect. +3. Playbooks: if a playbook was followed, improve it with what this run + taught. If the operation was investigated from scratch, write a new + playbook from what actually happened. Playbooks record lived + procedure, not designed procedure. + +## Done when + +`done/info.md` exists, the run index shows every step done, and the +playbook library reflects what this run learned. diff --git a/templates/coolify-ops/steps/index.md b/templates/coolify-ops/steps/index.md new file mode 100644 index 0000000..a598b4b --- /dev/null +++ b/templates/coolify-ops/steps/index.md @@ -0,0 +1,18 @@ +# Steps + +## Starting a run + +When the user requests an operation, the agent opens the run before step +1: create `runs/-/`, write `0-parameters.md` (the +requested operation, its target, who asked, when), and write the run's +`index.md` with a status table listing the three steps, all pending. +Update that table as each step finishes. See `runs/example/index.md` for +the closed-run shape. + +## The loop + +The loop, executed in order on every run: + +1. `1-sync.md`: sync the mirror against the live API, report drift. Always first, never optional. +2. `2-operate.md`: perform the requested operation, playbooks first. +3. `3-close.md`: close the run folder, write or improve the playbook.