fix(docker): update helper CLI for registry pushes (#11461)

This commit is contained in:
Andras Bacsai 2026-08-22 12:53:20 +02:00 committed by GitHub
parent 59cd5b0d72
commit 60eff6fb6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 368 additions and 8 deletions

View file

@ -228,6 +228,35 @@ # Development Guides
## Local Development ## Local Development
To build and run Coolify locally, see: [Development](./DEVELOPMENT.md) To build and run Coolify locally, see: [Development](./DEVELOPMENT.md)
### Testing the Coolify Helper Locally
Use `scripts/dev-helper` to build a local helper image and test it with the running development instance. The script requires the standard local Coolify container and the seeded Dockerfile, Docker Compose, and Nixpacks applications.
Run the complete workflow:
```bash
./scripts/dev-helper test my-helper-test
```
This builds and selects the helper image, verifies its bundled tools and Docker socket access, runs a Docker Compose smoke test, and deploys all three seeded applications.
You can also run each step separately:
```bash
./scripts/dev-helper build my-helper-test
./scripts/dev-helper use my-helper-test
./scripts/dev-helper verify my-helper-test
./scripts/dev-helper deploy my-helper-test
```
Clear the helper override when finished:
```bash
./scripts/dev-helper reset
```
The default image repository is `docker.io/coollabsio/coolify-helper`. Set `HELPER_IMAGE_REPOSITORY` to test another repository, or `COOLIFY_CONTAINER` if the local Coolify container has a different name.
### macOS Development with Lima ### macOS Development with Lima
Mac users can use [Lima](https://lima-vm.io/) to run a lightweight Linux virtual machine for local Coolify development. This is useful if you prefer a Linux-based Docker environment on macOS. Mac users can use [Lima](https://lima-vm.io/) to run a lightweight Linux virtual machine for local Coolify development. This is useful if you prefer a Linux-based Docker environment on macOS.

View file

@ -212,7 +212,7 @@ public function buildHelperImage()
return; return;
} }
$imageRef = escapeshellarg("ghcr.io/coollabsio/coolify-helper:{$version}"); $imageRef = escapeshellarg(coolifyHelperImage().":{$version}");
$buildCommand = "docker build -t {$imageRef} -f docker/coolify-helper/Dockerfile ."; $buildCommand = "docker build -t {$imageRef} -f docker/coolify-helper/Dockerfile .";
$activity = remote_process( $activity = remote_process(

View file

@ -4152,11 +4152,12 @@ function coolifyHelperImage(): string
function getHelperVersion(): string function getHelperVersion(): string
{ {
$settings = instanceSettings(); if (isDev()) {
$devHelperVersion = InstanceSettings::query()->whereKey(0)->value('dev_helper_version');
// In development mode, use the dev_helper_version if set, otherwise fallback to config if (! empty($devHelperVersion)) {
if (isDev() && ! empty($settings->dev_helper_version)) { return $devHelperVersion;
return $settings->dev_helper_version; }
} }
return config('constants.coolify.helper_version'); return config('constants.coolify.helper_version');

View file

@ -2,11 +2,11 @@
# https://hub.docker.com/_/alpine # https://hub.docker.com/_/alpine
ARG BASE_IMAGE=alpine:3.21 ARG BASE_IMAGE=alpine:3.21
# https://download.docker.com/linux/static/stable/ # https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=28.0.0 ARG DOCKER_VERSION=29.7.2
# https://github.com/docker/compose/releases # https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.38.2 ARG DOCKER_COMPOSE_VERSION=5.5.0
# https://github.com/docker/buildx/releases # https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.25.0 ARG DOCKER_BUILDX_VERSION=0.36.1
# https://github.com/buildpacks/pack/releases # https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.38.2 ARG PACK_VERSION=0.38.2
# https://github.com/railwayapp/nixpacks/releases # https://github.com/railwayapp/nixpacks/releases

280
scripts/dev-helper Executable file
View file

@ -0,0 +1,280 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
REPOSITORY=${HELPER_IMAGE_REPOSITORY:-docker.io/coollabsio/coolify-helper}
DEFAULT_TAG="dev-$(git -C "$ROOT_DIR" rev-parse --short HEAD)"
COOLIFY_CONTAINER=${COOLIFY_CONTAINER:-coolify}
usage() {
cat <<EOF
Usage: $0 <command> [tag]
Commands:
build [tag] Build the local helper image
use [tag] Configure local Coolify to use an existing helper image
verify [tag] Verify tools and run a Docker Compose socket smoke test
deploy [tag] Deploy seeded Dockerfile, Compose, and Nixpacks applications
reset Clear the local helper version override
test [tag] Build, select, verify, and run real seeded deployments
Default tag: $DEFAULT_TAG
EOF
}
require_local_coolify() {
local is_dev
if ! docker inspect "$COOLIFY_CONTAINER" >/dev/null 2>&1; then
echo "Coolify container '$COOLIFY_CONTAINER' is not running." >&2
exit 1
fi
is_dev=$(docker exec "$COOLIFY_CONTAINER" php artisan tinker --execute 'echo isDev() ? "yes" : "no";' 2>/dev/null | tail -1)
if [[ $is_dev != yes ]]; then
echo "The running Coolify instance is not in development mode." >&2
exit 1
fi
}
validate_tag() {
local tag=$1
if [[ ! $tag =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then
echo "Invalid Docker tag: $tag" >&2
exit 1
fi
}
image_for() {
echo "${REPOSITORY}:$1"
}
build_helper() {
local tag=$1
local image
image=$(image_for "$tag")
echo "Building $image"
docker build --progress=plain -f "$ROOT_DIR/docker/coolify-helper/Dockerfile" -t "$image" "$ROOT_DIR"
}
use_helper() {
local tag=$1
local image
image=$(image_for "$tag")
require_local_coolify
docker image inspect "$image" >/dev/null
docker exec -e DEV_HELPER_VERSION="$tag" "$COOLIFY_CONTAINER" php artisan tinker --execute '
App\Models\InstanceSettings::findOrFail(0)->update([
"dev_helper_version" => getenv("DEV_HELPER_VERSION"),
]);
' >/dev/null
local selected
selected=$(docker exec "$COOLIFY_CONTAINER" php artisan tinker --execute 'echo getHelperVersion();' 2>/dev/null | tail -1)
if [[ $selected != "$tag" ]]; then
echo "Coolify selected helper '$selected' instead of '$tag'." >&2
exit 1
fi
echo "Coolify now uses $image"
}
verify_helper() {
local tag=$1
local image project compose
image=$(image_for "$tag")
project="coolify-helper-${tag//[^A-Za-z0-9_-]/-}-smoke"
compose=$'services:\n app:\n image: alpine:3.21\n command: ["sh", "-c", "sleep 30"]\n'
docker image inspect "$image" >/dev/null
docker run --rm "$image" docker --version
docker run --rm "$image" docker compose version
docker run --rm "$image" docker buildx version
docker run --rm "$image" pack version
docker run --rm "$image" nixpacks --version
docker run --rm "$image" railpack --version
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock "$image" \
docker version --format 'client={{.Client.Version}} server={{.Server.Version}} api={{.Client.APIVersion}}/{{.Server.APIVersion}}'
cleanup_compose() {
printf '%s' "$compose" | docker run --rm -i \
-v /var/run/docker.sock:/var/run/docker.sock "$image" \
docker compose -p "$project" -f - down --remove-orphans >/dev/null 2>&1 || true
}
trap cleanup_compose RETURN
printf '%s' "$compose" | docker run --rm -i \
-v /var/run/docker.sock:/var/run/docker.sock "$image" \
docker compose -p "$project" -f - up -d --wait
printf '%s' "$compose" | docker run --rm -i \
-v /var/run/docker.sock:/var/run/docker.sock "$image" \
docker compose -p "$project" -f - ps --format json
cleanup_compose
trap - RETURN
if [[ -n $(docker ps -aq --filter "label=com.docker.compose.project=$project") ]]; then
echo "Compose smoke-test resources were not removed." >&2
exit 1
fi
echo "Helper verification passed for $image"
}
queue_deployment() {
local application_uuid=$1
docker exec -e APPLICATION_UUID="$application_uuid" "$COOLIFY_CONTAINER" php artisan tinker --execute '
$application = App\Models\Application::query()
->where("uuid", getenv("APPLICATION_UUID"))
->firstOrFail();
$deploymentUuid = (string) Illuminate\Support\Str::uuid();
queue_application_deployment(
application: $application,
deployment_uuid: $deploymentUuid,
force_rebuild: true,
no_questions_asked: true,
);
echo "DEPLOYMENT_UUID={$deploymentUuid}";
' 2>/dev/null | sed -n 's/^DEPLOYMENT_UUID=//p' | tail -1
}
wait_for_deployment() {
local deployment_uuid=$1
local expected_image=$2
local status
for _ in $(seq 1 150); do
status=$(docker exec -e DEPLOYMENT_UUID="$deployment_uuid" "$COOLIFY_CONTAINER" php artisan tinker --execute '
$deployment = App\Models\ApplicationDeploymentQueue::query()
->where("deployment_uuid", getenv("DEPLOYMENT_UUID"))
->first();
echo $deployment?->status ?? "missing";
' 2>/dev/null | tail -1)
case "$status" in
finished)
break
;;
failed|cancelled|missing)
echo "Deployment $deployment_uuid ended with status: $status" >&2
return 1
;;
esac
sleep 4
done
if [[ $status != finished ]]; then
echo "Deployment $deployment_uuid timed out with status: $status" >&2
return 1
fi
local used_expected_helper
used_expected_helper=$(docker exec \
-e DEPLOYMENT_UUID="$deployment_uuid" \
-e EXPECTED_HELPER_IMAGE="$expected_image" \
"$COOLIFY_CONTAINER" php artisan tinker --execute '
$deployment = App\Models\ApplicationDeploymentQueue::query()
->where("deployment_uuid", getenv("DEPLOYMENT_UUID"))
->firstOrFail();
$logs = collect(json_decode($deployment->logs, true))
->pluck("output")
->implode("\n");
echo str_contains(
$logs,
"Preparing container with helper image: ".getenv("EXPECTED_HELPER_IMAGE"),
) ? "yes" : "no";
' 2>/dev/null | tail -1)
if [[ $used_expected_helper != yes ]]; then
echo "Deployment $deployment_uuid did not use $expected_image." >&2
return 1
fi
}
deploy_examples() {
local tag=$1
local image application_uuid deployment_uuid fqdn
image=$(image_for "$tag")
require_local_coolify
use_helper "$tag"
for application_uuid in dockerfile docker-compose nodejs; do
echo "Deploying seeded application: $application_uuid"
deployment_uuid=$(queue_deployment "$application_uuid")
if [[ -z $deployment_uuid ]]; then
echo "Could not queue $application_uuid." >&2
exit 1
fi
wait_for_deployment "$deployment_uuid" "$image"
fqdn=$(docker exec -e APPLICATION_UUID="$application_uuid" "$COOLIFY_CONTAINER" php artisan tinker --execute '
echo App\Models\Application::query()
->where("uuid", getenv("APPLICATION_UUID"))
->value("fqdn") ?? "";
' 2>/dev/null | tail -1)
if [[ -n $fqdn ]]; then
curl --fail --silent --show-error --output /dev/null "$fqdn"
fi
echo "Deployment passed: $application_uuid ($deployment_uuid)"
done
}
reset_helper() {
require_local_coolify
docker exec "$COOLIFY_CONTAINER" php artisan tinker --execute '
App\Models\InstanceSettings::findOrFail(0)->update([
"dev_helper_version" => null,
]);
' >/dev/null
echo "Development helper override cleared."
}
command=${1:-help}
tag=${2:-$DEFAULT_TAG}
case "$command" in
build)
validate_tag "$tag"
build_helper "$tag"
;;
use)
validate_tag "$tag"
use_helper "$tag"
;;
verify)
validate_tag "$tag"
verify_helper "$tag"
;;
deploy)
validate_tag "$tag"
deploy_examples "$tag"
;;
reset)
reset_helper
;;
test)
validate_tag "$tag"
build_helper "$tag"
use_helper "$tag"
verify_helper "$tag"
deploy_examples "$tag"
;;
help|-h|--help)
usage
;;
*)
usage >&2
exit 1
;;
esac

View file

@ -88,3 +88,22 @@
->call('buildHelperImage') ->call('buildHelperImage')
->assertDispatched('error'); ->assertDispatched('error');
}); });
test('development helper version is read fresh for queue workers', function () {
config(['app.env' => 'local']);
InstanceSettings::findOrFail(0)->update(['dev_helper_version' => 'first']);
expect(getHelperVersion())->toBe('first');
InstanceSettings::query()->whereKey(0)->update(['dev_helper_version' => 'second']);
expect(getHelperVersion())->toBe('second');
});
test('development helper build uses the configured helper repository', function () {
$component = file_get_contents(app_path('Livewire/Settings/Index.php'));
expect($component)
->toContain('$imageRef = escapeshellarg(coolifyHelperImage().":{$version}");')
->not->toContain('"ghcr.io/coollabsio/coolify-helper:{$version}"');
});

View file

@ -0,0 +1,19 @@
<?php
it('provides a local helper build and deployment workflow', function () {
$script = dirname(__DIR__, 2).'/scripts/dev-helper';
expect($script)->toBeFile()
->and(is_executable($script))->toBeTrue();
exec('bash -n '.escapeshellarg($script), $output, $exitCode);
expect($exitCode)->toBe(0)
->and(file_get_contents($script))
->toContain('build)')
->toContain('use)')
->toContain('verify)')
->toContain('deploy)')
->toContain('reset)')
->toContain('test)');
});

View file

@ -0,0 +1,12 @@
<?php
it('installs the current Docker CLI in the Coolify helper image', function () {
$dockerfile = file_get_contents(dirname(__DIR__, 2).'/docker/coolify-helper/Dockerfile');
expect($dockerfile)
->toContain('ARG DOCKER_VERSION=29.7.2')
->toContain('ARG DOCKER_COMPOSE_VERSION=5.5.0')
->toContain('ARG DOCKER_BUILDX_VERSION=0.36.1')
->toContain('https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz')
->toContain('https://download.docker.com/linux/static/stable/aarch64/docker-${DOCKER_VERSION}.tgz')
->toMatch('/chmod \+x [^\n]*\/usr\/bin\/docker/');
});