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:
bernatsampera
2026-08-08 12:41:59 +02:00
co-authored by Claude Fable 5
parent f398e01e29
commit 63f5f4cebd
12 changed files with 422 additions and 0 deletions
+82
View File
@@ -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/`.
+63
View File
@@ -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.
+57
View File
@@ -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.
+47
View File
@@ -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.
+36
View File
@@ -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.
+18
View File
@@ -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.