Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ on:
description: "Generate & sign a keyless SLSA build provenance attestation for the pushed image (the CALLER must grant id-token: write and attestations: write)"
default: false
type: boolean
dockerhub-login:
description: "Log in to Docker Hub before the build, to avoid anonymous pull rate limits on base images. Ignored when `push: true` and `registry` already points at Docker Hub, since the push login covers that host"
default: true
type: boolean
security-scan:
description: "Enable Trivy Security Scan"
default: true
Expand Down Expand Up @@ -70,9 +74,9 @@ on:
type: string
secrets:
dockerhub-username:
required: true
required: false
dockerhub-password:
required: true
required: false
username:
required: false
password:
Expand All @@ -89,9 +93,11 @@ jobs:

# avoid shell injection through string interpolation
env:
PLATFORM: ${{ inputs.platform }}
OCI_IMAGE: ${{ inputs.image-name }}:${{ inputs.image-tag }}
IMAGE_TAG: ${{ inputs.image-tag }}
OCI_IMAGE: ${{ inputs.image-name }}:${{ inputs.image-tag }}
PLATFORM: ${{ inputs.platform }}
PUSH: ${{ inputs.push }}
REGISTRY: ${{ inputs.registry }}
REPORT_MODE: ${{ inputs.security-report }}

steps:
Expand All @@ -116,6 +122,43 @@ jobs:

echo "✅ Security report mode validation passed: $REPORT_MODE"

# Resolves whether the Docker Hub pull login is still needed, and fails fast
# when the push credentials required by `push: true` are missing.
- name: Validate registry credentials
id: validate-credentials
env:
DOCKERHUB_LOGIN: ${{ inputs.dockerhub-login }}
DOCKERHUB_USERNAME: ${{ secrets.dockerhub-username }}
DOCKERHUB_PASSWORD: ${{ secrets.dockerhub-password }}
REGISTRY_USERNAME: ${{ secrets.username }}
REGISTRY_PASSWORD: ${{ secrets.password }}
run: |
set -euo pipefail

if [ "$PUSH" = "true" ] && { [ -z "$REGISTRY_USERNAME" ] || [ -z "$REGISTRY_PASSWORD" ]; }; then
echo "❌ Error: push is enabled but 'username'/'password' are missing"
echo "Pass the secrets of the target registry ($REGISTRY), or set push: false"
exit 1
fi

NEEDS_DOCKERHUB_LOGIN=false
if [ "$DOCKERHUB_LOGIN" != "true" ]; then
echo "✅ Docker Hub login disabled by input"
elif [ "$PUSH" = "true" ] && { [ "$REGISTRY" = "docker.io" ] || [ "$REGISTRY" = "index.docker.io" ] || [ "$REGISTRY" = "registry-1.docker.io" ]; }; then
# The Docker Registry login below would authenticate the same host as
# Docker Hub login and would just overwrite credentials in ~/.docker/config.json.
# So pushing to Docker Hub does not require the Docker Hub secrets at all.
echo "✅ Skipping the Docker Hub login: pushing to $REGISTRY already authenticates that host"
elif [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then
echo "❌ Error: dockerhub-login is enabled but 'dockerhub-username'/'dockerhub-password' are missing"
echo "Pass the Docker Hub secrets, or set dockerhub-login: false"
exit 1
else
NEEDS_DOCKERHUB_LOGIN=true
fi

echo "needs-dockerhub-login=$NEEDS_DOCKERHUB_LOGIN" >> "$GITHUB_OUTPUT"

- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -131,7 +174,10 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

# Only needed to raise the pull rate limit on Docker Hub hosted base images
# when push `registry` is not Docker Hub.
- name: Login to Docker Hub
if: ${{ steps.validate-credentials.outputs.needs-dockerhub-login == 'true' }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.dockerhub-username }}
Expand Down Expand Up @@ -279,6 +325,7 @@ jobs:
run: docker push "$OCI_IMAGE"

- name: Record the pushed image tag
if: ${{ inputs.push }}
run: |
echo "$IMAGE_TAG" > image-tag.txt

Expand Down
53 changes: 39 additions & 14 deletions docker-build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ This reusable GitHub Actions workflow automates the process of building and push

## ✨ Features

- 🔐 Securely authenticates with a Docker Registry using best practices
- 🏗️ Builds optimized Docker images from a specified Dockerfile
- 🏷️ Intelligently tags and pushes images to a Docker Registry
- 🔐 Securely authenticates with a Docker Registry using best practices, using credentials stored as GitHub Secrets
- 🧭 Keeps the Docker Hub pull login and the registry push login separate, and skips the former when the target registry already is Docker Hub
- 🏭 Builds optimized Docker images from a specified Dockerfile
- 🔖 Intelligently tags and pushes images to a Docker Registry
- 🔎 Scan for vulnerabilities with Trivy
- 👍 Lint dockerfile
- 📜 Generates & signs a keyless SLSA build provenance attestation (optional)
- 🛡️ Handles authentication securely using GitHub Secrets
- 🚀 Optimizes build performance with layer caching
- 📦 Supports AMD64 and ARM64 platforms (one per workflow run)
- 📥 Optionally pulls a build artifact from an earlier job into the build context, so a compiled binary or jar can be built once and copied into the image
Expand All @@ -29,6 +29,7 @@ This reusable GitHub Actions workflow automates the process of building and push
| `build-args` | Docker build arguments (multiline format: `KEY1=value1\nKEY2=value2`) | No | `""` |
| `context` | Path to Docker Build Context | No | `"."` |
| `dockerfile` | Path to the Dockerfile to build (e.g., './Dockerfile', './docker/Dockerfile') | No | `"Dockerfile"` |
| `dockerhub-login` | Log in to Docker Hub before the build, to avoid anonymous pull rate limits on base images. Ignored when `push: true` and `registry` already points at Docker Hub, since the push login covers that host | No | `true` |
| `hadolint` | Enable Hadolint | No | `true` |
| `image-name` | Name of Docker Image (e.g., 'myimage', 'myorg/myimage') | true | - |
| `image-tag` | Tag to apply to the built image (e.g., 'latest', 'v1.2.3') | No | `"latest"` |
Expand All @@ -44,10 +45,17 @@ This reusable GitHub Actions workflow automates the process of building and push

| Name | Description | Required |
| --- | --- | --- |
| `dockerhub-username` | Username for Docker Hub authentication | Yes |
| `dockerhub-password` | Token for Docker Hub authentication (with read-only permissions) | Yes |
| `username` | Username for Docker Registry authentication | When `push: true` |
| `password` | Password or Personal Access Token for Docker registry authentication (with appropriate permissions) | When `push: true` |
| `dockerhub-username` | Username for Docker Hub authentication (pull-only, used when `dockerhub-login: true`) | When a Docker Hub login is still needed |
| `dockerhub-password` | Token for Docker Hub authentication (with read-only permissions) | When a Docker Hub login is still needed |

The two pairs serve different purposes and are **not** interchangeable:

- `username` / `password` authenticate the **push** against `registry`, and are the only credentials needed when `push: true`.
- `dockerhub-username` / `dockerhub-password` only raise the **pull** rate limit on base images (`FROM golang:…`, `FROM alpine:…`).

When `push: true` and `registry` already points at Docker Hub, the push login covers that host, so the workflow **skips** the Docker Hub login and the `dockerhub-*` secrets are not needed. A second login would only overwrite the first in `~/.docker/config.json`. Set `dockerhub-login: false` to skip the pull login explicitly, e.g. when every base image comes from a private mirror.

## 📤 Outputs

Expand All @@ -59,6 +67,8 @@ The built image tag, digest and checksum are visible on the workflow run summary

## 💻 Example Usage

Pushing to Docker Hub only needs the push credentials, because that login already covers pulling base images from Docker Hub:

```yaml
name: Build and Push Docker Image

Expand All @@ -80,26 +90,38 @@ jobs:
- name: Build and Push Docker Image
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/docker-build.yml@main # ⚠️ use tagged version here
with:
image-name: "username/my-image"
image-name: "docker.io/username/my-image"
registry: "docker.io"
push: true
dockerfile: "Dockerfile"
build-args: |
BUILD_VERSION=1.0.0
NODE_ENV=production
secrets:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PAT }}
```

Pushing to another registry, such as a private Nexus, keeps the Docker Hub pull login separate so base images are still fetched above the anonymous rate limit:

```yaml
- name: Build and Push Docker Image
uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/docker-build.yml@main # ⚠️ use tagged version here
with:
image-name: "docker-regis.iex.ec/my-image"
registry: "docker-regis.iex.ec"
push: true
secrets:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN_PULL_ONLY }}
username: ${{ secrets.NEXUS_USERNAME }}
password: ${{ secrets.NEXUS_PASSWORD }}
```

## 📝 Notes

- 🔒 Ensure your Docker Registry credentials are stored securely as GitHub Secrets
- 🔄 The workflow will automatically handle the Docker build and push process
- 🏷️ You can specify any valid Docker tag format in the `tag` input
- 🔖 You can specify any valid Docker tag format in the `image-tag` input
- 📤 When `push: true`, the applied tag is also saved as a `docker-image-tag` artifact, so `docker-promote` can promote the exact image CI built for a commit rather than reconstruct its tag
- 📅 Consider using dynamic tags based on git tags, commit SHAs, or dates
- 🧪 For testing purposes, you can use the `--dry-run` flag in your own implementation
- 📜 When `attest: true`, the attestation is only generated if `push: true`, and the **caller** workflow must grant the following permissions:
```yaml
permissions:
Expand All @@ -110,6 +132,9 @@ jobs:
## 🛠️ Troubleshooting

- If you encounter authentication issues, verify your Docker Registry credentials are correct and have appropriate permissions
- ❌ **`push is enabled but 'username'/'password' are missing`** — pass the credentials of `registry`, or set `push: false`
- ❌ **`dockerhub-login is enabled but 'dockerhub-username'/'dockerhub-password' are missing`** — the pull login is requested but no credential was given; pass them, or set `dockerhub-login: false` when base images come from a private mirror. Not raised when pushing to Docker Hub, since that login is skipped as redundant
- ❌ **Base image pull fails with a rate limit error** — set `dockerhub-login: true` and pass the `dockerhub-*` secrets
- For build failures, check your Dockerfile syntax and ensure all referenced files exist
- Large images may take longer to push - consider optimizing your Dockerfile with multi-stage builds
- If you need to debug the build process, you can add the `ACTIONS_STEP_DEBUG` secret set to `true` in your repository
2 changes: 1 addition & 1 deletion docker-build/workflow-sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eda9395589dd24e9b931f836a515d6733a82df11438abc850ae0220d9c49fc9e
38bec74c032cfdf66b75251458deb6ef8c0ba1fbc10307049d3311fa8538648f
Loading