Skip to content

fix(docker): serialize image builds with flock to prevent parallel collisions#9878

Open
igorpecovnik wants to merge 3 commits into
mainfrom
fix/docker-unique-image-tag
Open

fix(docker): serialize image builds with flock to prevent parallel collisions#9878
igorpecovnik wants to merge 3 commits into
mainfrom
fix/docker-unique-image-tag

Conversation

@igorpecovnik
Copy link
Copy Markdown
Member

@igorpecovnik igorpecovnik commented May 22, 2026

Summary

  • Fixed "AlreadyExists" error when multiple runners build in parallel on same host
  • Uses flock to serialize Docker image builds - first builder builds, others wait and reuse
  • Lock stored in /run (tmpfs) for performance and auto-cleanup on reboot
  • Maintains single-image model (no additional disk overhead)

Problem

Multiple GitHub Actions runners building on the same host all tried to create
armbian.local.only/armbian-build:initial simultaneously, causing:

failed to create an image armbian.local.only/armbian-build:initial with target
sha256:601e3284edef5399031ba520b29e1d58483a5a89b5aca0c219388bd66a357f55 after
deleting the existing one: AlreadyExists: image
"armbian.local.only/armbian-build:initial": already exists

Solution

Serialize the Docker build step using flock:

  • First runner: Acquires lock → builds image → releases lock
  • Subsequent runners: Wait for lock → check if image exists → reuse if yes, build if no
  • Lock location: /run/armbian-docker-build-initial.lock (tmpfs, cleared on reboot)
  • Timeout: 1 hour (builds should never take this long; prevents deadlock)

Benefits over unique-tag approach

  • ✅ No extra disk usage (single shared image)
  • ✅ No accumulation of stale images
  • ✅ Faster builds for runners that can reuse
  • ✅ No dependency on uuid-runtime

Summary by CodeRabbit

  • Improvements
    • Docker build artifacts now receive unique per-build image tags to avoid collisions when multiple builds run concurrently on the same host.
    • Host preparation now ensures a UUID runtime is installed, improving reliability of non-interactive host setup and downstream build steps.

Review Change Stack

@igorpecovnik igorpecovnik requested a review from a team as a code owner May 22, 2026 18:17
@igorpecovnik igorpecovnik requested review from TRSx80 and mhoffrog and removed request for a team May 22, 2026 18:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

docker_cli_prepare() builds an 8-character suffix (from ARMBIAN_BUILD_UUID or uuidgen) and sets DOCKER_ARMBIAN_INITIAL_IMAGE_TAG to armbian.local.only/armbian-build:${build_suffix}. The host dependency list now includes uuid-runtime in addition to uuid-dev.

Changes

Host prepare & Docker tag

Layer / File(s) Summary
Per-build Docker initial image tag
lib/functions/host/docker.sh
docker_cli_prepare() computes an 8-character build_suffix (from ARMBIAN_BUILD_UUID or uuidgen) and sets DOCKER_ARMBIAN_INITIAL_IMAGE_TAG="armbian.local.only/armbian-build:${build_suffix}", replacing the static :initial tag.
Add uuid-runtime to host dependencies
lib/functions/host/prepare-host.sh
The host_dependencies array now includes uuid-runtime alongside uuid-dev for non-interactive host preparation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nibbled at a UUID seed,
A little suffix sprouted speed,
No clash, no scramble on the floor,
Each build hops out, unique once more,
🎉 Tiny paws, proud work galore.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title claims to serialize builds with flock, but the actual changes add uuid-based tags and uuid-runtime dependency, which contradicts the stated approach. Update the title to accurately reflect the actual implementation: 'fix(docker): prevent image tag collisions with unique UUID-based tags' or similar, since the changes use UUID-based image tagging, not flock serialization.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/docker-unique-image-tag

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size/small PR with less then 50 lines 05 Milestone: Second quarter release Needs review Seeking for review Framework Framework components labels May 22, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/functions/host/docker.sh`:
- Around line 405-418: The current reuse check for
DOCKER_ARMBIAN_INITIAL_IMAGE_TAG is unsafe because it treats any existing
:initial image as valid; modify the flow to compute an input fingerprint (from
SRC, generated Dockerfile content and extension deps) and either incorporate
that fingerprint into DOCKER_ARMBIAN_INITIAL_IMAGE_TAG or label the image with a
"fingerprint" label during the docker build command, then change the pre-build
check (the docker image inspect call and the logic that sets
build_result/"already_built") to verify the stored image's fingerprint label (or
tag) matches the computed fingerprint before returning "already_built"; also
ensure the docker build invocation (the BUILDKIT_COLORS ... docker
'${DOCKER_BUILDX_OR_BUILD[@]}' ...) sets the fingerprint label when building so
future inspect/label checks can validate reuse, and keep the display_alert
branches (display_alert "Docker image" ...) unchanged except that their messages
reference the validated fingerprint.
- Around line 404-421: The flock call spawns a bash -c string which prevents the
array DOCKER_BUILDX_OR_BUILD from expanding correctly; acquire the lock in the
current shell instead and run docker with the array directly. Replace the
current flock -w "${lock_timeout}" "${DOCKER_BUILD_LOCK_FILE}" bash -c "..."
block by opening the lock file descriptor (e.g. exec
200>"${DOCKER_BUILD_LOCK_FILE}"), use flock -w "${lock_timeout}" 200 to obtain
the lock (or call exit_with_error on timeout), then inside the current shell
check docker image inspect "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" and run docker
"${DOCKER_BUILDX_OR_BUILD[@]}" -t "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" -f
"${SRC}"/Dockerfile "${SRC}", setting build_result to 'already_built' or 'built'
as before so the subsequent if [[ "$build_result" == *"already_built"* ]] logic
continues to work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81f9751b-182c-45ab-90cf-53ac8c23c753

📥 Commits

Reviewing files that changed from the base of the PR and between 869f0df and f936d14.

📒 Files selected for processing (1)
  • lib/functions/host/docker.sh

Comment thread lib/functions/host/docker.sh Outdated
Comment thread lib/functions/host/docker.sh Outdated
@igorpecovnik igorpecovnik force-pushed the fix/docker-unique-image-tag branch from f936d14 to aeb5620 Compare May 23, 2026 14:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/functions/host/docker.sh (1)

410-420: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Keep the flocked Docker build in the current shell (fix bash -c argv + set -e propagation)

  • The locked block is executed via flock ... bash -c "..."; this child shell doesn’t inherit the file’s set -e, so a failing docker ... can still reach echo 'built' and be treated as success by the outer if.
  • docker '${DOCKER_BUILDX_OR_BUILD[@]}' ... turns the whole array into a single argument (e.g., buildx build --progress=plain --load) instead of separate argv entries when DOCKER_BUILDX_OR_BUILD is a multi-element array.
💡 Minimal fix
-	if build_result=$(flock -w ${lock_timeout} "${DOCKER_BUILD_LOCK_FILE}" bash -c "
-		# Inside the lock: check if image already exists (another runner built it while we waited)
-		if docker image inspect '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}' &>/dev/null; then
-			echo 'already_built'
-			exit 0
-		fi
-		# Build the image
-		BUILDKIT_COLORS='run=123,20,245:error=yellow:cancel=blue:warning=white' \
-			docker '${DOCKER_BUILDX_OR_BUILD[@]}' -t '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}' -f '${SRC}'/Dockerfile '${SRC}'
-		echo 'built'
-	" 2>&1); then
-		if [[ "$build_result" == *"already_built"* ]]; then
+	local docker_build_lock_fd
+	exec {docker_build_lock_fd}> "${DOCKER_BUILD_LOCK_FILE}" || exit_with_error "Cannot open lock file '${DOCKER_BUILD_LOCK_FILE}'"
+	if flock -w "${lock_timeout}" "${docker_build_lock_fd}"; then
+		if docker image inspect "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" &>/dev/null; then
+			build_result="already_built"
+		else
+			BUILDKIT_COLORS='run=123,20,245:error=yellow:cancel=blue:warning=white' \
+				docker "${DOCKER_BUILDX_OR_BUILD[@]}" -t "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" -f "${SRC}"/Dockerfile "${SRC}"
+			build_result="built"
+		fi
+		if [[ "${build_result}" == "already_built" ]]; then
 			display_alert "Docker image" "already built by another runner, reusing '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
 		else
 			display_alert "Docker image" "built successfully '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
 		fi
 	else
 		exit_with_error "Docker build failed or lock timeout after ${lock_timeout}s"
 	fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/functions/host/docker.sh` around lines 410 - 420, The locked build block
uses `flock ... bash -c "..."`, which loses the parent shell's set -e and
collapses the DOCKER_BUILDX_OR_BUILD array into a single string; replace this
with an in-shell flock using a file-descriptor lock so the block runs in the
current shell with set -e and proper argv expansion. Concretely: open a FD for
DOCKER_BUILD_LOCK_FILE (e.g., exec {lock_fd}>"${DOCKER_BUILD_LOCK_FILE}"), run a
subshell or grouped commands with set -e and flock -w "${lock_timeout}"
"${lock_fd}" so all commands (the docker image inspect for
DOCKER_ARMBIAN_INITIAL_IMAGE_TAG and the BUILDKIT_COLORS docker
"${DOCKER_BUILDX_OR_BUILD[@]}" -t ... using SRC) execute in the same shell
context, then close the FD; ensure build_result captures that block's
stdout/stderr as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@lib/functions/host/docker.sh`:
- Around line 410-420: The locked build block uses `flock ... bash -c "..."`,
which loses the parent shell's set -e and collapses the DOCKER_BUILDX_OR_BUILD
array into a single string; replace this with an in-shell flock using a
file-descriptor lock so the block runs in the current shell with set -e and
proper argv expansion. Concretely: open a FD for DOCKER_BUILD_LOCK_FILE (e.g.,
exec {lock_fd}>"${DOCKER_BUILD_LOCK_FILE}"), run a subshell or grouped commands
with set -e and flock -w "${lock_timeout}" "${lock_fd}" so all commands (the
docker image inspect for DOCKER_ARMBIAN_INITIAL_IMAGE_TAG and the
BUILDKIT_COLORS docker "${DOCKER_BUILDX_OR_BUILD[@]}" -t ... using SRC) execute
in the same shell context, then close the FD; ensure build_result captures that
block's stdout/stderr as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78ae49ff-c2de-48e1-8a82-a4c95d9de2dd

📥 Commits

Reviewing files that changed from the base of the PR and between f936d14 and aeb5620.

📒 Files selected for processing (1)
  • lib/functions/host/docker.sh

@igorpecovnik igorpecovnik force-pushed the fix/docker-unique-image-tag branch from aeb5620 to 02bb342 Compare May 23, 2026 15:05
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/functions/host/docker.sh (1)

411-421: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

bash -c string breaks array expansion of DOCKER_BUILDX_OR_BUILD.

When DOCKER_BUILDX_OR_BUILD=("buildx" "build" "--progress=plain" "--load"), embedding '${DOCKER_BUILDX_OR_BUILD[@]}' inside a double-quoted bash -c string causes the array elements to be joined into a single string: 'buildx build --progress=plain --load'. Docker receives this as one argument and fails.

Use file-descriptor-based locking in the current shell instead:

Proposed fix using fd-based flock
-	display_alert "Acquiring Docker build lock" "${DOCKER_BUILD_LOCK_FILE}" "debug"
-	if build_result=$(flock -w ${lock_timeout} "${DOCKER_BUILD_LOCK_FILE}" bash -c "
-		# Inside the lock: check if image already exists (another runner built it while we waited)
-		if docker image inspect '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}' &>/dev/null; then
-			echo 'already_built'
-			exit 0
-		fi
-		# Build the image
-		BUILDKIT_COLORS='run=123,20,245:error=yellow:cancel=blue:warning=white' \
-			docker '${DOCKER_BUILDX_OR_BUILD[@]}' -t '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}' -f '${SRC}'/Dockerfile '${SRC}'
-		echo 'built'
-	" 2>&1); then
-		if [[ "$build_result" == *"already_built"* ]]; then
-			display_alert "Docker image" "already built by another runner, reusing '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
-		else
-			display_alert "Docker image" "built successfully '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
-		fi
-	else
-		exit_with_error "Docker build failed or lock timeout after ${lock_timeout}s"
-	fi
+	display_alert "Acquiring Docker build lock" "${DOCKER_BUILD_LOCK_FILE}" "debug"
+	
+	# Open lock file on fd 200 and acquire exclusive lock with timeout
+	exec 200>"${DOCKER_BUILD_LOCK_FILE}"
+	if ! flock -w "${lock_timeout}" 200; then
+		exec 200>&-  # close fd on failure
+		exit_with_error "Failed to acquire Docker build lock after ${lock_timeout}s"
+	fi
+	
+	# Inside the lock: check if image already exists (another runner built it while we waited)
+	if docker image inspect "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" &>/dev/null; then
+		display_alert "Docker image" "already built by another runner, reusing '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
+	else
+		# Build the image
+		display_alert "Building Docker image" "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" "info"
+		BUILDKIT_COLORS='run=123,20,245:error=yellow:cancel=blue:warning=white' \
+			docker "${DOCKER_BUILDX_OR_BUILD[@]}" -t "${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}" -f "${SRC}"/Dockerfile "${SRC}" \
+			|| { exec 200>&-; exit_with_error "Docker build failed"; }
+		display_alert "Docker image" "built successfully '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}'" "info"
+	fi
+	
+	# Release lock (fd closed automatically when script exits, but explicit is clearer)
+	exec 200>&-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/functions/host/docker.sh` around lines 411 - 421, The current use of bash
-c with flock causes the DOCKER_BUILDX_OR_BUILD array to be expanded as a single
string (breaking docker args); replace the subshell-quoted flock invocation with
fd-based locking in the current shell so arrays expand normally: open the lock
file with exec to get a file descriptor (referencing DOCKER_BUILD_LOCK_FILE),
use flock -w "${lock_timeout}" "$fd" to acquire the lock, then perform the
image-exists check (docker image inspect '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}')
and run the docker command with "${DOCKER_BUILDX_OR_BUILD[@]}" and the rest of
the environment in the same shell, capturing stdout/stderr into build_result as
before; ensure you release/close the fd after the critical section and preserve
the 'already_built'/'built' echo behavior used by build_result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/functions/host/docker.sh`:
- Around line 417-420: The docker build is being run in a subshell so failures
are masked and "built" is printed even on error; run the docker command in the
current shell (do not wrap it in a bash -c/subshell), keep the BUILDKIT_COLORS /
DOCKER_BUILDX_OR_BUILD / DOCKER_ARMBIAN_INITIAL_IMAGE_TAG / SRC variables as
before, and immediately check its exit status—on non-zero return log an error
(including the failing command context) and exit non-zero so set -e behavior is
preserved (alternatively use explicit "|| { ...; exit 1; }" style handling
around the docker invocation).

---

Duplicate comments:
In `@lib/functions/host/docker.sh`:
- Around line 411-421: The current use of bash -c with flock causes the
DOCKER_BUILDX_OR_BUILD array to be expanded as a single string (breaking docker
args); replace the subshell-quoted flock invocation with fd-based locking in the
current shell so arrays expand normally: open the lock file with exec to get a
file descriptor (referencing DOCKER_BUILD_LOCK_FILE), use flock -w
"${lock_timeout}" "$fd" to acquire the lock, then perform the image-exists check
(docker image inspect '${DOCKER_ARMBIAN_INITIAL_IMAGE_TAG}') and run the docker
command with "${DOCKER_BUILDX_OR_BUILD[@]}" and the rest of the environment in
the same shell, capturing stdout/stderr into build_result as before; ensure you
release/close the fd after the critical section and preserve the
'already_built'/'built' echo behavior used by build_result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e66799f3-4cee-4778-973b-5fa5f0179b36

📥 Commits

Reviewing files that changed from the base of the PR and between aeb5620 and 02bb342.

📒 Files selected for processing (1)
  • lib/functions/host/docker.sh

Comment thread lib/functions/host/docker.sh Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/functions/host/docker.sh`:
- Around line 130-134: The current fallback for build_suffix (using
ARMBIAN_BUILD_UUID or uuidgen and then truncating the first 8 chars) can produce
a constant value like "no-uuidg" when uuidgen is unavailable; change the
fallback to derive a unique suffix even without uuidgen by combining available
entropy (e.g., timestamp, PID, random bytes) and hashing it before truncation so
collisions are unlikely; update the logic around
build_suffix/ARMBIAN_BUILD_UUID/uuidgen and ensure
DOCKER_ARMBIAN_INITIAL_IMAGE_TAG uses that hashed/truncated value (referencing
the build_suffix variable and DOCKER_ARMBIAN_INITIAL_IMAGE_TAG symbol to locate
and replace the current fallback generation).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 49cc7ba7-7529-4d5f-a9d1-f9239a1bfa59

📥 Commits

Reviewing files that changed from the base of the PR and between 02bb342 and d0fc71b.

📒 Files selected for processing (2)
  • lib/functions/host/docker.sh
  • lib/functions/host/prepare-host.sh

Comment on lines +130 to +134
# Unique tag per build to avoid collisions when multiple runners build in parallel on
# the same host under different users (shared Docker daemon, separate ${SRC} trees).
declare build_suffix="${ARMBIAN_BUILD_UUID:-$(uuidgen)}"
build_suffix="${build_suffix:0:8}"
declare -g DOCKER_ARMBIAN_INITIAL_IMAGE_TAG="armbian.local.only/armbian-build:${build_suffix}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a suffix derivation that stays unique when uuidgen is unavailable.

On Line 133, truncating the first 8 chars can produce a constant tag (no-uuidg) for multiple runs when ARMBIAN_BUILD_UUID came from the fallback format, so collisions can still happen.

💡 Suggested patch
-	declare build_suffix="${ARMBIAN_BUILD_UUID:-$(uuidgen)}"
-	build_suffix="${build_suffix:0:8}"
+	declare build_suffix="${ARMBIAN_BUILD_UUID:-$(uuidgen)}"
+	build_suffix="${build_suffix//[^[:alnum:]]/}"
+	build_suffix="${build_suffix: -8}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/functions/host/docker.sh` around lines 130 - 134, The current fallback
for build_suffix (using ARMBIAN_BUILD_UUID or uuidgen and then truncating the
first 8 chars) can produce a constant value like "no-uuidg" when uuidgen is
unavailable; change the fallback to derive a unique suffix even without uuidgen
by combining available entropy (e.g., timestamp, PID, random bytes) and hashing
it before truncation so collisions are unlikely; update the logic around
build_suffix/ARMBIAN_BUILD_UUID/uuidgen and ensure
DOCKER_ARMBIAN_INITIAL_IMAGE_TAG uses that hashed/truncated value (referencing
the build_suffix variable and DOCKER_ARMBIAN_INITIAL_IMAGE_TAG symbol to locate
and replace the current fallback generation).

When multiple GitHub Actions runners on the same host build in parallel,
they all try to create the same "armbian.local.only/armbian-build:initial"
tag, causing "AlreadyExists" errors.

Fix by appending a short UUID suffix to the image tag, making each build's
image unique. Uses ARMBIAN_BUILD_UUID if available (reused across relaunches),
otherwise generates a fresh UUID with uuidgen (already in BASIC_DEPS via
uuid-runtime).

This allows true parallel builds without the previous race condition.
…llisions

When multiple runners build in parallel on the same host, they all tried to
create the same "armbian-build:initial" image, causing "AlreadyExists" errors.

Solution: Use flock to serialize the Docker build step:
- First runner acquires lock and builds the image
- Subsequent runners wait for lock, then reuse the already-built image
- Lock is in ${SRC}/cache/docker/ (guaranteed writable by build process)
- 1 hour timeout prevents permanent deadlock

This maintains the single-image model (no extra disk usage) while fixing
the race condition.

Fixes: armbian/os#<issue_number>
…uilds

The flock added in 02bb342 did not actually serialize anything across
parallel runners: the lock path lived under ${SRC}/cache/docker, which is
per-user on hosts where each runner executes as a separate Linux account,
so every runner owned its own lock and they all collided on the shared
daemon's :initial tag anyway.

It also broke the docker invocation: '${DOCKER_BUILDX_OR_BUILD[@]}' inside
bash -c "..." collapsed the array into a single literal argument (e.g.
"buildx build --progress=plain --load"), causing docker to fail in ~5s.
The captured stderr was then silently dropped, so the failure surfaced as
the generic "lock timeout after 3600s" message regardless of cause.

Restore the unique-UUID-tag strategy from d6902e5 (each runner builds
its own armbian-build:<uuid8> image, so there is nothing to serialize),
drop the flock + bash -c machinery, and put uuid-runtime back into
BASIC_DEPS so uuidgen is available.
@igorpecovnik igorpecovnik force-pushed the fix/docker-unique-image-tag branch from d0fc71b to 6e06503 Compare May 24, 2026 07:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/functions/host/docker.sh (1)

130-134: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

First 8 characters produce collisions when uuidgen is unavailable.

This issue was previously raised but remains unaddressed. Taking the first 8 characters of ARMBIAN_BUILD_UUID produces the constant prefix "no-uuidg" when the fallback value from entrypoint.sh is used ("no-uuidgen-yet-${RANDOM}-..."). This defeats the uniqueness guarantee in exactly the scenario this PR addresses: parallel builds on a clean host where uuidgen has not yet been installed.

Based on the entrypoint.sh context (lines 123-138), ARMBIAN_BUILD_UUID is generated before basic dependencies (including uuid-runtime) are installed, so the fallback case will occur on first builds. Multiple concurrent first-time builds will collide on the "no-uuidg" tag.

The previously suggested fix (strip non-alphanumeric characters and take the last 8 characters) resolves this by preserving the varying RANDOM-generated suffix.

🔧 Apply the previously suggested fix
 declare build_suffix="${ARMBIAN_BUILD_UUID:-$(uuidgen)}"
-build_suffix="${build_suffix:0:8}"
+build_suffix="${build_suffix//[^[:alnum:]]/}"
+build_suffix="${build_suffix: -8}"
 declare -g DOCKER_ARMBIAN_INITIAL_IMAGE_TAG="armbian.local.only/armbian-build:${build_suffix}"

This strips non-alphanumeric characters and takes the last 8 characters. For the fallback "no-uuidgen-yet-12345-5678", after stripping this becomes "nouuidgenyet123455678", and the last 8 characters "45567" vary with each $RANDOM invocation. For actual UUIDs, the last 8 hex digits provide equivalent collision resistance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/functions/host/docker.sh` around lines 130 - 134, The current slicing of
ARMBIAN_BUILD_UUID into build_suffix uses the first 8 characters which yields
the constant prefix "no-uuidg" when the fallback value from entrypoint.sh is
used; change the logic referenced by build_suffix and
DOCKER_ARMBIAN_INITIAL_IMAGE_TAG so that you first strip non-alphanumeric
characters from ARMBIAN_BUILD_UUID (to remove dashes/other text) and then take
the last 8 characters (not the first) to form build_suffix; this preserves
entropy from the RANDOM suffix in the "no-uuidgen-..." fallback and keeps
UUID-derived values robust for real UUIDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@lib/functions/host/docker.sh`:
- Around line 130-134: The current slicing of ARMBIAN_BUILD_UUID into
build_suffix uses the first 8 characters which yields the constant prefix
"no-uuidg" when the fallback value from entrypoint.sh is used; change the logic
referenced by build_suffix and DOCKER_ARMBIAN_INITIAL_IMAGE_TAG so that you
first strip non-alphanumeric characters from ARMBIAN_BUILD_UUID (to remove
dashes/other text) and then take the last 8 characters (not the first) to form
build_suffix; this preserves entropy from the RANDOM suffix in the
"no-uuidgen-..." fallback and keeps UUID-derived values robust for real UUIDs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d8e6504-5d32-4724-b75e-de31386b0e00

📥 Commits

Reviewing files that changed from the base of the PR and between d0fc71b and 6e06503.

📒 Files selected for processing (2)
  • lib/functions/host/docker.sh
  • lib/functions/host/prepare-host.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

05 Milestone: Second quarter release Framework Framework components Needs review Seeking for review size/small PR with less then 50 lines

Development

Successfully merging this pull request may close these issues.

1 participant