A tiny, self-hosted webhook that push-to-deploys Docker Swarm services — no SSH, no exposed Docker API, no CI runner on your infra.
- CI builds and pushes an image, e.g.
ghcr.io/yourorg/api:abc123. - CI POSTs
{"image": "ghcr.io/yourorg/api:abc123", "version": "1.4.0"}to Swarmhook's/deployendpoint, with a bearer token.versionis optional and, if given, is included in the Slack notification. - Swarmhook finds every Swarm service labeled
autodeploy.enabled=truethat's currently running an image from that same repository (any tag), and runsdocker service update --image ...on it. - Everything else is left untouched. Swarmhook never touches
docker-compose.ymland never runsdocker stack deploy. - Optionally posts a summary to Slack.
1. Build and push the image (must be run from a git clone — the binary's version is stamped in automatically from git):
docker build -t ghcr.io/yourorg/swarmhook:latest .
docker push ghcr.io/yourorg/swarmhook:latest2. Create the required secrets on your Swarm manager:
openssl rand -hex 32 # this becomes your WEBHOOK_SECRET env var below
echo "yourdockerhubusername" | docker secret create dockerhub_username -
echo "yourdockerhubtoken" | docker secret create dockerhub_password -
echo "https://hooks.slack.com/services/..." | docker secret create slack_webhook_url - # optional3. Deploy the stack — see examples/docker-compose.yml:
export WEBHOOK_SECRET=<the token you generated above>
docker stack deploy -c docker-compose.yml myapp4. Label the services you want managed:
services:
api:
image: ghcr.io/yourorg/api:latest
deploy:
labels:
- "autodeploy.enabled=true"5. Trigger a deploy from CI:
curl -sf -X POST https://deploy.yourdomain.com/deploy \
-H "Authorization: Bearer $WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d "{\"image\": \"ghcr.io/yourorg/api:$GIT_SHA\", \"version\": \"$SERVICE_VERSION\"}"POST /deploy— body: JSON{"image": "registry/repo:tag", "version": "1.4.0"}.versionis optional and, if provided, is included in the Slack notification. RequiresAuthorization: Bearer <WEBHOOK_SECRET>.GET /healthz— returnsenvironmentandversion. No auth required.
| Env var | Required | Description |
|---|---|---|
WEBHOOK_SECRET |
yes | Bearer token required on every /deploy request. |
ENVIRONMENT |
no | Free-form label shown in logs and Slack. |
SLACK_WEBHOOK_URL |
no | Slack Incoming Webhook URL; omit to disable Slack. |
- Put Swarmhook behind a reverse proxy with TLS — never expose it over plain HTTP.
- Registry credentials are passed as Docker secrets, never plain env vars.
- Swarmhook needs
/var/run/docker.sockmounted, which is root-equivalent control of the host — restrict who can trigger deploys and keep the webhook secret out of logs.
MIT — see LICENSE.