Skip to content

Enable push-to-deploy on any server. Trigger rolling deployments with `webhook` & `docker-rollout`

License

Notifications You must be signed in to change notification settings

lotap/webhook-rollout

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

webhook-rollout

GitHub Release Docker Image Version Dependabot Updates semantic-release: conventionalcommits

webhook and docker-rollout, 2 great libraries that go great together!

A simple way to push-to-deploy on any server with docker compose

How it works

When you put webhook-rollout on a server, it listens for a webhook request on a specified port (default 9000). You can configure github to build, package, and register an app image when you commit a change. And when the build completes, you can configure github to send the webhook request to your server. From there, rollout takes over and will automatically handle pulling the newly registered image and updating your app with no downtime.

This is mostly intended for use with github actions and the github container registry, but the core implementation is workflow/registry agnostic.

Requirements

  1. A repository with automation that builds a new image on code changes. (See Push your app to the GitHub Container Registry)

  2. A Webhook that triggers when that new image is pushed to a registry. (See Set up a webhook in your GitHub Repo)

  3. A server that runs the image with Docker compose.

  4. A docker-aware reverse-proxy such as Traefik, Caddy, or nginx-proxy that can handle routing incoming traffic between containers as a new image is rolled out.

Usage

Volumes

/app is the WORKINGDIR of the image

Required

/app/compose.yaml - The compose file with your service definitions.

/var/run/docker.sock - The running Docker socket the container should attach to.

Caution

Mounting the Docker socket as a volume comes with security risks, so it is recommended to run Docker in Rootless mode if possible. In rootless mode, the socket can typically be found at /run/user/$UID/docker.sock

Optional

/var/scripts/ - A directory containing any custom hooks you would like to add

/etc/webhook/config.yaml - The configuration file for the webhook service

The image comes with a preconfigured gh-pkg-rollout.sh hook and config.yaml to handle webhooks from GitHub automatically. See Adding Custom Hooks for more information.

Secrets & Env Vars

Required (if you are using the default gh-pkg-rollout hook and config.yaml)

Type Name Description
ENV APP_IMAGE Docker image for your app (format: ghcr.io/<username>/<repo>:<tag>)
ENV APP_SERVICE_NAME Which service to apply the rollout to
SECRET WEBHOOK_SECRET Secret used for verification of the webhook

Optional

Type Name Description
ENV REGISTRY_URL Container registry URL (ghcr.io, for example) used for docker login
ENV REGISTRY_USERNAME Username for the container registry
ENV WEBHOOK_PORT Port the image listens on (default 9000)
SECRET REGISTRY_PASSWORD Password (or access token) for the container registry

Important

Since webhook-rollout runs docker compose from within the container, it needs access to any env vars that are necessary for your web app and reverse proxy. For example, in the Traefik example below, the DOMAIN is passed to the webhook-rollout service because the webapp service requires it for deployment.

If you are using a .env file, you can mount it as a volume to handle such cases. (But that may also add some unnecessary exposure of your env vars)

Configuring your compose file

Traefik Example

# compose.yaml

secrets:
  REGISTRY_PASSWORD:
    environment: "REGISTRY_PASSWORD"
  WEBHOOK_SECRET:
    environment: "WEBHOOK_SECRET"

services:
  webapp:
    image: ${APP_IMAGE:-webapp:latest}
    environment:
      APP_PORT: ${APP_PORT:-3000}
    healthcheck:
      test: test ! -f /tmp/drain && curl -f http://localhost:${APP_PORT:-3000}/healthcheck
      interval: 10s
      timeout: 2s
      retries: 3
    labels:
      - traefik.enable=true
      - traefik.http.routers.webapp.entrypoints=web
      - traefik.http.routers.webapp.rule=Host(`${DOMAIN}`)
      - traefik.http.services.webapp.loadbalancer.server.port=${APP_PORT:-3000}
      # DRAIN CONTAINER ON ROLLOUT - https://docker-rollout.wowu.dev/container-draining.html
      - docker-rollout.pre-stop-hook=touch /tmp/drain && sleep 45

  webhook-rollout:
    image: lotap/webhook-rollout
    environment:
      - APP_IMAGE=${APP_IMAGE}
      - APP_SERVICE_NAME=webapp
      - REGISTRY_URL=${REGISTRY_URL}
      - REGISTRY_USERNAME=${REGISTRY_USERNAME}
      - WEBHOOK_PORT=${WEBHOOK_PORT:-9000}
      - DOMAIN=${DOMAIN}
    secrets:
      - REGISTRY_PASSWORD
      - WEBHOOK_SECRET
    volumes:
      - ${CONTAINER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
      - ./compose.yaml:/app/compose.yaml:ro
    labels:
      - traefik.enable=true
      - traefik.http.routers.webhook.entrypoints=webhook
      - traefik.http.routers.webhook.rule=Host(`${DOMAIN:-localhost}`)
      - traefik.http.services.webhook.loadbalancer.server.port=${WEBHOOK_PORT:-9000}

  traefik:
    image: traefik
    command:
      # ENTRY
      - --entryPoints.web.address=:80
      - --entryPoints.webhook.address=:${WEBHOOK_PORT:-9000}
      # PROVIDER
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
    ports:
      - 80:80 # HTTP
      - ${WEBHOOK_PORT:-9000}:${WEBHOOK_PORT:-9000} # Webhook
    volumes:
      - ${CONTAINER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro
SSL Certs

If you are using Traefik to handle a cert, you will need to share access to it with the webhook-rollout service.

webhook-rollout:
  # ...
  volumes:
    # ...
    - ./acme:/app/acme

traefik:
  # ...
  command:
    # ...
    - --certificatesresolvers.app-resolver.acme.storage=/acme/acme.json
  volumes:
    # ...
    - ./acme:/app/acme

Adding Custom Hooks

webhook-rollout comes pre-configured with a single script that will work for most people. If you need additional functionality, you will need to update code in two places and mount them as volumes:

  1. The actual script(s). Mounted to /var/scripts/

  2. The configuration file. Mounted to /etc/webhook/config.yaml. This acts as a place to register the scripts and add rules for their execution. Configuration details can be found in the webhook readme and docs. You can find the default config for this project at root/etc/webhook/config.yaml. Note that this project expects the .yaml extension. You will need to provide a custom entry script to use .json. See root/usr/local/bin/entrypoint.sh for details.

Important

Any binaries you need for custom hooks will need to be installed or added to the Dockerfile by cloning/forking and building a custom image.

Example Custom Hook

#!/usr/bin/env sh
set -e

# install jq
if ! command -v jq > /dev/null 2>&1; then
    apk add --no-cache jq
fi

# grab data from an endpoint
RES=$(wget -qO- https://jsonplaceholder.typicode.com/posts/1)

# log the json data
echo "$RES" | jq .

exit 0

GitHub Configuration

Push your app to the GitHub Container Registry

Creating an image automatically is relatively simple to set up with a github action. You can see this project's action to do so at .github/workflows/publish.yaml

Here is a simple example for a generic web app that creates an image when a change to apps/web is made on the trunk branch:

name: Build & Publish @/apps/web

on:
  push:
    branches: [trunk]
    paths: ["apps/web/**"]
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}-web

concurrency:
  group: web
  cancel-in-progress: true

permissions:
  contents: read
  packages: write
  attestations: write
  id-token: write

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout your repository using git
        uses: actions/checkout@v5

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        id: push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./apps/web/Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

The code above is based on the github documentation found here.

More information about the github container registry can be found here

Set up a webhook in your GitHub Repo

  1. Navigate to your repo's settings page and select webhooks from the sidebar
Screenshot 2025-09-15 at 2 37 18 PM
  1. Click "Add webhook"
Screenshot 2025-09-15 at 2 37 30 PM
  1. Configure the settings. Use application/json and securely generate a secret with openssl rand -base64 32 or something similar to paste into the secret. Make sure that secret is also made available as a environment variable for your web app.
Screenshot 2025-09-15 at 2 36 15 PM
  1. Select the "packages" event
Screenshot 2025-09-15 at 2 34 11 PM
  1. Activate the webhook and begin testing. You should be all set!

Securing your webhook

It's recommended to limit connections to your webhook port to prevent malicious activity, much like you would for ssh. If you are using ufw, you can run ufw limit 9000/tcp.

About

Enable push-to-deploy on any server. Trigger rolling deployments with `webhook` & `docker-rollout`

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 3

  •  
  •  
  •