diff --git a/build-tools/docker/Dockerfile.builder b/build-tools/docker/Dockerfile.builder index 1cffd0eb7..61c09b3f2 100644 --- a/build-tools/docker/Dockerfile.builder +++ b/build-tools/docker/Dockerfile.builder @@ -8,11 +8,19 @@ # TODO: dockerfile 1.7 syntax allows specifying --exclude for COPY, so the same can be done # without an additional stage. But at the moment of writing this it's still experimental. # Switch to using it when it becomes stable. -FROM rust as source + +# Note: the base image here doesn't really matter, we just use the same one as in the "builder" +# stage below. +FROM rust:bookworm as source COPY . /src RUN rm -r /src/build-tools -FROM rust AS builder +# Note: the builder image should use the same or an older distro compared to the runner images, +# so that its GLIBC version is also the same or older. Otherwise the built executables have +# a chance to get a dependency on a newer version of some GLIBC symbol that is not present +# in the runner's GLIBC, and the executables won't work. +# TODO: consider producing musl-based executables instead. +FROM rust:bookworm AS builder WORKDIR /usr/src/ diff --git a/build-tools/docker/Dockerfile.runner-base b/build-tools/docker/Dockerfile.runner-base index 2512b9ba2..d2101cbd4 100644 --- a/build-tools/docker/Dockerfile.runner-base +++ b/build-tools/docker/Dockerfile.runner-base @@ -1,3 +1,5 @@ +# Note: this must be consistent with the builder image's base image, see the corresponding comment +# in Dockerfile.builder. FROM debian:bookworm-slim RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/* diff --git a/build-tools/docker/build.py b/build-tools/docker/build.py index 4f17feb1a..04324cf3a 100644 --- a/build-tools/docker/build.py +++ b/build-tools/docker/build.py @@ -34,6 +34,9 @@ def build_docker_image(dockerfile_path, image_name, tags, num_jobs=None): if num_jobs: command += f" --build-arg NUM_JOBS={num_jobs}" + # Force the amd64 platform in case we're building on an arm-based one. + command += " --platform linux/amd64" + # Note: "plain" output is more verbose, but it makes it easier to understand what went wrong # when a problem occurs. command += " --progress=plain" @@ -43,31 +46,22 @@ def build_docker_image(dockerfile_path, image_name, tags, num_jobs=None): # Run the command subprocess.check_call(command, shell=True) print(f"Built {image_name} successfully (the tags are: {full_tags}).") - except Exception as error: - print(f"Error occurred: {error}") + except subprocess.CalledProcessError as error: + print(f"Failed to build {image_name}: {error}") exit(1) # stop the build -def push_docker_image(image_name, version, latest=False): - # Docker tag command - full_image_name = f"{image_name}:{version}" - if latest: - latest_image_name = f"{image_name}:latest" - tag_command = f"docker tag {full_image_name} {latest_image_name}" - subprocess.check_call(tag_command, shell=True) - - # Docker push command - push_command = f"docker push {full_image_name}" - subprocess.check_call(push_command, shell=True) - - # if latest flag is true, push the image with 'latest' tag - if latest: - push_command_latest = f"docker push {latest_image_name}" - subprocess.check_call(push_command_latest, shell=True) +def push_docker_image(image_name, tags): + for tag in tags: + full_image_name = f"{image_name}:{tag}" + push_command = f"docker push {full_image_name}" - print(f"Pushed {full_image_name} successfully.") - if latest: - print(f"Pushed {latest_image_name} successfully.") + try: + subprocess.check_call(push_command, shell=True) + print(f"Pushed {full_image_name} successfully.") + except subprocess.CalledProcessError as error: + print(f"Failed to push {full_image_name}: {error}") + exit(1) # stop the build def delete_docker_image(image_name, version): @@ -81,8 +75,9 @@ def delete_docker_image(image_name, version): try: subprocess.check_call(command, shell=True) print(f"Deleted {full_image_name} successfully.") - except subprocess.CalledProcessError: - print(f"Failed to delete {full_image_name}.") + except subprocess.CalledProcessError as error: + print(f"Failed to delete {full_image_name}: {error}") + # No need to fail the build here def build_instances(tags, docker_hub_user, num_jobs): @@ -106,20 +101,20 @@ def build_instances(tags, docker_hub_user, num_jobs): # delete_docker_image("mintlayer-builder", "latest") -def push_instances(docker_hub_user, version, latest): - push_docker_image(f"{docker_hub_user}/node-daemon", version, latest) - push_docker_image(f"{docker_hub_user}/api-blockchain-scanner-daemon", version, latest) - push_docker_image(f"{docker_hub_user}/api-web-server", version, latest) - push_docker_image(f"{docker_hub_user}/wallet-cli", version, latest) - push_docker_image(f"{docker_hub_user}/wallet-rpc-daemon", version, latest) - push_docker_image(f"{docker_hub_user}/dns-server", version, latest) +def push_instances(docker_hub_user, tags): + push_docker_image(f"{docker_hub_user}/node-daemon", tags) + push_docker_image(f"{docker_hub_user}/api-blockchain-scanner-daemon", tags) + push_docker_image(f"{docker_hub_user}/api-web-server", tags) + push_docker_image(f"{docker_hub_user}/wallet-cli", tags) + push_docker_image(f"{docker_hub_user}/wallet-rpc-daemon", tags) + push_docker_image(f"{docker_hub_user}/dns-server", tags) def main(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--push', action='store_true', help='Push the Docker image to Docker Hub') parser.add_argument('--docker-hub-user', help='Docker Hub username', default='mintlayer') - parser.add_argument('--latest', action='store_true', help='Tag the Docker image as latest while pushing') + parser.add_argument('--latest', action='store_true', help='Tag the Docker image as latest') parser.add_argument('--build', type=lambda x: (str(x).lower() == 'true'), default=True, help="Set to false avoid the build") parser.add_argument('--version', help='Override version number', default=None) parser.add_argument('--num_jobs', help='Number of parallel jobs', default=(os.cpu_count() or 1)) @@ -127,14 +122,23 @@ def main(): args = parser.parse_args() version = args.version if args.version else get_cargo_version("Cargo.toml") - tags = [version, *args.local_tags] + # Note: the CI currently takes the version from the release tag, so it always starts with "v", + # but the version from Cargo.toml doesn't have this prefix. + version = version.removeprefix("v") + + # We want to push both "X.Y.Z" and "vX.Y.Z". + tags_to_push = [version, f"v{version}"] + if args.latest: + tags_to_push.append("latest") + + all_tags = args.local_tags + tags_to_push if args.build: - build_instances(tags, args.docker_hub_user, args.num_jobs) + build_instances(all_tags, args.docker_hub_user, args.num_jobs) # Only push the image if the --push flag is provided if args.push: - push_instances(args.docker_hub_user, version, args.latest) + push_instances(args.docker_hub_user, tags_to_push) if __name__ == "__main__":