2025-09-04 10:51:44 +09:00
|
|
|
#!/usr/bin/env bash
|
2025-12-02 10:05:11 +09:00
|
|
|
# 🐳 Build production Docker images for Plesk deployment
|
|
|
|
|
# Features: Parallel builds, BuildKit, compressed tarballs
|
2025-09-04 10:51:44 +09:00
|
|
|
|
|
|
|
|
set -Eeuo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
# Configuration (override via env vars or flags)
|
|
|
|
|
IMAGE_FRONTEND="${IMAGE_FRONTEND_NAME:-portal-frontend}"
|
|
|
|
|
IMAGE_BACKEND="${IMAGE_BACKEND_NAME:-portal-backend}"
|
2025-09-04 10:51:44 +09:00
|
|
|
IMAGE_TAG="${IMAGE_TAG:-}"
|
|
|
|
|
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT}"
|
2025-12-02 10:05:11 +09:00
|
|
|
PUSH_REMOTE="${PUSH_REMOTE:-}"
|
|
|
|
|
PARALLEL="${PARALLEL_BUILD:-1}"
|
|
|
|
|
COMPRESS="${COMPRESS:-1}"
|
|
|
|
|
USE_LATEST_FILENAME="${USE_LATEST_FILENAME:-1}" # Default: save as .latest.tar.gz
|
|
|
|
|
SAVE_TARS=1
|
2025-09-04 10:51:44 +09:00
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
# Colors
|
|
|
|
|
G='\033[0;32m' Y='\033[1;33m' R='\033[0;31m' B='\033[0;34m' N='\033[0m'
|
|
|
|
|
log() { echo -e "${G}[BUILD]${N} $*"; }
|
|
|
|
|
info() { echo -e "${B}[BUILD]${N} $*"; }
|
|
|
|
|
warn() { echo -e "${Y}[BUILD]${N} $*"; }
|
|
|
|
|
fail() { echo -e "${R}[BUILD] ERROR:${N} $*"; exit 1; }
|
2025-09-04 10:51:44 +09:00
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
|
cat <<EOF
|
2025-12-02 10:05:11 +09:00
|
|
|
Build Docker images and save tarballs for Plesk.
|
2025-09-04 10:51:44 +09:00
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
Usage: $0 [OPTIONS]
|
2025-09-04 10:51:44 +09:00
|
|
|
|
|
|
|
|
Options:
|
2025-12-02 10:05:11 +09:00
|
|
|
--tag <tag> Version tag for image (default: YYYYMMDD-gitsha)
|
|
|
|
|
--output <dir> Output directory (default: project root)
|
|
|
|
|
--push <registry> Push to registry after build
|
|
|
|
|
--no-save Build only, no tar files
|
|
|
|
|
--no-compress Save as .tar instead of .tar.gz
|
|
|
|
|
--versioned Name files with version tag (default: .latest.tar.gz)
|
|
|
|
|
--sequential Build one at a time (default: parallel)
|
|
|
|
|
-h, --help Show this help
|
2025-09-04 10:51:44 +09:00
|
|
|
|
|
|
|
|
Examples:
|
2025-12-02 10:05:11 +09:00
|
|
|
$0 # Output: portal-frontend.latest.tar.gz (default)
|
|
|
|
|
$0 --versioned # Output: portal-frontend.20251201-abc123.tar.gz
|
|
|
|
|
$0 --tag v1.2.3 --versioned # Output: portal-frontend.v1.2.3.tar.gz
|
|
|
|
|
$0 --sequential --no-save # Debug build
|
2025-09-04 10:51:44 +09:00
|
|
|
EOF
|
2025-12-02 10:05:11 +09:00
|
|
|
exit 0
|
2025-09-04 10:51:44 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
# Parse arguments
|
2025-09-04 10:51:44 +09:00
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
2025-12-02 10:05:11 +09:00
|
|
|
--tag) IMAGE_TAG="${2:-}"; shift 2 ;;
|
|
|
|
|
--output) OUTPUT_DIR="${2:-}"; shift 2 ;;
|
|
|
|
|
--push) PUSH_REMOTE="${2:-}"; shift 2 ;;
|
|
|
|
|
--no-save) SAVE_TARS=0; shift ;;
|
|
|
|
|
--no-compress) COMPRESS=0; shift ;;
|
|
|
|
|
--versioned) USE_LATEST_FILENAME=0; shift ;;
|
|
|
|
|
--sequential) PARALLEL=0; shift ;;
|
|
|
|
|
-h|--help) usage ;;
|
|
|
|
|
*) fail "Unknown option: $1" ;;
|
2025-09-04 10:51:44 +09:00
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
# Validation
|
|
|
|
|
command -v docker >/dev/null 2>&1 || fail "Docker required"
|
2025-09-04 10:51:44 +09:00
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
|
[[ -f apps/portal/Dockerfile ]] || fail "Missing apps/portal/Dockerfile"
|
2025-12-02 10:05:11 +09:00
|
|
|
[[ -f apps/bff/Dockerfile ]] || fail "Missing apps/bff/Dockerfile"
|
|
|
|
|
|
|
|
|
|
# Auto-generate tag if not provided
|
|
|
|
|
[[ -z "$IMAGE_TAG" ]] && IMAGE_TAG="$(date +%Y%m%d)-$(git rev-parse --short HEAD 2>/dev/null || echo 'local')"
|
|
|
|
|
|
|
|
|
|
# Enable BuildKit
|
|
|
|
|
export DOCKER_BUILDKIT=1
|
|
|
|
|
|
|
|
|
|
# Build args
|
|
|
|
|
NEXT_PUBLIC_API_BASE="${NEXT_PUBLIC_API_BASE:-/api}"
|
|
|
|
|
NEXT_PUBLIC_APP_NAME="${NEXT_PUBLIC_APP_NAME:-Customer Portal}"
|
|
|
|
|
GIT_SOURCE="$(git config --get remote.origin.url 2>/dev/null || echo unknown)"
|
|
|
|
|
|
|
|
|
|
log "🏷️ Tag: ${IMAGE_TAG}"
|
|
|
|
|
|
|
|
|
|
LOG_DIR="${OUTPUT_DIR}/.build-logs"
|
|
|
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
|
|
|
|
|
|
build_frontend() {
|
|
|
|
|
local logfile="$LOG_DIR/frontend.log"
|
|
|
|
|
docker build -f apps/portal/Dockerfile \
|
|
|
|
|
--build-arg "NEXT_PUBLIC_API_BASE=${NEXT_PUBLIC_API_BASE}" \
|
|
|
|
|
--build-arg "NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME}" \
|
|
|
|
|
--build-arg "NEXT_PUBLIC_APP_VERSION=${IMAGE_TAG}" \
|
|
|
|
|
-t "${IMAGE_FRONTEND}:latest" -t "${IMAGE_FRONTEND}:${IMAGE_TAG}" \
|
|
|
|
|
--label "org.opencontainers.image.version=${IMAGE_TAG}" \
|
|
|
|
|
--label "org.opencontainers.image.source=${GIT_SOURCE}" \
|
|
|
|
|
. > "$logfile" 2>&1
|
|
|
|
|
local exit_code=$?
|
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
|
|
|
log "✅ Frontend done ($(tail -1 "$logfile" | grep -oP 'DONE \K[0-9.]+s' || echo 'complete'))"
|
|
|
|
|
else
|
|
|
|
|
warn "❌ Frontend FAILED - see $logfile"
|
|
|
|
|
tail -20 "$logfile"
|
|
|
|
|
fi
|
|
|
|
|
return $exit_code
|
|
|
|
|
}
|
2025-09-04 10:51:44 +09:00
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
build_backend() {
|
|
|
|
|
local logfile="$LOG_DIR/backend.log"
|
|
|
|
|
docker build -f apps/bff/Dockerfile \
|
|
|
|
|
-t "${IMAGE_BACKEND}:latest" -t "${IMAGE_BACKEND}:${IMAGE_TAG}" \
|
|
|
|
|
--label "org.opencontainers.image.version=${IMAGE_TAG}" \
|
|
|
|
|
--label "org.opencontainers.image.source=${GIT_SOURCE}" \
|
|
|
|
|
. > "$logfile" 2>&1
|
|
|
|
|
local exit_code=$?
|
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
|
|
|
log "✅ Backend done ($(tail -1 "$logfile" | grep -oP 'DONE \K[0-9.]+s' || echo 'complete'))"
|
2025-09-04 10:51:44 +09:00
|
|
|
else
|
2025-12-02 10:05:11 +09:00
|
|
|
warn "❌ Backend FAILED - see $logfile"
|
|
|
|
|
tail -20 "$logfile"
|
2025-09-04 10:51:44 +09:00
|
|
|
fi
|
2025-12-02 10:05:11 +09:00
|
|
|
return $exit_code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Build images
|
|
|
|
|
START=$(date +%s)
|
|
|
|
|
|
|
|
|
|
if [[ "$PARALLEL" -eq 1 ]]; then
|
|
|
|
|
log "🚀 Parallel build (logs: $LOG_DIR/)"
|
|
|
|
|
log "🔨 Building frontend..."
|
|
|
|
|
log "🔨 Building backend..."
|
|
|
|
|
|
|
|
|
|
build_frontend & FE_PID=$!
|
|
|
|
|
build_backend & BE_PID=$!
|
|
|
|
|
|
|
|
|
|
# Show progress dots while waiting
|
|
|
|
|
while kill -0 $FE_PID 2>/dev/null || kill -0 $BE_PID 2>/dev/null; do
|
|
|
|
|
printf "."
|
|
|
|
|
sleep 5
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# Check results
|
|
|
|
|
wait $FE_PID || fail "Frontend build failed - check $LOG_DIR/frontend.log"
|
|
|
|
|
wait $BE_PID || fail "Backend build failed - check $LOG_DIR/backend.log"
|
|
|
|
|
else
|
|
|
|
|
log "🔧 Sequential build..."
|
|
|
|
|
log "🔨 Building frontend..."
|
|
|
|
|
build_frontend || fail "Frontend build failed"
|
|
|
|
|
log "🔨 Building backend..."
|
|
|
|
|
build_backend || fail "Backend build failed"
|
2025-09-04 10:51:44 +09:00
|
|
|
fi
|
|
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
BUILD_TIME=$(($(date +%s) - START))
|
|
|
|
|
log "⏱️ Built in ${BUILD_TIME}s"
|
|
|
|
|
|
|
|
|
|
# Save tarballs
|
|
|
|
|
if [[ "$SAVE_TARS" -eq 1 ]]; then
|
2025-09-04 10:51:44 +09:00
|
|
|
mkdir -p "$OUTPUT_DIR"
|
2025-12-02 10:05:11 +09:00
|
|
|
SAVE_START=$(date +%s)
|
|
|
|
|
|
|
|
|
|
# Determine filename suffix
|
|
|
|
|
if [[ "$USE_LATEST_FILENAME" -eq 1 ]]; then
|
|
|
|
|
FILE_TAG="latest"
|
|
|
|
|
else
|
|
|
|
|
FILE_TAG="$IMAGE_TAG"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "$COMPRESS" -eq 1 ]]; then
|
|
|
|
|
# Pick fastest available compressor: pigz (parallel) > gzip
|
|
|
|
|
if command -v pigz >/dev/null 2>&1; then
|
|
|
|
|
COMPRESSOR="pigz -p $(nproc)" # Use all CPU cores
|
|
|
|
|
COMP_NAME="pigz"
|
|
|
|
|
else
|
|
|
|
|
COMPRESSOR="gzip -1" # Fast mode if no pigz
|
|
|
|
|
COMP_NAME="gzip"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
FE_TAR="$OUTPUT_DIR/${IMAGE_FRONTEND}.${FILE_TAG}.tar.gz"
|
|
|
|
|
BE_TAR="$OUTPUT_DIR/${IMAGE_BACKEND}.${FILE_TAG}.tar.gz"
|
|
|
|
|
log "💾 Compressing with $COMP_NAME..."
|
|
|
|
|
|
|
|
|
|
(docker save "${IMAGE_FRONTEND}:latest" | $COMPRESSOR > "$FE_TAR") &
|
|
|
|
|
(docker save "${IMAGE_BACKEND}:latest" | $COMPRESSOR > "$BE_TAR") &
|
|
|
|
|
wait
|
|
|
|
|
else
|
|
|
|
|
FE_TAR="$OUTPUT_DIR/${IMAGE_FRONTEND}.${FILE_TAG}.tar"
|
|
|
|
|
BE_TAR="$OUTPUT_DIR/${IMAGE_BACKEND}.${FILE_TAG}.tar"
|
|
|
|
|
log "💾 Saving uncompressed tarballs..."
|
|
|
|
|
docker save -o "$FE_TAR" "${IMAGE_FRONTEND}:latest" &
|
|
|
|
|
docker save -o "$BE_TAR" "${IMAGE_BACKEND}:latest" &
|
|
|
|
|
wait
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
SAVE_TIME=$(($(date +%s) - SAVE_START))
|
|
|
|
|
sha256sum "$FE_TAR" > "${FE_TAR}.sha256"
|
|
|
|
|
sha256sum "$BE_TAR" > "${BE_TAR}.sha256"
|
2025-09-04 10:51:44 +09:00
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
log "✅ Saved in ${SAVE_TIME}s:"
|
|
|
|
|
printf " %-50s %s\n" "$FE_TAR" "$(du -h "$FE_TAR" | cut -f1)"
|
|
|
|
|
printf " %-50s %s\n" "$BE_TAR" "$(du -h "$BE_TAR" | cut -f1)"
|
2025-09-04 10:51:44 +09:00
|
|
|
fi
|
|
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
# Push to registry
|
|
|
|
|
if [[ -n "$PUSH_REMOTE" ]]; then
|
|
|
|
|
log "📤 Pushing to ${PUSH_REMOTE}..."
|
|
|
|
|
for img in "${IMAGE_FRONTEND}" "${IMAGE_BACKEND}"; do
|
|
|
|
|
for tag in "latest" "${IMAGE_TAG}"; do
|
|
|
|
|
docker tag "${img}:${tag}" "${PUSH_REMOTE}/${img}:${tag}"
|
|
|
|
|
docker push "${PUSH_REMOTE}/${img}:${tag}" &
|
|
|
|
|
done
|
|
|
|
|
done
|
|
|
|
|
wait
|
|
|
|
|
log "✅ Pushed"
|
|
|
|
|
fi
|
2025-09-04 10:51:44 +09:00
|
|
|
|
2025-12-02 10:05:11 +09:00
|
|
|
TOTAL_TIME=$(($(date +%s) - START))
|
|
|
|
|
log "🎉 Complete in ${TOTAL_TIME}s"
|
|
|
|
|
echo ""
|
|
|
|
|
info "Next: Upload to Plesk, then:"
|
|
|
|
|
if [[ "$COMPRESS" -eq 1 ]]; then
|
|
|
|
|
echo " gunzip -c ${IMAGE_FRONTEND}.${FILE_TAG}.tar.gz | docker load"
|
|
|
|
|
echo " gunzip -c ${IMAGE_BACKEND}.${FILE_TAG}.tar.gz | docker load"
|
|
|
|
|
else
|
|
|
|
|
echo " docker load -i ${IMAGE_FRONTEND}.${FILE_TAG}.tar"
|
|
|
|
|
echo " docker load -i ${IMAGE_BACKEND}.${FILE_TAG}.tar"
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$USE_LATEST_FILENAME" -eq 0 ]]; then
|
|
|
|
|
echo " Update Portainer with tag: ${IMAGE_TAG}"
|
|
|
|
|
fi
|