mirror of
https://github.com/bleak-ai/gcontext.git
synced 2026-08-11 13:19:23 +02:00
Add coolify-ops, the first workflow template (marketplace task 3)
Created from the lived module with the share-workflow skill: manifest, steps, seed playbook, setup command, and a fabricated example run. Staged in templates/ until the workflows API (task 3b) hosts it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f398e01e29
commit
63f5f4cebd
@@ -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
|
||||
`<instance-url>/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 `<instance-url>/api/v1` with the header
|
||||
`Authorization: Bearer <token>`. 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 `<date>-<operation-slug>`.
|
||||
|
||||
## 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/`.
|
||||
@@ -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 `<instance-url>/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 `<date>-<operation-slug>`, 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.
|
||||
@@ -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=<app_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/<deployment_uuid>` 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/<app_uuid>` 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: <deployment_uuid>`. 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 `<instance-url>/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.
|
||||
@@ -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=<app_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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Steps
|
||||
|
||||
## Starting a run
|
||||
|
||||
When the user requests an operation, the agent opens the run before step
|
||||
1: create `runs/<date>-<operation-slug>/`, 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.
|
||||
Reference in New Issue
Block a user