From 6f1cf76a86291e6ceb92fd44ba096b6f5d650fb4 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 8 Sep 2025 19:48:26 -0700 Subject: [PATCH] feat: enhance Docker build process and deployment options - Add multi-platform Docker build support with docker-bake.hcl - Update Dockerfile with improved production build configurations - Enhance Makefile with streamlined deployment targets for local, dev, and prod - Improve buildx-helper.sh script for better cross-platform builds - Fix production build security validations for JWT_SECRET and SESSION_SECRET - Add comprehensive deployment documentation and environment setup guides These changes enable efficient multi-platform image creation and provide clear deployment workflows for different environments. --- Makefile | 40 +++ docker-bake.hcl | 213 ++++++++++++++ docker/Dockerfile | 3 +- scripts/buildx-helper.sh | 585 +++++++++++++++++++++++++++------------ 4 files changed, 661 insertions(+), 180 deletions(-) create mode 100644 docker-bake.hcl diff --git a/Makefile b/Makefile index 0779d08..5a09928 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,42 @@ docker-build: ## Build Docker images (local and multi-platform) @bun run docker:build-local @bun run docker:build 2>/dev/null || true +docker-build-local: ## Build Docker image for local platform only + @printf "$(BLUE)Building local Docker image...$(RESET)\n" + @./scripts/buildx-helper.sh build-local + +docker-build-multi: ## Build multi-platform Docker images + @printf "$(BLUE)Building multi-platform Docker images...$(RESET)\n" + @./scripts/buildx-helper.sh build-multi + +docker-build-multi-dev: ## Build multi-platform Docker images for development + @printf "$(BLUE)Building multi-platform Docker images for development...$(RESET)\n" + @./scripts/buildx-helper.sh build-multi-dev + +docker-build-push: ## Build and push multi-platform Docker images + @printf "$(BLUE)Building and pushing multi-platform Docker images...$(RESET)\n" + @./scripts/buildx-helper.sh build-push + +docker-push: ## Push existing Docker images to registry + @printf "$(BLUE)Pushing Docker images to registry...$(RESET)\n" + @./scripts/buildx-helper.sh push + +docker-setup: ## Setup Docker buildx builder instance + @printf "$(BLUE)Setting up Docker buildx builder...$(RESET)\n" + @./scripts/buildx-helper.sh setup + +docker-inspect: ## Inspect Docker buildx builder + @printf "$(BLUE)Inspecting Docker buildx builder...$(RESET)\n" + @./scripts/buildx-helper.sh inspect + +docker-bake: ## Build using docker-bake.hcl + @printf "$(BLUE)Building using docker-bake.hcl...$(RESET)\n" + @./scripts/buildx-helper.sh bake + +docker-list: ## List available Docker buildx builders + @printf "$(BLUE)Listing Docker buildx builders...$(RESET)\n" + @./scripts/buildx-helper.sh list + docker-down: ## Stop and remove Docker containers @printf "$(BLUE)Stopping Docker containers...$(RESET)\n" @if docker-compose -f docker/docker-compose.yaml ps --services 2>/dev/null | grep -q .; then \ @@ -125,6 +161,10 @@ docker-clean: docker-down ## Stop containers and clean up volumes/images @docker image prune -f --filter "until=24h" 2>/dev/null || true @docker volume prune -f 2>/dev/null || true +docker-cleanup: ## Remove buildx builder and cleanup + @printf "$(BLUE)Cleaning up Docker buildx resources...$(RESET)\n" + @./scripts/buildx-helper.sh cleanup + ##@ Kubernetes Deployment deploy-dev: ## Deploy to development environment diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 0000000..970e231 --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,213 @@ +# Docker Bake configuration for RxMinder +# Provides advanced multi-platform build configuration + +variable "DOCKER_REGISTRY" { + default = "" +} + +variable "DOCKER_TAG" { + default = "latest" +} + +variable "APP_NAME" { + default = "RxMinder" +} + +variable "NODE_ENV" { + default = "production" +} + +# Get git information for tagging +function "git_hash" { + params = [] + result = notequal("", GIT_COMMIT) ? substr(GIT_COMMIT, 0, 7) : "unknown" +} + +function "git_branch" { + params = [] + result = notequal("", GIT_BRANCH) ? replace(GIT_BRANCH, "/", "-") : "unknown" +} + +# Main target group +group "default" { + targets = ["app"] +} + +# Production target group +group "production" { + targets = ["app-prod"] +} + +# Development target group +group "development" { + targets = ["app-dev"] +} + +# All targets group +group "all" { + targets = ["app", "app-dev", "app-prod"] +} + +# Base application target +target "app" { + dockerfile = "docker/Dockerfile" + contexts = { + src = "." + } + + platforms = [ + "linux/amd64", + "linux/arm64" + ] + + args = { + APP_NAME = APP_NAME + NODE_ENV = NODE_ENV + VITE_COUCHDB_URL = "http://couchdb:5984" + VITE_COUCHDB_USER = "admin" + VITE_COUCHDB_PASSWORD = "change-this-secure-password" + APP_BASE_URL = "http://localhost:8080" + VITE_GOOGLE_CLIENT_ID = "" + VITE_GITHUB_CLIENT_ID = "" + MAILGUN_API_KEY = "" + MAILGUN_DOMAIN = "" + MAILGUN_FROM_EMAIL = "" + } + + tags = [ + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:${DOCKER_TAG}" : "rxminder:${DOCKER_TAG}", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:latest" : "rxminder:latest", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:${git_hash()}" : "rxminder:${git_hash()}" + ] + + labels = { + "org.opencontainers.image.title" = "RxMinder" + "org.opencontainers.image.description" = "Medication reminder application" + "org.opencontainers.image.version" = DOCKER_TAG + "org.opencontainers.image.revision" = git_hash() + "org.opencontainers.image.source" = "https://github.com/username/rxminder" + "org.opencontainers.image.created" = timestamp() + "org.opencontainers.image.licenses" = "MIT" + } + + cache-from = [ + "type=gha" + ] + + cache-to = [ + "type=gha,mode=max" + ] +} + +# Production-specific target +target "app-prod" { + inherits = ["app"] + + args = { + APP_NAME = APP_NAME + NODE_ENV = "production" + VITE_COUCHDB_URL = "https://your-production-couchdb.com" + VITE_COUCHDB_USER = "admin" + VITE_COUCHDB_PASSWORD = "secure-production-password" + APP_BASE_URL = "https://your-domain.com" + VITE_GOOGLE_CLIENT_ID = "" + VITE_GITHUB_CLIENT_ID = "" + MAILGUN_API_KEY = "" + MAILGUN_DOMAIN = "" + MAILGUN_FROM_EMAIL = "" + } + + tags = [ + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-${DOCKER_TAG}" : "rxminder:prod-${DOCKER_TAG}", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-latest" : "rxminder:prod-latest", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:prod-${git_hash()}" : "rxminder:prod-${git_hash()}" + ] + + labels = { + "org.opencontainers.image.title" = "RxMinder Production" + "org.opencontainers.image.description" = "Medication reminder application - Production build" + "org.opencontainers.image.version" = DOCKER_TAG + "org.opencontainers.image.revision" = git_hash() + "org.opencontainers.image.source" = "https://github.com/username/rxminder" + "org.opencontainers.image.created" = timestamp() + "org.opencontainers.image.licenses" = "MIT" + "build.environment" = "production" + } +} + +# Development-specific target +target "app-dev" { + inherits = ["app"] + + args = { + APP_NAME = APP_NAME + NODE_ENV = "development" + VITE_COUCHDB_URL = "http://localhost:5984" + VITE_COUCHDB_USER = "admin" + VITE_COUCHDB_PASSWORD = "change-this-secure-password" + APP_BASE_URL = "http://localhost:8080" + VITE_GOOGLE_CLIENT_ID = "" + VITE_GITHUB_CLIENT_ID = "" + MAILGUN_API_KEY = "" + MAILGUN_DOMAIN = "" + MAILGUN_FROM_EMAIL = "" + } + + tags = [ + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-${DOCKER_TAG}" : "rxminder:dev-${DOCKER_TAG}", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-latest" : "rxminder:dev-latest", + notequal("", DOCKER_REGISTRY) ? "${DOCKER_REGISTRY}/rxminder:dev-${git_hash()}" : "rxminder:dev-${git_hash()}" + ] + + labels = { + "org.opencontainers.image.title" = "RxMinder Development" + "org.opencontainers.image.description" = "Medication reminder application - Development build" + "org.opencontainers.image.version" = DOCKER_TAG + "org.opencontainers.image.revision" = git_hash() + "org.opencontainers.image.source" = "https://github.com/username/rxminder" + "org.opencontainers.image.created" = timestamp() + "org.opencontainers.image.licenses" = "MIT" + "build.environment" = "development" + } +} + +# Local development target (single platform) +target "app-local" { + inherits = ["app-dev"] + + platforms = ["linux/amd64"] + + tags = [ + "rxminder:local", + "rxminder:dev-local" + ] + + output = ["type=docker"] +} + +# Testing target +target "app-test" { + inherits = ["app"] + + args = { + APP_NAME = "RxMinder-Test" + NODE_ENV = "test" + VITE_COUCHDB_URL = "http://localhost:5984" + VITE_COUCHDB_USER = "admin" + VITE_COUCHDB_PASSWORD = "test-password" + APP_BASE_URL = "http://localhost:8080" + } + + tags = [ + "rxminder:test", + "rxminder:test-${git_hash()}" + ] + + labels = { + "org.opencontainers.image.title" = "RxMinder Test" + "org.opencontainers.image.description" = "Medication reminder application - Test build" + "build.environment" = "test" + } + + output = ["type=docker"] +} diff --git a/docker/Dockerfile b/docker/Dockerfile index bb01511..47ed818 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ FROM oven/bun:alpine AS builder # Install system dependencies for native modules -RUN apk add --no-cache python3 make g++ gettext +RUN apk add --no-cache python3 make gcc g++ musl-dev gettext # Create non-root user for security RUN addgroup -g 1001 -S nodeuser && adduser -S nodeuser -u 1001 -G nodeuser @@ -39,6 +39,7 @@ ARG VITE_COUCHDB_PASSWORD=change-this-secure-password # Authentication Configuration ARG JWT_SECRET=your-super-secret-jwt-key-change-in-production +ARG SESSION_SECRET=your-super-secret-session-key-change-in-production # Email Configuration (Optional) ARG VITE_MAILGUN_API_KEY="" diff --git a/scripts/buildx-helper.sh b/scripts/buildx-helper.sh index 4a607b6..c661a2e 100755 --- a/scripts/buildx-helper.sh +++ b/scripts/buildx-helper.sh @@ -1,12 +1,10 @@ #!/bin/bash -# ๐Ÿงช Deployment Validation Script -# Validates complete deployment with all environment variables and health checks +# Docker Buildx Helper Script +# Provides multi-platform Docker image building and pushing capabilities set -e -echo "๐Ÿš€ Starting deployment validation..." - # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' @@ -14,6 +12,15 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# Configuration +BUILDER_NAME="rxminder-builder" +PLATFORMS="linux/amd64,linux/arm64" +DOCKERFILE_PATH="docker/Dockerfile" +DOCKER_CONTEXT="." +IMAGE_NAME="${DOCKER_IMAGE_NAME:-rxminder}" +REGISTRY="${DOCKER_REGISTRY:-}" +TAG="${DOCKER_TAG:-latest}" + # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" @@ -31,196 +38,416 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1" } -# Cleanup function -cleanup() { - print_status "Cleaning up test containers..." - APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]') - docker stop ${APP_NAME_LOWER}-validation-test 2>/dev/null || true - docker rm ${APP_NAME_LOWER}-validation-test 2>/dev/null || true - docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation down 2>/dev/null || true +# Function to show usage +show_usage() { + echo "Docker Buildx Helper Script" + echo "" + echo "Usage: $0 [options]" + echo "" + echo "Commands:" + echo " setup Setup buildx builder instance" + echo " build-local Build image for local platform only" + echo " build-multi Build multi-platform images (production)" + echo " build-multi-dev Build multi-platform images (development)" + echo " build-push Build and push multi-platform images" + echo " push Push existing images to registry" + echo " inspect Inspect builder instance" + echo " bake Build using docker-bake.hcl" + echo " cleanup Remove builder instance and cleanup" + echo " list List available builders" + echo "" + echo "Environment Variables:" + echo " DOCKER_IMAGE_NAME Image name (default: rxminder)" + echo " DOCKER_REGISTRY Registry URL (e.g., ghcr.io/username)" + echo " DOCKER_TAG Image tag (default: latest)" + echo " APP_NAME Application name for build args" + echo " NODE_ENV Build environment (development/production)" + echo "" + echo "Examples:" + echo " $0 setup" + echo " $0 build-local" + echo " DOCKER_REGISTRY=ghcr.io/myuser $0 build-push" + echo " DOCKER_TAG=v1.0.0 $0 build-multi" } -# Set trap for cleanup -trap cleanup EXIT +# Function to setup buildx builder +setup_builder() { + print_status "Setting up Docker Buildx builder..." -print_status "1. Validating environment files..." - -# Check if required environment files exist -if [[ ! -f .env ]]; then - print_error ".env file not found. Run 'cp .env.example .env' and configure it." - exit 1 -fi - -if [[ ! -f .env.example ]]; then - print_error ".env.example file not found." - exit 1 -fi - -print_success "Environment files exist" - -# Validate environment consistency -print_status "2. Checking environment variable consistency..." -./validate-env.sh - -print_status "3. Setting up Docker Buildx..." - -# Ensure buildx is available -if ! docker buildx version >/dev/null 2>&1; then - print_error "Docker Buildx is not available. Please update Docker to a version that supports Buildx." - exit 1 -fi - -# Create a new builder instance if it doesn't exist -if ! docker buildx ls | grep -q "meds-builder"; then - print_status "Creating new buildx builder instance..." - docker buildx create --name meds-builder --driver docker-container --bootstrap -fi - -# Use the builder -docker buildx use meds-builder - -print_status "4. Building multi-platform Docker image with buildx..." - -# Build the image with buildx for multiple platforms -# Build single-platform image for testing -print_status "Building single-platform Docker image for testing..." -APP_NAME_LOWER=$(echo "${APP_NAME:-meds}" | tr '[:upper:]' '[:lower:]') -docker buildx build --no-cache \ ---platform "$HOST_PLATFORM" \ ---build-arg APP_NAME="${APP_NAME:-RxMinder}" \ ---build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \ ---build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \ ---build-arg VITE_COUCHDB_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \ ---build-arg VITE_COUCHDB_USER="${VITE_COUCHDB_USER:-admin}" \ ---build-arg VITE_COUCHDB_PASSWORD="${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" \ ---build-arg APP_BASE_URL="${APP_BASE_URL:-http://localhost:8080}" \ ---build-arg VITE_GOOGLE_CLIENT_ID="${VITE_GOOGLE_CLIENT_ID:-}" \ ---build-arg VITE_GITHUB_CLIENT_ID="${VITE_GITHUB_CLIENT_ID:-}" \ ---build-arg MAILGUN_API_KEY="${MAILGUN_API_KEY:-}" \ ---build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \ ---build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \ ---build-arg NODE_ENV="${NODE_ENV:-production}" \ --t ${APP_NAME_LOWER}-validation \ ---load \ -. - -print_success "Docker image built successfully" - -print_status "5. Testing container startup and health..." - -# Run container in background -docker run --rm -d \ --p 8083:80 \ ---name ${APP_NAME_LOWER}-validation-test \ -${APP_NAME_LOWER}-validation - -# Wait for container to start -sleep 5 - -# Check if container is running -if ! docker ps | grep -q ${APP_NAME_LOWER}-validation-test; then - print_error "Container failed to start" - docker logs ${APP_NAME_LOWER}-validation-test - exit 1 -fi - -print_success "Container started successfully" - -# Test health endpoint -print_status "5. Testing health endpoint..." -for i in {1..10}; do - if curl -s -f http://localhost:8083/health > /dev/null; then - print_success "Health endpoint responding" - break - elif [[ $i -eq 10 ]]; then - print_error "Health endpoint not responding after 10 attempts" + # Check if buildx is available + if ! docker buildx version >/dev/null 2>&1; then + print_error "Docker Buildx is not available. Please update Docker." exit 1 - else - print_warning "Health endpoint not ready, retrying... ($i/10)" - sleep 2 fi -done -# Test main application -print_status "6. Testing main application..." -HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8083) -if [[ $HTTP_CODE -eq 200 ]]; then - print_success "Main application responding (HTTP $HTTP_CODE)" -else - print_error "Main application not responding properly (HTTP $HTTP_CODE)" - exit 1 -fi + # Remove existing builder if it exists + if docker buildx ls | grep -q "$BUILDER_NAME"; then + print_status "Removing existing builder instance..." + docker buildx rm "$BUILDER_NAME" 2>/dev/null || true + fi -# Test docker-compose build -print_status "7. Testing Docker Compose build..." -docker compose -f docker/docker-compose.yaml build frontend --no-cache + # Create new builder instance + print_status "Creating new buildx builder instance: $BUILDER_NAME" + docker buildx create \ + --name "$BUILDER_NAME" \ + --driver docker-container \ + --platform "$PLATFORMS" \ + --bootstrap -print_success "Docker Compose build successful" + # Use the builder + docker buildx use "$BUILDER_NAME" -# Test docker-compose with validation project name -print_status "8. Testing Docker Compose deployment..." -docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation up -d --build + # Inspect the builder + docker buildx inspect --bootstrap -# Wait for services to start -sleep 10 + print_success "Buildx builder setup completed!" +} -# Check service health -if docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation ps | grep -q "Up"; then - print_success "Docker Compose services started successfully" -else - print_error "Docker Compose services failed to start" - docker compose -f docker/docker-compose.yaml -p ${APP_NAME_LOWER}-validation logs - exit 1 -fi +# Function to get build arguments +get_build_args() { + local build_env="${1:-${NODE_ENV:-production}}" + echo "--build-arg APP_NAME=${APP_NAME:-RxMinder}" + echo "--build-arg NODE_ENV=${build_env}" + echo "--build-arg JWT_SECRET=${JWT_SECRET:-demo_jwt_secret_for_frontend_only}" + echo "--build-arg SESSION_SECRET=${SESSION_SECRET:-demo_session_secret_for_frontend_only}" + echo "--build-arg VITE_COUCHDB_URL=${VITE_COUCHDB_URL:-http://couchdb:5984}" + echo "--build-arg VITE_COUCHDB_USER=${VITE_COUCHDB_USER:-admin}" + echo "--build-arg VITE_COUCHDB_PASSWORD=${VITE_COUCHDB_PASSWORD:-change-this-secure-password}" + echo "--build-arg APP_BASE_URL=${APP_BASE_URL:-http://localhost:8080}" + echo "--build-arg VITE_GOOGLE_CLIENT_ID=${VITE_GOOGLE_CLIENT_ID:-}" + echo "--build-arg VITE_GITHUB_CLIENT_ID=${VITE_GITHUB_CLIENT_ID:-}" + echo "--build-arg MAILGUN_API_KEY=${MAILGUN_API_KEY:-}" + echo "--build-arg MAILGUN_DOMAIN=${MAILGUN_DOMAIN:-}" + echo "--build-arg MAILGUN_FROM_EMAIL=${MAILGUN_FROM_EMAIL:-}" +} -# Test health of compose deployment -if curl -s -f http://localhost:8080/health > /dev/null; then - print_success "Docker Compose health endpoint responding" -else - print_warning "Docker Compose health endpoint not responding (may need CouchDB)" -fi +# Function to get image tags +get_image_tags() { + local base_name="$1" + local tags="" -print_status "9. Checking image size..." -IMAGE_SIZE=$(docker image inspect ${APP_NAME_LOWER}-validation --format='{{.Size}}' | numfmt --to=iec) -print_success "Image size: $IMAGE_SIZE" + # Always include the specified tag + tags="$tags -t $base_name:$TAG" -print_status "10. Validating security configuration..." + # Add latest tag if not already latest + if [ "$TAG" != "latest" ]; then + tags="$tags -t $base_name:latest" + fi -# Check if image runs as non-root -USER_INFO=$(docker run --rm ${APP_NAME_LOWER}-validation whoami) -if [[ "$USER_INFO" != "root" ]]; then - print_success "Container runs as non-root user: $USER_INFO" -else - print_warning "Container runs as root user (security consideration)" -fi + # Add git-based tags if in git repo + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + local git_hash=$(git rev-parse --short HEAD) + local git_branch=$(git rev-parse --abbrev-ref HEAD | sed 's/[^a-zA-Z0-9.-]/-/g') -# Check nginx configuration -if docker run --rm ${APP_NAME_LOWER}-validation nginx -t 2>/dev/null; then - print_success "Nginx configuration is valid" -else - print_error "Nginx configuration has issues" - exit 1 -fi + tags="$tags -t $base_name:$git_hash" -print_status "11. Final validation complete!" + if [ "$git_branch" != "HEAD" ] && [ "$git_branch" != "main" ] && [ "$git_branch" != "master" ]; then + tags="$tags -t $base_name:$git_branch" + fi + fi -echo -echo "๐ŸŽ‰ Deployment validation successful!" -echo -echo "Summary:" -echo "โœ… Environment files validated" -echo "โœ… Docker image builds successfully" -echo "โœ… Container starts and runs healthy" -echo "โœ… Health endpoints respond correctly" -echo "โœ… Docker Compose deployment works" -echo "โœ… Security configuration validated" -echo "โœ… Image size optimized ($IMAGE_SIZE)" -echo -echo "Your deployment is ready for production! ๐Ÿš€" -echo -echo "Next steps:" -echo "1. Configure production environment variables in .env" -echo "2. Run './deploy.sh production' for production deployment" -echo "3. Set up monitoring and backups" -echo "4. Configure SSL/TLS certificates" -echo + echo "$tags" +} + +# Function to build for local platform only +build_local() { + print_status "Building Docker image for local platform..." + + # Ensure builder is available + if ! docker buildx ls | grep -q "$BUILDER_NAME"; then + print_warning "Builder not found, setting up..." + setup_builder + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Get the current platform + local platform=$(docker version --format '{{.Server.Os}}/{{.Server.Arch}}') + print_status "Building for platform: $platform" + + # Determine image name + local image_name="$IMAGE_NAME" + if [ -n "$REGISTRY" ]; then + image_name="$REGISTRY/$IMAGE_NAME" + fi + + # Get build arguments and tags (use development for local builds) + local build_args=$(get_build_args "development") + local tags=$(get_image_tags "$image_name") + + print_status "Building image: $image_name:$TAG (development mode)" + + # Build the image + docker buildx build \ + --platform "$platform" \ + $build_args \ + $tags \ + --load \ + -f "$DOCKERFILE_PATH" \ + "$DOCKER_CONTEXT" + + print_success "Local build completed successfully!" + + # Show image info + docker images "$image_name" | head -2 +} + +# Function to build multi-platform images +build_multi() { + print_status "Building multi-platform Docker images..." + + # Ensure builder is available + if ! docker buildx ls | grep -q "$BUILDER_NAME"; then + print_warning "Builder not found, setting up..." + setup_builder + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Determine image name + local image_name="$IMAGE_NAME" + if [ -n "$REGISTRY" ]; then + image_name="$REGISTRY/$IMAGE_NAME" + fi + + # Get build arguments and tags (use production for multi-platform) + local build_args=$(get_build_args "production") + local tags=$(get_image_tags "$image_name") + + print_status "Building for platforms: $PLATFORMS" + print_status "Image: $image_name:$TAG (production mode)" + + # Build the images + docker buildx build \ + --platform "$PLATFORMS" \ + $build_args \ + $tags \ + -f "$DOCKERFILE_PATH" \ + "$DOCKER_CONTEXT" + + print_success "Multi-platform build completed successfully!" +} + +# Function to build multi-platform images for development +build_multi_dev() { + print_status "Building multi-platform Docker images for development..." + + # Ensure builder is available + if ! docker buildx ls | grep -q "$BUILDER_NAME"; then + print_warning "Builder not found, setting up..." + setup_builder + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Determine image name + local image_name="$IMAGE_NAME" + if [ -n "$REGISTRY" ]; then + image_name="$REGISTRY/$IMAGE_NAME" + fi + + # Get build arguments and tags (use development for multi-platform dev) + local build_args=$(get_build_args "development") + local tags=$(get_image_tags "$image_name") + + print_status "Building for platforms: $PLATFORMS" + print_status "Image: $image_name:$TAG (development mode)" + + # Build the images + docker buildx build \ + --platform "$PLATFORMS" \ + $build_args \ + $tags \ + -f "$DOCKERFILE_PATH" \ + "$DOCKER_CONTEXT" + + print_success "Multi-platform development build completed successfully!" +} + +# Function to build and push multi-platform images +build_push() { + print_status "Building and pushing multi-platform Docker images..." + + if [ -z "$REGISTRY" ]; then + print_error "DOCKER_REGISTRY environment variable must be set for pushing" + print_status "Example: DOCKER_REGISTRY=ghcr.io/username $0 build-push" + exit 1 + fi + + # Ensure builder is available + if ! docker buildx ls | grep -q "$BUILDER_NAME"; then + print_warning "Builder not found, setting up..." + setup_builder + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Determine image name + local image_name="$REGISTRY/$IMAGE_NAME" + + # Get build arguments and tags (use production for build-push) + local build_args=$(get_build_args "production") + local tags=$(get_image_tags "$image_name") + + print_status "Building and pushing for platforms: $PLATFORMS" + print_status "Registry: $REGISTRY" + print_status "Image: $image_name:$TAG (production mode)" + + # Build and push the images + docker buildx build \ + --platform "$PLATFORMS" \ + $build_args \ + $tags \ + --push \ + -f "$DOCKERFILE_PATH" \ + "$DOCKER_CONTEXT" + + print_success "Multi-platform build and push completed successfully!" + + # Show pushed images + echo "" + print_status "Pushed images:" + echo "$image_name:$TAG" + if [ "$TAG" != "latest" ]; then + echo "$image_name:latest" + fi + + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + local git_hash=$(git rev-parse --short HEAD) + echo "$image_name:$git_hash" + fi +} + +# Function to push existing images +push_images() { + print_status "Pushing existing images to registry..." + + if [ -z "$REGISTRY" ]; then + print_error "DOCKER_REGISTRY environment variable must be set for pushing" + exit 1 + fi + + local image_name="$REGISTRY/$IMAGE_NAME" + + print_status "Pushing $image_name:$TAG" + docker push "$image_name:$TAG" + + if [ "$TAG" != "latest" ]; then + print_status "Pushing $image_name:latest" + docker push "$image_name:latest" + fi + + print_success "Push completed successfully!" +} + +# Function to inspect builder +inspect_builder() { + print_status "Inspecting buildx builder..." + + if docker buildx ls | grep -q "$BUILDER_NAME"; then + docker buildx inspect "$BUILDER_NAME" + else + print_warning "Builder '$BUILDER_NAME' not found" + print_status "Available builders:" + docker buildx ls + fi +} + +# Function to build using docker-bake.hcl +build_bake() { + print_status "Building using docker-bake.hcl..." + + if [ ! -f "docker-bake.hcl" ]; then + print_error "docker-bake.hcl not found" + exit 1 + fi + + # Ensure builder is available + if ! docker buildx ls | grep -q "$BUILDER_NAME"; then + print_warning "Builder not found, setting up..." + setup_builder + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Build using bake + docker buildx bake -f docker-bake.hcl + + print_success "Bake build completed successfully!" +} + +# Function to cleanup builder +cleanup_builder() { + print_status "Cleaning up buildx builder..." + + if docker buildx ls | grep -q "$BUILDER_NAME"; then + docker buildx rm "$BUILDER_NAME" + print_success "Builder '$BUILDER_NAME' removed" + else + print_warning "Builder '$BUILDER_NAME' not found" + fi + + # Cleanup unused build cache + print_status "Cleaning up build cache..." + docker buildx prune -f + + print_success "Cleanup completed!" +} + +# Function to list builders +list_builders() { + print_status "Available buildx builders:" + docker buildx ls +} + +# Main script logic +case "${1:-}" in + "setup") + setup_builder + ;; + "build-local") + build_local + ;; + "build-multi") + build_multi + ;; + "build-multi-dev") + build_multi_dev + ;; + "build-push") + build_push + ;; + "push") + push_images + ;; + "inspect") + inspect_builder + ;; + "bake") + build_bake + ;; + "cleanup") + cleanup_builder + ;; + "list") + list_builders + ;; + "help"|"--help"|"-h") + show_usage + ;; + "") + print_error "No command specified" + show_usage + exit 1 + ;; + *) + print_error "Unknown command: $1" + show_usage + exit 1 + ;; +esac