feat(docker-build): support Docker Hub registry alongside DOCR and GHCR (#179)

This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-08 19:33:37 -05:00
1 parent 3b3ea7ed62
commit a5eda472ea
3 files changed
+70 -5

No files matched your search

+18 -4
View File
@@ -20,7 +20,7 @@ on:
workflow_call:
inputs:
images:
description: "Image repositories to push, one per line (registry.digitalocean.com/... and/or ghcr.io/...)"
description: "Image repositories to push, one per line (registry.digitalocean.com/..., ghcr.io/... and/or docker.io/...)"
type: string
required: true
platforms:
@@ -91,10 +91,10 @@ on:
description: "DigitalOcean token or DOCR robot with write access; used when an image lives in registry.digitalocean.com"
required: false
DOCKERHUB_USERNAME:
description: "Docker Hub user for dhi.io base images; only read with dhi-login"
description: "Docker Hub user for dhi.io base images and docker.io pushes; only read when configured"
required: false
DOCKERHUB_TOKEN:
description: "Docker Hub token for dhi.io base images; only read with dhi-login"
description: "Docker Hub token for dhi.io base images and docker.io pushes; only read when configured"
permissions:
contents: read
@@ -161,6 +161,13 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
if: ${{ contains(inputs.images, 'docker.io/') }}
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
@@ -186,7 +193,7 @@ jobs:
image="${image//[[:space:]]/}"
[ -n "$image" ] || continue
case "$image" in
registry.digitalocean.com/* | ghcr.io/*) ;;
registry.digitalocean.com/* | ghcr.io/* | docker.io/*) ;;
*)
echo "error: unsupported image repository: $image" >&2
exit 1
@@ -273,6 +280,13 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
if: ${{ contains(inputs.images, 'docker.io/') }}
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
+2 -1
View File
@@ -690,7 +690,8 @@ jobs:
Builds one image per entry in `platforms`, pushes every platform by digest to each repository in
`images`, then joins those digests into a single multi-arch manifest per repository. It logs in only
where an image needs it: `registry.digitalocean.com` when a DOCR image is listed, `ghcr.io` through
the workflow token, and `dhi.io` when `dhi-login: true` and the Docker Hub secrets are provided.
the workflow token, `docker.io` when Docker Hub images are listed (using `DOCKERHUB_USERNAME` and
`DOCKERHUB_TOKEN`), and `dhi.io` when `dhi-login: true` and the Docker Hub secrets are provided.
Both craft web images publish this way. `font-preview-service` fetches its font sources into the
build context first, so it passes a `pre-build-command`; `artcraft-services` builds one platform on a
+50
View File
@@ -0,0 +1,50 @@
"""Verify the shared docker-build workflow handles Docker Hub alongside DOCR and GHCR."""
from pathlib import Path
import unittest
import yaml
ROOT = Path(__file__).resolve().parents[1]
WORKFLOW_PATH = ROOT / ".github/workflows/docker-build.yml"
WORKFLOW = yaml.load(WORKFLOW_PATH.read_text(), Loader=yaml.BaseLoader)
class DockerBuildWorkflowTests(unittest.TestCase):
def test_workflow_interface(self):
call = WORKFLOW["on"]["workflow_call"]
self.assertIn("images", call["inputs"])
self.assertIn("DOCKERHUB_USERNAME", call["secrets"])
self.assertIn("DOCKERHUB_TOKEN", call["secrets"])
def test_dockerhub_login_in_build_job(self):
steps = WORKFLOW["jobs"]["build"]["steps"]
dh_step = next(
(s for s in steps if s.get("name") == "Log in to Docker Hub"),
None,
)
self.assertIsNotNone(dh_step, "Build job missing 'Log in to Docker Hub' step")
self.assertIn("docker.io/", dh_step["if"])
self.assertEqual(dh_step["with"]["username"], "${{ secrets.DOCKERHUB_USERNAME }}")
self.assertEqual(dh_step["with"]["password"], "${{ secrets.DOCKERHUB_TOKEN }}")
def test_dockerhub_login_in_merge_job(self):
steps = WORKFLOW["jobs"]["merge"]["steps"]
dh_step = next(
(s for s in steps if s.get("name") == "Log in to Docker Hub"),
None,
)
self.assertIsNotNone(dh_step, "Merge job missing 'Log in to Docker Hub' step")
self.assertIn("docker.io/", dh_step["if"])
self.assertEqual(dh_step["with"]["username"], "${{ secrets.DOCKERHUB_USERNAME }}")
self.assertEqual(dh_step["with"]["password"], "${{ secrets.DOCKERHUB_TOKEN }}")
def test_build_step_accepts_docker_io_repository(self):
steps = WORKFLOW["jobs"]["build"]["steps"]
build_step = next(s for s in steps if s.get("name") == "Build and push by digest")
script = build_step["run"]
self.assertIn("docker.io/*", script)
self.assertIn("registry.digitalocean.com/* | ghcr.io/* | docker.io/*", script)
if __name__ == "__main__":
unittest.main()