feat: Enable multi-platform Docker builds and dynamic image tagging

- Detect host architecture to set build platform - Support
multi-platform builds when MULTI_PLATFORM=1 or CONTAINER_REGISTRY is set
- Dynamically set image tag based on registry and platform - Pull pushed
images for local validation - Update all docker run and inspect commands
to use dynamic image tag
This commit is contained in:
William Valentin
2025-09-06 17:44:53 -07:00
parent 75d0f772e9
commit 6bddac7656
2 changed files with 74 additions and 40 deletions

View File

@@ -52,21 +52,21 @@ load_env() {
# Function to substitute environment variables in template files # Function to substitute environment variables in template files
substitute_templates() { substitute_templates() {
print_info "Processing template files..." print_info "Processing template files..."
# Create temporary directory # Create temporary directory
mkdir -p "$TEMP_DIR" mkdir -p "$TEMP_DIR"
# Process each template file # Process each template file
for template_file in "$K8S_DIR"/*.template; do for template_file in "$K8S_DIR"/*.template; do
if [[ -f "$template_file" ]]; then if [[ -f "$template_file" ]]; then
local filename=$(basename "$template_file" .template) local filename=$(basename "$template_file" .template)
local output_file="$TEMP_DIR/$filename" local output_file="$TEMP_DIR/$filename"
print_info "Processing template: $filename" print_info "Processing template: $filename"
# Substitute environment variables # Substitute environment variables
envsubst < "$template_file" > "$output_file" envsubst < "$template_file" > "$output_file"
print_success "Generated: $output_file" print_success "Generated: $output_file"
fi fi
done done
@@ -76,13 +76,13 @@ substitute_templates() {
validate_env() { validate_env() {
local required_vars=("INGRESS_HOST") local required_vars=("INGRESS_HOST")
local missing_vars=() local missing_vars=()
for var in "${required_vars[@]}"; do for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then if [[ -z "${!var:-}" ]]; then
missing_vars+=("$var") missing_vars+=("$var")
fi fi
done done
if [[ ${#missing_vars[@]} -gt 0 ]]; then if [[ ${#missing_vars[@]} -gt 0 ]]; then
print_error "Missing required environment variables:" print_error "Missing required environment variables:"
for var in "${missing_vars[@]}"; do for var in "${missing_vars[@]}"; do
@@ -108,15 +108,15 @@ ensure_namespace() {
# Function to apply Kubernetes manifests # Function to apply Kubernetes manifests
apply_manifests() { apply_manifests() {
local manifest_dir="$1" local manifest_dir="$1"
print_info "Applying Kubernetes manifests from $manifest_dir" print_info "Applying Kubernetes manifests from $manifest_dir"
# Apply static files that don't have template counterparts # Apply static files that don't have template counterparts
for manifest_file in "$K8S_DIR"/*.yaml; do for manifest_file in "$K8S_DIR"/*.yaml; do
if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then if [[ -f "$manifest_file" && ! "$manifest_file" =~ \.template$ ]]; then
local basename_file=$(basename "$manifest_file") local basename_file=$(basename "$manifest_file")
local template_file="$K8S_DIR/${basename_file}.template" local template_file="$K8S_DIR/${basename_file}.template"
# Only apply if there's no corresponding template file # Only apply if there's no corresponding template file
if [[ ! -f "$template_file" ]]; then if [[ ! -f "$template_file" ]]; then
print_info "Applying static file: $basename_file" print_info "Applying static file: $basename_file"
@@ -124,7 +124,7 @@ apply_manifests() {
fi fi
fi fi
done done
# Apply processed template files # Apply processed template files
if [[ -d "$TEMP_DIR" ]]; then if [[ -d "$TEMP_DIR" ]]; then
for manifest_file in "$TEMP_DIR"/*.yaml; do for manifest_file in "$TEMP_DIR"/*.yaml; do
@@ -148,19 +148,19 @@ cleanup() {
show_status() { show_status() {
print_info "Deployment Status:" print_info "Deployment Status:"
echo "" echo ""
print_info "Pods:" print_info "Pods:"
kubectl get pods -l app=rxminder -n "$NAMESPACE" kubectl get pods -l app=rxminder -n "$NAMESPACE"
echo "" echo ""
print_info "Services:" print_info "Services:"
kubectl get services -l app=rxminder -n "$NAMESPACE" kubectl get services -l app=rxminder -n "$NAMESPACE"
echo "" echo ""
print_info "Ingress:" print_info "Ingress:"
kubectl get ingress -l app=rxminder -n "$NAMESPACE" kubectl get ingress -l app=rxminder -n "$NAMESPACE"
echo "" echo ""
if [[ -n "${INGRESS_HOST:-}" ]]; then if [[ -n "${INGRESS_HOST:-}" ]]; then
print_success "Application should be available at: http://$INGRESS_HOST" print_success "Application should be available at: http://$INGRESS_HOST"
fi fi
@@ -190,7 +190,7 @@ main() {
local dry_run=false local dry_run=false
local status_only=false local status_only=false
local cleanup_only=false local cleanup_only=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
@@ -221,54 +221,56 @@ main() {
;; ;;
esac esac
done done
# Handle cleanup only # Handle cleanup only
if [[ "$cleanup_only" == true ]]; then if [[ "$cleanup_only" == true ]]; then
cleanup cleanup
exit 0 exit 0
fi fi
# Handle status only # Handle status only
if [[ "$status_only" == true ]]; then if [[ "$status_only" == true ]]; then
load_env "$env_file"
NAMESPACE="${NAMESPACE:-rxminder}"
show_status show_status
exit 0 exit 0
fi fi
# Check if kubectl is available # Check if kubectl is available
if ! command -v kubectl &> /dev/null; then if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed or not in PATH" print_error "kubectl is not installed or not in PATH"
exit 1 exit 1
fi fi
# Check if we can connect to Kubernetes cluster # Check if we can connect to Kubernetes cluster
if ! kubectl cluster-info &> /dev/null; then if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster" print_error "Cannot connect to Kubernetes cluster"
print_info "Make sure your kubectl is configured correctly" print_info "Make sure your kubectl is configured correctly"
exit 1 exit 1
fi fi
print_info "🚀 Deploying Medication Reminder App to Kubernetes" print_info "🚀 Deploying Medication Reminder App to Kubernetes"
echo "" echo ""
# Load environment variables # Load environment variables
load_env "$env_file" load_env "$env_file"
# Set default namespace if not provided in environment # Set default namespace if not provided in environment
NAMESPACE="${NAMESPACE:-rxminder}" NAMESPACE="${NAMESPACE:-rxminder}"
# Validate required environment variables # Validate required environment variables
validate_env validate_env
# Ensure namespace exists # Ensure namespace exists
ensure_namespace ensure_namespace
# Process templates # Process templates
substitute_templates substitute_templates
if [[ "$dry_run" == true ]]; then if [[ "$dry_run" == true ]]; then
print_info "Dry run mode - showing generated manifests:" print_info "Dry run mode - showing generated manifests:"
echo "" echo ""
for manifest_file in "$TEMP_DIR"/*.yaml; do for manifest_file in "$TEMP_DIR"/*.yaml; do
if [[ -f "$manifest_file" ]]; then if [[ -f "$manifest_file" ]]; then
echo "=== $(basename "$manifest_file") ===" echo "=== $(basename "$manifest_file") ==="
@@ -279,14 +281,14 @@ main() {
else else
# Apply manifests # Apply manifests
apply_manifests "$K8S_DIR" apply_manifests "$K8S_DIR"
print_success "Deployment completed!" print_success "Deployment completed!"
echo "" echo ""
# Show status # Show status
show_status show_status
fi fi
# Cleanup # Cleanup
cleanup cleanup
} }

View File

@@ -59,7 +59,7 @@ print_success "Environment files exist"
# Validate environment consistency # Validate environment consistency
print_status "2. Checking environment variable consistency..." print_status "2. Checking environment variable consistency..."
./validate-env.sh ./scripts/validate-env.sh
print_status "3. Setting up Docker Buildx..." print_status "3. Setting up Docker Buildx..."
@@ -80,9 +80,35 @@ docker buildx use meds-builder
print_status "4. Building multi-platform Docker image with buildx..." print_status "4. Building multi-platform Docker image with buildx..."
# Build the image with buildx for multiple platforms # Determine host platform
HOST_ARCH="$(uname -m)"
case "$HOST_ARCH" in
x86_64) HOST_PLATFORM="linux/amd64" ;;
aarch64|arm64) HOST_PLATFORM="linux/arm64" ;;
*) HOST_PLATFORM="linux/amd64" ;;
esac
# Decide build strategy:
# Enable multi-platform (push) if MULTI_PLATFORM=1 or CONTAINER_REGISTRY is set
if [[ "${MULTI_PLATFORM:-0}" == "1" || -n "${CONTAINER_REGISTRY:-}" ]]; then
BUILD_PLATFORMS="linux/amd64,linux/arm64"
EXPORT_MODE="--push"
# If CONTAINER_REGISTRY provided, ensure trailing slash
if [[ -n "${CONTAINER_REGISTRY:-}" && "${CONTAINER_REGISTRY}" != */ ]]; then
CONTAINER_REGISTRY="${CONTAINER_REGISTRY}/"
fi
IMAGE_TAG="${IMAGE_TAG:-${CONTAINER_REGISTRY:-}meds-validation:latest}"
print_status "Multi-platform build enabled (platforms: $BUILD_PLATFORMS) -> push $IMAGE_TAG"
else
BUILD_PLATFORMS="$HOST_PLATFORM"
EXPORT_MODE="--load"
IMAGE_TAG="${IMAGE_TAG:-meds-validation}"
print_status "Single-platform build ($BUILD_PLATFORMS) -> load locally as $IMAGE_TAG"
fi
# Perform build
docker buildx build --no-cache \ docker buildx build --no-cache \
--platform linux/amd64,linux/arm64 \ --platform "$BUILD_PLATFORMS" \
--build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \ --build-arg COUCHDB_USER="${COUCHDB_USER:-admin}" \
--build-arg COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-change-this-secure-password}" \ --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_URL="${VITE_COUCHDB_URL:-http://localhost:5984}" \
@@ -95,10 +121,16 @@ docker buildx build --no-cache \
--build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \ --build-arg MAILGUN_DOMAIN="${MAILGUN_DOMAIN:-}" \
--build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \ --build-arg MAILGUN_FROM_EMAIL="${MAILGUN_FROM_EMAIL:-}" \
--build-arg NODE_ENV="${NODE_ENV:-production}" \ --build-arg NODE_ENV="${NODE_ENV:-production}" \
-t meds-validation \ -t "$IMAGE_TAG" \
--load \ $EXPORT_MODE \
. .
# If we pushed (multi-platform), pull the host-specific image for local tests
if [[ "$EXPORT_MODE" == "--push" ]]; then
print_status "Pulling image $IMAGE_TAG for local validation..."
docker pull "$IMAGE_TAG"
fi
print_success "Docker image built successfully" print_success "Docker image built successfully"
print_status "5. Testing container startup and health..." print_status "5. Testing container startup and health..."
@@ -107,7 +139,7 @@ print_status "5. Testing container startup and health..."
docker run --rm -d \ docker run --rm -d \
-p 8083:80 \ -p 8083:80 \
--name meds-validation-test \ --name meds-validation-test \
meds-validation "$IMAGE_TAG"
# Wait for container to start # Wait for container to start
sleep 5 sleep 5
@@ -176,13 +208,13 @@ else
fi fi
print_status "9. Checking image size..." print_status "9. Checking image size..."
IMAGE_SIZE=$(docker image inspect meds-validation --format='{{.Size}}' | numfmt --to=iec) IMAGE_SIZE=$(docker image inspect "$IMAGE_TAG" --format='{{.Size}}' | numfmt --to=iec)
print_success "Image size: $IMAGE_SIZE" print_success "Image size: $IMAGE_SIZE"
print_status "10. Validating security configuration..." print_status "10. Validating security configuration..."
# Check if image runs as non-root # Check if image runs as non-root
USER_INFO=$(docker run --rm meds-validation whoami) USER_INFO=$(docker run --rm "$IMAGE_TAG" whoami)
if [[ "$USER_INFO" != "root" ]]; then if [[ "$USER_INFO" != "root" ]]; then
print_success "Container runs as non-root user: $USER_INFO" print_success "Container runs as non-root user: $USER_INFO"
else else
@@ -190,7 +222,7 @@ else
fi fi
# Check nginx configuration # Check nginx configuration
if docker run --rm meds-validation nginx -t 2>/dev/null; then if docker run --rm "$IMAGE_TAG" nginx -t 2>/dev/null; then
print_success "Nginx configuration is valid" print_success "Nginx configuration is valid"
else else
print_error "Nginx configuration has issues" print_error "Nginx configuration has issues"