A lightweight, minimal webhook container
Docker images are available from both GitHub Container Registry (GHCR) and Docker Hub.
If you would prefer to pull from GHCR, simply replace thecatlady/webhook with ghcr.io/thecatlady/webhook in the examples below.
Add the following volume and service definitions to a docker-compose.yml file:
services:
  webhook:
    image: thecatlady/webhook
    container_name: webhook
    command: -verbose -hooks=hooks.yml -hotreload
    environment:
      - TZ=America/New_York #optional
    volumes:
      - /path/to/appdata/config:/config:ro
    ports:
      - 9000:9000
    restart: alwaysThen, run the following command from the directory containing your docker-compose.yml file:
docker-compose up -dRun the following command to create the container:
docker run -d \
  --name=webhook \
  -e TZ=America/New_York `#optional` \
  -v /path/to/appdata/config:/config:ro \
  -p 9000:9000 \
  --restart always \
  thecatlady/webhook \
  -verbose -hooks=hooks.yml -hotreloadThe process to update the container when a new image is available is dependent on how you set it up initially.
Run the following commands from the directory containing your docker-compose.yml file:
docker-compose pull webhook
docker-compose up -d
docker image pruneRun the commands below, followed by your original docker run command:
docker stop webhook
docker rm webhook
docker pull thecatlady/webhook
docker image pruneThe container image is configured using the following parameters passed at runtime:
| Parameter | Function | 
|---|---|
-e TZ= | 
TZ database name of system time zone; e.g., America/New_York | 
-v /path/to/appdata/config:/config:ro | 
Container data directory (mounted as read-only); your JSON/YAML hook definition file should be placed in this folder (Replace /path/to/appdata/config with the desired path on your host) | 
-p 9000:9000 | 
Expose port 9000(Necessary unless only accessing webhook via other containers in the same Docker network) | 
--restart | 
Container restart policy ( always or unless-stopped recommended) | 
-verbose -hooks=/config/hooks.yml -hotreload | 
webhook parameters; replace hooks.yml with the name of your JSON/YAML hook definition file, and add/modify/remove arguments to suit your needs(Can omit if using this exact configuration; otherwise, all parameters must be specified, not just those modified)  | 
See adnanh/webhook for documentation on how to define hooks.
- The webhook processes inside the container to run as 
rootso things like permissions need to be accounted for - The image includes 
sh(shell) to execute commands/scripts (it does not includebash) 
You can set your execute-command to be a shell script that checks if any of the commands required exist, and if not installs them. Check and then install is useful because it will check and install when the first webhook request is received after creating the container, but not reinstall every time a webhook is received.
Here is an example of using webhook with git to retrieve the latest commit changes for a repository:
Example docker-compose.yml
services:
  webhook:
    image: thecatlady/webhook
    container_name: webhook
    command: -verbose -hooks=hooks.yml -hotreload
    environment:
      - TZ=America/New_York #optional
    volumes:
      - /path/to/appdata/config:/config:ro
      - /path/to/parent/git_folder:/opt/git
      - /path/to/.ssh:/root/.ssh:ro
    ports:
      - 9000:9000
    restart: alwaysIn the above:
/path/to/parent/git_folderis one folder level above where the git repos exist (ex:/home/myuser/git/which contains multiple repos), you can mount your git repos however works for you/path/to/.sshin this example is/home/myuser/.sshwhich contains a deploy key such asid_ed25519orid_rsa
Example hooks.json (placed at /path/to/appdata/config/hooks.json)
[
  {
    "id": "my-hook-name",
    "execute-command": "/config/run/git-checkout-force.sh",
    "command-working-directory": "/opt/git/myrepo",
    "include-command-output-in-response": true,
    "include-command-output-in-response-on-error": true,
    "pass-arguments-to-command": [
      { "source": "payload", "name": "head_commit.id", "comment": "GIT_REF" },
      {
        "source": "string",
        "name": "/opt/git/myrepo",
        "comment": "GIT_DIR"
      },
      { "source": "string", "name": "1000", "comment": "PUID" },
      { "source": "string", "name": "1000", "comment": "PGID" }
    ],
    "trigger-rule": {
      "and": [
        {
          "match": {
            "type": "payload-hmac-sha1",
            "secret": "<YOUR_GITHUB_WEBHOOK_SECRET>",
            "parameter": { "source": "header", "name": "X-Hub-Signature" }
          }
        },
        {
          "match": {
            "type": "value",
            "value": "refs/heads/main",
            "parameter": { "source": "payload", "name": "ref" }
          }
        }
      ]
    }
  }
]In the above:
- The branch is expected to be 
main, you might need it to bemasteror something else - The 
command-working-directoryis pointed at where the container will see the repo folder (mounted inside the container) - The elements inside 
pass-arguments-to-commandwill be passed to theexecute-commandas parameters (see below) 
Example git-checkout-force.sh (placed at /path/to/appdata/config/run/git-checkout-force.sh)
#!/usr/bin/env sh
# variables
GIT_REF=${1}
GIT_DIR=${2}
PUID=${3}
PGID=${4}
# log date
date
# install git
if ! command -v git > /dev/null 2>&1; then
    apk add --no-cache \
        git
fi
# install openssh
if ! command -v ssh > /dev/null 2>&1; then
    apk add --no-cache \
        openssh
fi
# allow git with different ownership
git config --global --add safe.directory ${GIT_DIR}
# fetch from git
git fetch --all
# checkout git reference
git checkout --force ${GIT_REF}
# set ownership
chown -R ${PUID}:${PGID} ${GIT_DIR}In the above:
GIT_REFis the first argument, passed in whenwebhookruns the script. It should be the commit id (seehooks.json above)PUIDandPGIDare the second/third arguments, passed in, later used tochown. Passing these from webhook allows multiple hooks with to be setup resulting in different file ownership in each working directory.- Log the date, just for the sake of it
 - Check if the 
gitcommand exists. If not, install it usingapk(the alpine package manager included in the base OS of the image) - Check if the 
sshcommand exists. If not, install it git config --global --add safe.directory ${GIT_DIR}newer versions ofgitcare about who owns the files in the repository, so tellgitthis directory is safe to run the rest of the commands (sincerootis the user runningwebhookin the container). This can result inrootbeing the owner of newly added or changed files, which we will handle belowgit fetch --allto retrieve the latest changes from your git repositorygit checkout --force ${GIT_REF}force checkout the referenced commit (passed in argument)chown -R ${PUID}:${PGID} ${GIT_DIR}set the ownership of all the files in the repo using thePUIDandPGIDthat are passed to the script fromhooks.json
Important takeaways:
webhookruns in the container as root- all commands 
webhookexecutes via yourhooks.jsonexecute as root - you (may) need to 
chownat the end of your script so thatrootis not the owner of your files - the image does not include much tooling (ex: 
gitorssh) but you can install the tools you need in your script - when installing tooling, check if it exists before installing, so that you're not reinstalling every time you receive a webhook request
 
Show your support by starring this project! 🌟 Pull requests, bug reports, and feature requests are also welcome!
You can also support me by becoming a GitHub sponsor or making a one-time donation 💖