A starting point for building a plugin — a small, single-purpose tool that executes actions against an API or system.
Built and maintained by the team at Kontango. Part of the schmutz-plugins collection.
A plugin is a stateless Docker container that does one job. It is driven entirely by environment variables, does its work, writes its results to a file, and exits with a meaningful status code. That's the whole contract:
flowchart LR
inputs["PLUGIN_* env vars"] --> plugin
subgraph plugin["plugin container"]
step["pick an ACTION,<br/>run it against an API / system"]
end
plugin --> code["exit code<br/>(0 = ok)"]
plugin --> env[".env<br/>(key=value outputs)"]
plugin --> json["output.json<br/>(raw response)"]
Because the container only reads env vars and writes files, anything that can run a container can call a plugin — with the exact same contract. In our world that's two ecosystems:
- Woodpecker CI — a pipeline step runs the
plugin image and passes inputs as
settings:. Woodpecker maps each setting to aPLUGIN_*environment variable automatically. - schmutz — our zero-trust enrollment/provisioning agent runs the same image to execute an action as part of standing up or maintaining a machine.
You never write ecosystem-specific code. You write actions that read PLUGIN_*
and produce output; the caller decides how to invoke the container.
All three do the identical thing — run the health action against an API. Only
the caller differs; the container and its contract are the same.
1. Woodpecker CI — inputs come from settings:. Woodpecker uppercases each
key and prefixes it with PLUGIN_, so api_url → PLUGIN_API_URL
(docs):
steps:
- name: check-api
image: ghcr.io/kontangooss/schmutz-plugin-template:latest
settings:
action: health
api_url: https://api.example.com
token:
from_secret: api_token # secrets are injected the same way2. schmutz — the agent runs the same image with the inputs as environment variables while provisioning a machine:
schmutz plugin run ghcr.io/kontangooss/schmutz-plugin-template:latest \
--set action=health \
--set api_url=https://api.example.com3. Plain docker run — the lowest common denominator, and how you develop
and debug locally:
docker run --rm \
-e PLUGIN_ACTION=health \
-e PLUGIN_API_URL=https://api.example.com \
ghcr.io/kontangooss/schmutz-plugin-template:latestThis repo is the starting point. The full step-by-step — fork, rename, define settings, write actions, test, build, publish — lives in CONTRIBUTING.md. The short version:
# 1. Start from this template
git clone https://ghcr.io/kontangooss/schmutz-plugin-template.git my-plugin
cd my-plugin && rm -rf .git && git init
# 2. Develop and test locally (see "Develop locally" below)
cp .env.example .env # then edit .env
docker compose up --build
# 3. Check it meets the quality bar
./check.sh
# 4. Build and publish (see CONTRIBUTING.md)docker-compose.yml + .env.example are a local dev/test harness — the loop
you use to build a plugin before it ever touches CI.
cp .env.example .env # copy once
$EDITOR .env # set ACTION, API_URL, auth, etc.
docker compose up --build # builds the image and runs it with your .envWarning
.env holds your real credentials and is git-ignored. Never commit it.
.env.example documents every setting the core understands (it is heavily
commented). docker compose reads your .env via env_file and passes the
values straight through as PLUGIN_* — exactly as Woodpecker or schmutz would.
| File | Purpose |
|---|---|
plugin.sh |
Your plugin. Declares settings, defines action_* functions, dispatches. Edit this. |
lib/plugin-core.sh |
Shared core — logging, settings validation, HTTP with retry, pluggable auth, output, dispatch. Reuse as-is. |
plugin.json |
Machine-readable settings/action schema. |
docs.md |
Reference docs, with the Woodpecker plugin-index metadata header. |
Dockerfile |
Builds the container image (bash entrypoint). |
.env.example |
Every setting, documented — copy to .env for local dev. |
docker-compose.yml |
Local dev/test harness. |
.woodpecker.yml |
CI pipeline that builds and publishes the image. |
check.sh |
Self-check you run before publishing (syntax, shellcheck, placeholder + secret lint). |
test.sh |
Simple local action runner. |
CONTRIBUTING.md |
The step-by-step creation guide. |
docs/QUALITY.md |
The quality bar every plugin must meet. |
Provided by lib/plugin-core.sh for free (your plugin adds its own on top). See
.env.example for the fully-commented list.
| Setting | Default | Description |
|---|---|---|
action |
— | (required) which action to run |
api_url |
— | base URL of the target API |
auth_mode |
none |
none, bearer, api_key, basic, pve |
| credential | — | the credential for your auth_mode (token, api_key, username+password, or api_token) — pass it via from_secret |
skip_verify |
false |
skip TLS verification (self-signed endpoints) |
debug |
false |
verbose logging |
retry_max |
3 |
HTTP retry attempts |
retry_delay |
2 |
seconds between retries |
http_timeout |
30 |
per-request timeout (seconds) |
Every action writes results to two files a later step can consume:
.env—export KEY='value'lines, safe tosourceoutput.json— the raw JSON response
MIT. See LICENSE.
Built by the team at Kontango