├── .github ├── CODEOWNERS ├── FUNDING.yml ├── docker-healthchecks.jpg ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature.yml │ └── bug.yml ├── workflows │ ├── labels.yml │ ├── test.yml │ └── build.yml ├── labels.yml └── SUPPORT.md ├── examples ├── compose │ ├── .env │ ├── msmtpd.env │ ├── healthchecks.env │ └── compose.yml ├── traefik │ ├── .env │ ├── README.md │ ├── msmtpd.env │ ├── healthchecks.env │ └── compose.yml └── mysql │ ├── .env │ ├── msmtpd.env │ ├── healthchecks.env │ └── compose.yml ├── .gitattributes ├── rootfs ├── tpl │ └── img │ │ ├── logo.png │ │ ├── logo@2x.png │ │ ├── logo-full.png │ │ ├── logo-full@2x.png │ │ ├── apple-touch-180.png │ │ └── logo-512-green.png ├── etc │ └── cont-init.d │ │ ├── 00-fix-logs.sh │ │ ├── 02-fix-perms.sh │ │ ├── 04-svc-main.sh │ │ ├── 01-fix-uidgid.sh │ │ └── 03-config.sh └── opt │ └── healthchecks │ └── uwsgi.ini ├── test ├── healthchecks.env └── compose.yml ├── .editorconfig ├── docker-bake.hcl ├── LICENSE ├── CHANGELOG.md ├── Dockerfile └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @crazy-max 2 | -------------------------------------------------------------------------------- /examples/compose/.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | -------------------------------------------------------------------------------- /examples/traefik/.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: crazy-max 2 | custom: https://www.paypal.me/crazyws 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /*.sh linguist-detectable=false 2 | /rootfs/** linguist-detectable=false 3 | -------------------------------------------------------------------------------- /examples/mysql/.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | 3 | MYSQL_DATABASE=hc 4 | MYSQL_USER=hc 5 | MYSQL_PASSWORD=hc 6 | -------------------------------------------------------------------------------- /rootfs/tpl/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/logo.png -------------------------------------------------------------------------------- /rootfs/tpl/img/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/logo@2x.png -------------------------------------------------------------------------------- /rootfs/tpl/img/logo-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/logo-full.png -------------------------------------------------------------------------------- /.github/docker-healthchecks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/.github/docker-healthchecks.jpg -------------------------------------------------------------------------------- /rootfs/tpl/img/logo-full@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/logo-full@2x.png -------------------------------------------------------------------------------- /rootfs/tpl/img/apple-touch-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/apple-touch-180.png -------------------------------------------------------------------------------- /rootfs/tpl/img/logo-512-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-healthchecks/HEAD/rootfs/tpl/img/logo-512-green.png -------------------------------------------------------------------------------- /examples/traefik/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | ```bash 4 | touch acme.json 5 | chmod 600 acme.json 6 | docker compose up -d 7 | docker compose logs -f 8 | ``` 9 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/00-fix-logs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | # Fix access rights to stdout and stderr 4 | chown ${PUID}:${PGID} /proc/self/fd/1 /proc/self/fd/2 || true 5 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/02-fix-perms.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | echo "Fixing perms..." 4 | mkdir -p /data/img 5 | chown -R healthchecks. \ 6 | /data \ 7 | /opt/healthchecks \ 8 | /tpl 9 | -------------------------------------------------------------------------------- /test/healthchecks.env: -------------------------------------------------------------------------------- 1 | PUID=1000 2 | PGID=1000 3 | 4 | SECRET_KEY=5up3rS3kr1t 5 | SITE_ROOT=http://healthchecks.example.com:8000 6 | SITE_NAME=Healthchecks 7 | ALLOWED_HOSTS=* 8 | 9 | SUPERUSER_EMAIL=healthchecks@example.com 10 | SUPERUSER_PASSWORD=pwd 11 | -------------------------------------------------------------------------------- /examples/compose/msmtpd.env: -------------------------------------------------------------------------------- 1 | # https://github.com/crazy-max/docker-msmtpd 2 | SMTP_HOST=smtp.gmail.com 3 | SMTP_PORT=587 4 | SMTP_TLS=on 5 | SMTP_STARTTLS=on 6 | SMTP_TLS_CHECKCERT=on 7 | SMTP_AUTH=on 8 | SMTP_USER=foo 9 | SMTP_PASSWORD=bar 10 | SMTP_FROM=foo@gmail.com 11 | -------------------------------------------------------------------------------- /examples/mysql/msmtpd.env: -------------------------------------------------------------------------------- 1 | # https://github.com/crazy-max/docker-msmtpd 2 | SMTP_HOST=smtp.gmail.com 3 | SMTP_PORT=587 4 | SMTP_TLS=on 5 | SMTP_STARTTLS=on 6 | SMTP_TLS_CHECKCERT=on 7 | SMTP_AUTH=on 8 | SMTP_USER=foo 9 | SMTP_PASSWORD=bar 10 | SMTP_FROM=foo@gmail.com 11 | -------------------------------------------------------------------------------- /examples/traefik/msmtpd.env: -------------------------------------------------------------------------------- 1 | # https://github.com/crazy-max/docker-msmtpd 2 | SMTP_HOST=smtp.gmail.com 3 | SMTP_PORT=587 4 | SMTP_TLS=on 5 | SMTP_STARTTLS=on 6 | SMTP_TLS_CHECKCERT=on 7 | SMTP_AUTH=on 8 | SMTP_USER=foo 9 | SMTP_PASSWORD=bar 10 | SMTP_FROM=foo@gmail.com 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | time: "08:00" 8 | timezone: "Europe/Paris" 9 | labels: 10 | - "kind/dependencies" 11 | - "bot" 12 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/04-svc-main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | mkdir -p /etc/services.d/healthchecks 4 | cat > /etc/services.d/healthchecks/run <&2 "error: both $var and $fileVar are set (but are exclusive)" 14 | exit 1 15 | fi 16 | local val="$def" 17 | if [ "${!var:-}" ]; then 18 | val="${!var}" 19 | elif [ "${!fileVar:-}" ]; then 20 | val="$(< "${!fileVar}")" 21 | fi 22 | export "$var"="$val" 23 | unset "$fileVar" 24 | } 25 | 26 | TZ=${TZ:-UTC} 27 | USE_OFFICIAL_LOGO=${USE_OFFICIAL_LOGO:-false} 28 | DB_TIMEOUT=${DB_TIMEOUT:-60} 29 | 30 | # Timezone 31 | echo "Setting timezone to ${TZ}..." 32 | ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime 33 | echo ${TZ} > /etc/timezone 34 | 35 | # Settings 36 | echo "Setting Healthchecks configuration..." 37 | sed -i "s|DEBUG = .*|DEBUG = False|g" /opt/healthchecks/hc/settings.py 38 | sed -i "s|TIME_ZONE = .*|TIME_ZONE = \"${TZ}\"|g" /opt/healthchecks/hc/settings.py 39 | 40 | # DB 41 | echo "Checking database..." 42 | if [ -z "$DB" ]; then 43 | >&2 echo "ERROR: DB must be defined" 44 | exit 1 45 | fi 46 | if [ "$DB" = "mysql" ]; then 47 | echo "Use MySQL database" 48 | if [ -z "${DB_HOST}" ]; then 49 | >&2 echo "ERROR: DB_HOST must be defined" 50 | exit 1 51 | fi 52 | if [ -z "${DB_NAME}" ]; then 53 | >&2 echo "ERROR: DB_NAME must be defined" 54 | exit 1 55 | fi 56 | if [ -z "${DB_USER}" ]; then 57 | >&2 echo "ERROR: DB_USER must be defined" 58 | exit 1 59 | fi 60 | 61 | DB_CMD="mysql -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USER} "-p${DB_PASSWORD}"" 62 | #echo "DB_CMD=$DB_CMD" 63 | 64 | echo "Waiting ${DB_TIMEOUT}s for MySQL database to be ready..." 65 | counter=1 66 | while ! ${DB_CMD} -e "show databases;" > /dev/null 2>&1; do 67 | sleep 1 68 | counter=$((counter + 1)) 69 | if [ ${counter} -gt "${DB_TIMEOUT}" ]; then 70 | >&2 echo "ERROR: Failed to connect to MySQL database on $DB_HOST" 71 | exit 1 72 | fi; 73 | done 74 | echo "MySQL database ready!" 75 | elif [ "$DB" = "postgres" ]; then 76 | echo "Use PostgreSQL database" 77 | if [ -z "${DB_HOST}" ]; then 78 | >&2 echo "ERROR: DB_HOST must be defined" 79 | exit 1 80 | fi 81 | if [ -z "${DB_NAME}" ]; then 82 | >&2 echo "ERROR: DB_NAME must be defined" 83 | exit 1 84 | fi 85 | if [ -z "${DB_USER}" ]; then 86 | >&2 echo "ERROR: DB_USER must be defined" 87 | exit 1 88 | fi 89 | 90 | DB_CMD="psql --host=${DB_HOST} --port=${DB_PORT} --username=${DB_USER} -lqt" 91 | #echo "DB_CMD=$DB_CMD" 92 | 93 | echo "Waiting ${DB_TIMEOUT}s for database to be ready..." 94 | counter=1 95 | while ${DB_CMD} | cut -d \| -f 1 | grep -qw "${DB_NAME}" > /dev/null 2>&1; [ $? -ne 0 ]; do 96 | sleep 1 97 | counter=$((counter + 1)) 98 | if [ ${counter} -gt "${DB_TIMEOUT}" ]; then 99 | >&2 echo "ERROR: Failed to connect to PostgreSQL database on $DB_HOST" 100 | exit 1 101 | fi; 102 | done 103 | echo "PostgreSQL database ready!" 104 | elif [ "$DB" = "sqlite" ]; then 105 | echo "Use SQLite database" 106 | if [ -z "${DB_NAME}" ]; then 107 | >&2 echo "ERROR: DB_NAME must be defined" 108 | exit 1 109 | fi 110 | if [ ! -f "/opt/healthchecks/hc.sqlite" ]; then 111 | yasu healthchecks:healthchecks ln -sf /data/hc.sqlite /opt/healthchecks/hc.sqlite 112 | fi 113 | else 114 | >&2 echo "ERROR: Unknown database type: $DB" 115 | exit 1 116 | fi 117 | 118 | # Migrate database 119 | echo "Migrating database..." 120 | yasu healthchecks:healthchecks python /opt/healthchecks/manage.py migrate --noinput 121 | 122 | # Create superuser 123 | file_env 'SUPERUSER_PASSWORD' 124 | if [ -n "$SUPERUSER_EMAIL" ] && [ -n "$SUPERUSER_PASSWORD" ]; then 125 | cat << EOF | yasu healthchecks:healthchecks python /opt/healthchecks/manage.py shell 126 | from django.contrib.auth.models import User; 127 | username = 'su'; 128 | password = '$SUPERUSER_PASSWORD'; 129 | email = '$SUPERUSER_EMAIL'; 130 | if User.objects.filter(username=username).count()==0: 131 | User.objects.create_superuser(username, email, password); 132 | print('Superuser created successfully!'); 133 | else: 134 | print('Superuser already exists.'); 135 | EOF 136 | fi 137 | 138 | if [ "${USE_OFFICIAL_LOGO}" = "true" ]; then 139 | echo "Using official branding logo..." 140 | yasu healthchecks:healthchecks cp -f /tpl/img/* /opt/healthchecks/static/img/ 141 | fi 142 | 143 | if [ "$(ls -A /data/img)" ]; then 144 | echo "Copying /data/img to /opt/healthchecks/static/img..." 145 | yasu healthchecks:healthchecks cp -rf /data/img/* /opt/healthchecks/static/img/ 146 | fi 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Latest Version 5 | Build Status 6 | Docker Stars 7 | Docker Pulls 8 |
Become a sponsor 9 | Donate Paypal 10 |

11 | 12 | 13 | ## ⚠️ Abandoned project 14 | 15 | This project is not maintained anymore and is abandoned. Feel free to fork and 16 | make your own changes, or you can switch to the [official image](https://hub.docker.com/r/healthchecks/healthchecks). 17 | 18 | ## About 19 | 20 | Docker image for [Healthchecks](https://github.com/healthchecks/healthchecks), 21 | a cron monitoring tool. 22 | 23 | > [!TIP] 24 | > Want to be notified of new releases? Check out 🔔 [Diun (Docker Image Update Notifier)](https://github.com/crazy-max/diun) 25 | > project! 26 | 27 | ___ 28 | 29 | * [Features](#features) 30 | * [Build locally](#build-locally) 31 | * [Image](#image) 32 | * [Environment variables](#environment-variables) 33 | * [Ports](#ports) 34 | * [Usage](#usage) 35 | * [Docker Compose](#docker-compose) 36 | * [Command line](#command-line) 37 | * [Upgrade](#upgrade) 38 | * [Contributing](#contributing) 39 | * [License](#license) 40 | 41 | ## Features 42 | 43 | * Run as non-root user 44 | * Multi-platform image 45 | * [Traefik](https://github.com/containous/traefik-library-image) as reverse proxy and creation/renewal of Let's Encrypt certificates (see [this template](examples/traefik)) 46 | 47 | ## Build locally 48 | 49 | ```shell 50 | git clone https://github.com/crazy-max/docker-healthchecks.git 51 | cd docker-healthchecks 52 | 53 | # Build image and output to docker (default) 54 | docker buildx bake 55 | 56 | # Build multi-platform image 57 | docker buildx bake image-all 58 | ``` 59 | 60 | ## Image 61 | 62 | | Registry | Image | 63 | |--------------------------------------------------------------------------------------------------|---------------------------------| 64 | | [Docker Hub](https://hub.docker.com/r/crazymax/healthchecks/) | `crazymax/healthchecks` | 65 | | [GitHub Container Registry](https://github.com/users/crazy-max/packages/container/package/healthchecks) | `ghcr.io/crazy-max/healthchecks` | 66 | 67 | Following platforms for this image are available: 68 | 69 | ``` 70 | $ docker buildx imagetools inspect crazymax/healthchecks --format "{{json .Manifest}}" | \ 71 | jq -r '.manifests[] | select(.platform.os != null and .platform.os != "unknown") | .platform | "\(.os)/\(.architecture)\(if .variant then "/" + .variant else "" end)"' 72 | 73 | linux/amd64 74 | linux/arm/v7 75 | linux/arm64 76 | ``` 77 | 78 | ## Environment variables 79 | 80 | * `TZ`: The timezone assigned to the container (default `UTC`) 81 | * `PUID`: Process UID (default `1000`) 82 | * `PGID`: Process GID (default `1000`) 83 | * `SUPERUSER_EMAIL`: Superuser email to access [admin panel](https://github.com/healthchecks/healthchecks#accessing-administration-panel) 84 | * `SUPERUSER_PASSWORD`: Superuser password 85 | * `USE_OFFICIAL_LOGO`: Replace generic logo with official branding (default `false`) 86 | 87 | To configure the application, you just add the environment variables as shown in the 88 | [Configuration page](https://github.com/healthchecks/healthchecks#configuration) of Healthchecks Project. 89 | 90 | > 💡 `SUPERUSER_PASSWORD_FILE` can be used to fill in the value from a file, especially for Docker's secrets feature. 91 | 92 | ## Volumes 93 | 94 | * `/data`: Contains SQLite database and static images folder 95 | 96 | > :warning: Note that the volumes should be owned by the user/group with the specified `PUID` and `PGID`. If you don't 97 | > give the volume correct permissions, the container may not start. 98 | 99 | ## Ports 100 | 101 | * `2500`: [Healthchecks SMTP](https://github.com/healthchecks/healthchecks#receiving-emails) listener service 102 | * `8000`: HTTP port 103 | 104 | ## Usage 105 | 106 | ### Docker Compose 107 | 108 | Docker compose is the recommended way to run this image. You can use the following 109 | [docker compose template](examples/compose/compose.yml), then run the container: 110 | 111 | ```bash 112 | docker compose up -d 113 | docker compose logs -f 114 | ``` 115 | 116 | ### Command line 117 | 118 | You can also use the following minimal command: 119 | 120 | ```bash 121 | $ docker run -d -p 8000:8000 --name healthchecks \ 122 | -e "TZ=Europe/Paris" \ 123 | -e "SECRET_KEY=5up3rS3kr1t" \ 124 | -e "DB=sqlite" \ 125 | -e "DB_NAME=/data/hc.sqlite" \ 126 | -e "ALLOWED_HOSTS=*" \ 127 | -v $(pwd)/data:/data \ 128 | crazymax/healthchecks:latest 129 | ``` 130 | 131 | ## Upgrade 132 | 133 | Recreate the container whenever I push an update: 134 | 135 | ```bash 136 | docker compose pull 137 | docker compose up -d 138 | ``` 139 | 140 | ## Contributing 141 | 142 | Want to contribute? Awesome! The most basic way to show your support is to star 143 | the project, or to raise issues. You can also support this project by [**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) 144 | or by making a [PayPal donation](https://www.paypal.me/crazyws) to ensure this 145 | journey continues indefinitely! 146 | 147 | Thanks again for your support, it is much appreciated! :pray: 148 | 149 | ## License 150 | 151 | MIT. See `LICENSE` for more details. 152 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | schedule: 9 | - cron: '0 8 */6 * *' # every 6 days to keep cache 10 | push: 11 | branches: 12 | - 'master' 13 | tags: 14 | - '*' 15 | paths-ignore: 16 | - '**.md' 17 | pull_request: 18 | paths-ignore: 19 | - '**.md' 20 | 21 | env: 22 | DOCKERHUB_SLUG: crazymax/healthchecks 23 | GHCR_SLUG: ghcr.io/crazy-max/healthchecks 24 | 25 | jobs: 26 | prepare: 27 | runs-on: ubuntu-latest 28 | outputs: 29 | matrix: ${{ steps.platforms.outputs.matrix }} 30 | steps: 31 | - 32 | name: Checkout 33 | uses: actions/checkout@v4 34 | - 35 | name: Create matrix 36 | id: platforms 37 | run: | 38 | echo "matrix=$(docker buildx bake image-all --print | jq -cr '.target."image-all".platforms')" >>${GITHUB_OUTPUT} 39 | - 40 | name: Show matrix 41 | run: | 42 | echo ${{ steps.platforms.outputs.matrix }} 43 | - 44 | name: Docker meta 45 | id: meta 46 | uses: docker/metadata-action@v5 47 | with: 48 | images: | 49 | ${{ env.DOCKERHUB_SLUG }} 50 | ${{ env.GHCR_SLUG }} 51 | tags: | 52 | type=match,pattern=(.*)-r,group=1 53 | type=ref,event=pr 54 | type=edge 55 | labels: | 56 | org.opencontainers.image.title=Healthchecks 57 | org.opencontainers.image.description=Cron Monitoring Tool 58 | org.opencontainers.image.vendor=CrazyMax 59 | - 60 | name: Rename meta bake definition file 61 | run: | 62 | mv "${{ steps.meta.outputs.bake-file }}" "/tmp/bake-meta.json" 63 | - 64 | name: Upload meta bake definition 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: bake-meta 68 | path: /tmp/bake-meta.json 69 | if-no-files-found: error 70 | retention-days: 1 71 | 72 | build: 73 | runs-on: ubuntu-latest 74 | timeout-minutes: 480 75 | needs: 76 | - prepare 77 | strategy: 78 | fail-fast: false 79 | matrix: 80 | platform: ${{ fromJson(needs.prepare.outputs.matrix) }} 81 | steps: 82 | - 83 | name: Prepare 84 | run: | 85 | platform=${{ matrix.platform }} 86 | echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV 87 | - 88 | name: Checkout 89 | uses: actions/checkout@v4 90 | - 91 | name: Download meta bake definition 92 | uses: actions/download-artifact@v4 93 | with: 94 | name: bake-meta 95 | path: /tmp 96 | - 97 | name: Set up QEMU 98 | uses: docker/setup-qemu-action@v3 99 | - 100 | name: Set up Docker Buildx 101 | uses: docker/setup-buildx-action@v3 102 | with: 103 | buildkitd-flags: --debug 104 | - 105 | name: Login to DockerHub 106 | if: github.event_name != 'pull_request' 107 | uses: docker/login-action@v3 108 | with: 109 | username: ${{ secrets.DOCKER_USERNAME }} 110 | password: ${{ secrets.DOCKER_PASSWORD }} 111 | - 112 | name: Login to GHCR 113 | if: github.event_name != 'pull_request' 114 | uses: docker/login-action@v3 115 | with: 116 | registry: ghcr.io 117 | username: ${{ github.repository_owner }} 118 | password: ${{ secrets.GITHUB_TOKEN }} 119 | - 120 | name: Build 121 | id: bake 122 | uses: docker/bake-action@v5 123 | with: 124 | files: | 125 | ./docker-bake.hcl 126 | /tmp/bake-meta.json 127 | targets: image 128 | set: | 129 | *.tags= 130 | *.platform=${{ matrix.platform }} 131 | *.cache-from=type=gha,scope=build-${{ env.PLATFORM_PAIR }} 132 | *.cache-to=type=gha,scope=build-${{ env.PLATFORM_PAIR }},mode=max 133 | *.output=type=image,"name=${{ env.DOCKERHUB_SLUG }},${{ env.GHCR_SLUG }}",push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} 134 | - 135 | name: Export digest 136 | run: | 137 | mkdir -p /tmp/digests 138 | digest="${{ fromJSON(steps.bake.outputs.metadata).image['containerimage.digest'] }}" 139 | touch "/tmp/digests/${digest#sha256:}" 140 | - 141 | name: Upload digest 142 | uses: actions/upload-artifact@v4 143 | with: 144 | name: digests-${{ env.PLATFORM_PAIR }} 145 | path: /tmp/digests/* 146 | if-no-files-found: error 147 | retention-days: 1 148 | 149 | merge: 150 | runs-on: ubuntu-latest 151 | if: github.event_name != 'pull_request' 152 | needs: 153 | - build 154 | steps: 155 | - 156 | name: Download meta bake definition 157 | uses: actions/download-artifact@v4 158 | with: 159 | name: bake-meta 160 | path: /tmp 161 | - 162 | name: Download digests 163 | uses: actions/download-artifact@v4 164 | with: 165 | path: /tmp/digests 166 | pattern: digests-* 167 | merge-multiple: true 168 | - 169 | name: Set up Docker Buildx 170 | uses: docker/setup-buildx-action@v3 171 | - 172 | name: Login to DockerHub 173 | uses: docker/login-action@v3 174 | with: 175 | username: ${{ secrets.DOCKER_USERNAME }} 176 | password: ${{ secrets.DOCKER_PASSWORD }} 177 | - 178 | name: Login to GHCR 179 | uses: docker/login-action@v3 180 | with: 181 | registry: ghcr.io 182 | username: ${{ github.repository_owner }} 183 | password: ${{ secrets.GITHUB_TOKEN }} 184 | - 185 | name: Create manifest list and push 186 | working-directory: /tmp/digests 187 | run: | 188 | docker buildx imagetools create $(jq -cr '.target."docker-metadata-action".tags | map(select(startswith("${{ env.DOCKERHUB_SLUG }}")) | "-t " + .) | join(" ")' /tmp/bake-meta.json) \ 189 | $(printf '${{ env.DOCKERHUB_SLUG }}@sha256:%s ' *) 190 | docker buildx imagetools create $(jq -cr '.target."docker-metadata-action".tags | map(select(startswith("${{ env.GHCR_SLUG }}")) | "-t " + .) | join(" ")' /tmp/bake-meta.json) \ 191 | $(printf '${{ env.GHCR_SLUG }}@sha256:%s ' *) 192 | - 193 | name: Inspect image 194 | run: | 195 | tag=$(jq -r '.target."docker-metadata-action".args.DOCKER_META_VERSION' /tmp/bake-meta.json) 196 | docker buildx imagetools inspect ${{ env.DOCKERHUB_SLUG }}:${tag} 197 | docker buildx imagetools inspect ${{ env.GHCR_SLUG }}:${tag} 198 | --------------------------------------------------------------------------------