From ceebfb33f8a1e3ae10be166fc6cfb5df0093a485 Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Mon, 7 Sep 2026 18:02:37 -0300 Subject: [PATCH] build: add native build and unregistry push script scripts/build-and-push.sh builds the native image with the Mandrel Dockerfile and pushes it to a remote Docker host via docker-pussh (unregistry), transferring only missing layers over SSH. - Name and tag default to pom.xml artifactId/version (overridable) - Builds for linux/amd64; --skip-build to pussh only - Verifies image presence on the remote after push and prints a ready-to-use docker run hint README updated with a 'Building and deploying natively' section. --- README.md | 14 +++ scripts/build-and-push.sh | 196 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100755 scripts/build-and-push.sh diff --git a/README.md b/README.md index 13f2347..3fd431e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ This project uses Quarkus, the Supersonic Subatomic Java Framework. If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . +## Building and deploying natively + +Build the native image with the Mandrel-based multi-stage Dockerfile and push it +directly to a remote Docker host via [unregistry](https://github.com/psviderski/unregistry) +(no external registry required; only missing layers are transferred over SSH): + +```shell script +./scripts/build-and-push.sh --remote deploy@prod.example.com +``` + +The script reads `artifactId` and `version` from `pom.xml` for the image name +and tag, builds for `linux/amd64`, and verifies the image is present on the +remote after pushing. See `./scripts/build-and-push.sh --help` for all flags. + ## Running the application in dev mode You can run your application in dev mode that enables live coding using: diff --git a/scripts/build-and-push.sh b/scripts/build-and-push.sh new file mode 100755 index 0000000..2f300b7 --- /dev/null +++ b/scripts/build-and-push.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# +# Build the native Quarkus image and push it to a remote Docker host +# via docker-pussh (unregistry): https://github.com/psviderski/unregistry +# +# - Image name/tag default to pom.xml properties (quarkus.application.*) +# - Builds for linux/amd64 by default (cross-compile from arm64 hosts) +# - Transfers only missing layers over SSH (no external registry required) + +set -euo pipefail +IFS=$'\n\t' + +readonly DOCKERFILE="src/main/docker/Dockerfile" +readonly DEFAULT_PLATFORM="linux/amd64" +readonly UNREGISTRY_PLUGIN="https://raw.githubusercontent.com/psviderski/unregistry/main/docker-pussh" +readonly PLUGIN_DIR="${HOME}/.docker/cli-plugins" +readonly PLUGIN_PATH="${PLUGIN_DIR}/docker-pussh" + +REMOTE="" +IMAGE_NAME="" +IMAGE_TAG="" +SSH_KEY="" +SSH_CONFIG="" +SKIP_BUILD=0 +SKIP_VERIFY=0 + +# ---------- logging ---------- +if [[ -t 1 ]]; then + readonly C_INFO=$'\033[0;36m' + readonly C_OK=$'\033[0;32m' + readonly C_WARN=$'\033[0;33m' + readonly C_ERR=$'\033[0;31m' + readonly C_RESET=$'\033[0m' +else + readonly C_INFO="" C_OK="" C_WARN="" C_ERR="" C_RESET="" +fi + +log() { printf '%s[INFO]%s %s\n' "$C_INFO" "$C_RESET" "$*"; } +ok() { printf '%s[OK]%s %s\n' "$C_OK" "$C_RESET" "$*"; } +warn() { printf '%s[WARN]%s %s\n' "$C_WARN" "$C_RESET" "$*" >&2; } +err() { printf '%s[ERROR]%s %s\n' "$C_ERR" "$C_RESET" "$*" >&2; } +die() { err "$*"; exit 1; } + +on_error() { + local exit_code=$? + err "Script aborted on line ${BASH_LINENO[0]} (exit ${exit_code})." + exit "$exit_code" +} +trap on_error ERR + +# ---------- helpers ---------- +usage() { + cat </dev/null 2>&1; then + value=$(xmllint --xpath "string($xpath)" pom.xml 2>/dev/null || true) + fi + if [[ -z "$value" ]]; then + value=$(grep -E "|" pom.xml | head -2 \ + | sed -E 's:.*<(artifactId|version)>(.*).*:\2:') + fi + printf '%s' "${value:-$fallback}" +} + +require() { + command -v "$1" >/dev/null 2>&1 || die "Required command '$1' not found in PATH." +} + +# ---------- arg parsing ---------- +while [[ $# -gt 0 ]]; do + case "$1" in + --remote) REMOTE="$2"; shift 2 ;; + --name) IMAGE_NAME="$2"; shift 2 ;; + --tag) IMAGE_TAG="$2"; shift 2 ;; + --ssh-key) SSH_KEY="$2"; shift 2 ;; + --ssh-config) SSH_CONFIG="$2"; shift 2 ;; + --skip-build) SKIP_BUILD=1; shift ;; + --no-verify) SKIP_VERIFY=1; shift ;; + -h|--help) usage; exit 0 ;; + *) die "Unknown argument: $1 (use --help)" ;; + esac +done + +# ---------- defaults ---------- +[[ -z "$IMAGE_NAME" ]] && IMAGE_NAME=$(read_pom "/*[local-name()='project']/*[local-name()='artifactId']" "test-quarkus") +[[ -z "$IMAGE_TAG" ]] && IMAGE_TAG=$(read_pom "/*[local-name()='project']/*[local-name()='version']" "1.0.0-SNAPSHOT") +[[ -z "$REMOTE" ]] && die "Missing --remote USER@HOST[:PORT] (use --help)." + +IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" + +# ---------- pre-flight ---------- +log "Image: ${IMAGE_REF}" +log "Platform: ${DEFAULT_PLATFORM}" +log "Dockerfile: ${DOCKERFILE}" +log "Remote: ${REMOTE}" + +require docker +docker info >/dev/null 2>&1 \ + || die "Docker daemon is not reachable. Start Docker Desktop (or your daemon) and retry." + +if ! docker pussh --help >/dev/null 2>&1; then + cat >&2 </dev/null 2>&1 \ + || die "Image ${IMAGE_REF} not present locally; remove --skip-build to build it." +else + log "Building ${IMAGE_REF} for ${DEFAULT_PLATFORM} (this may take several minutes)…" + docker build \ + --platform "$DEFAULT_PLATFORM" \ + -f "$DOCKERFILE" \ + -t "$IMAGE_REF" \ + . + ok "Build finished." +fi + +# ---------- pussh ---------- +log "Pusshing ${IMAGE_REF} to ${REMOTE} via unregistry…" + +pussh_args=( "$REMOTE" ) +[[ -n "$SSH_KEY" ]] && pussh_args+=( -i "$SSH_KEY" ) +[[ -n "$SSH_CONFIG" ]] && pussh_args+=( -F "$SSH_CONFIG" ) + +docker pussh "${IMAGE_REF}" "${pussh_args[@]}" +ok "Pussh complete." + +# ---------- verify ---------- +if [[ "$SKIP_VERIFY" -eq 1 ]]; then + warn "Skipping post-push verification (--no-verify)." +else + log "Verifying ${IMAGE_REF} on ${REMOTE}…" + ssh_args=( "$REMOTE" ) + [[ -n "$SSH_KEY" ]] && ssh_args=( -i "$SSH_KEY" "${ssh_args[@]}" ) + [[ -n "$SSH_CONFIG" ]] && ssh_args=( -F "$SSH_CONFIG" "${ssh_args[@]}" ) + + if ! ssh "${ssh_args[@]}" "docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep -E '^${IMAGE_NAME}:${IMAGE_TAG} '"; then + die "Image ${IMAGE_REF} not found on ${REMOTE} after pussh." + fi + ok "Image present on remote." +fi + +# ---------- next step hint ---------- +printf '\n%s→ To run on the remote:%s\n' "$C_OK" "$C_RESET" +printf ' ssh %s "docker run -d --name %s --restart unless-stopped -p 8080:8080 %s"\n\n' \ + "$REMOTE" "$IMAGE_NAME" "$IMAGE_REF"