first commit

This commit is contained in:
2025-07-07 01:44:12 +02:00
commit bf68bde4ce
72 changed files with 29002 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
#!/bin/bash
# TankStopp Docker Build Script
# This script builds Docker images for the TankStopp application
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
IMAGE_NAME="tankstopp"
TAG="latest"
DOCKERFILE="Dockerfile"
CONTEXT="."
BUILD_ARGS=""
PLATFORM=""
PUSH=false
NO_CACHE=false
QUIET=false
ENVIRONMENT="production"
# Functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
show_help() {
cat << EOF
TankStopp Docker Build Script
Usage: $0 [OPTIONS]
OPTIONS:
-h, --help Show this help message
-t, --tag TAG Image tag (default: latest)
-e, --env ENV Environment: development|production (default: production)
-f, --file FILE Dockerfile path (default: Dockerfile)
-c, --context PATH Build context path (default: .)
-p, --platform ARCH Target platform (e.g., linux/amd64,linux/arm64)
--push Push image to registry after build
--no-cache Build without using cache
--quiet Suppress build output
--build-arg ARG=VALUE Pass build arguments
--clean Clean up old images before building
EXAMPLES:
# Build production image
$0 --tag v1.0.0 --env production
# Build development image
$0 --tag dev --env development
# Build multi-platform image
$0 --tag latest --platform linux/amd64,linux/arm64
# Build and push to registry
$0 --tag v1.0.0 --push
# Build with custom build args
$0 --build-arg VERSION=1.0.0 --build-arg BUILD_DATE=\$(date -u +'%Y-%m-%dT%H:%M:%SZ')
ENVIRONMENT CONFIGURATIONS:
production - Optimized for production deployment
development - Includes development tools and debugging
EOF
}
check_requirements() {
log_info "Checking requirements..."
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
exit 1
fi
if ! docker info &> /dev/null; then
log_error "Docker daemon is not running"
exit 1
fi
if [ ! -f "$DOCKERFILE" ]; then
log_error "Dockerfile not found: $DOCKERFILE"
exit 1
fi
if [ ! -d "$CONTEXT" ]; then
log_error "Build context directory not found: $CONTEXT"
exit 1
fi
log_success "Requirements check passed"
}
clean_old_images() {
log_info "Cleaning up old images..."
# Remove old tankstopp images (keep latest 3)
OLD_IMAGES=$(docker images "$IMAGE_NAME" --format "{{.Repository}}:{{.Tag}}" | tail -n +4)
if [ -n "$OLD_IMAGES" ]; then
echo "$OLD_IMAGES" | xargs docker rmi -f 2>/dev/null || true
log_success "Cleaned up old images"
else
log_info "No old images to clean"
fi
# Remove dangling images
DANGLING=$(docker images -f "dangling=true" -q)
if [ -n "$DANGLING" ]; then
echo "$DANGLING" | xargs docker rmi -f 2>/dev/null || true
log_success "Removed dangling images"
fi
}
prepare_build_args() {
log_info "Preparing build arguments..."
# Add environment-specific build args
case "$ENVIRONMENT" in
"development")
BUILD_ARGS="$BUILD_ARGS --build-arg APP_ENV=development"
BUILD_ARGS="$BUILD_ARGS --build-arg DEBUG=true"
;;
"production")
BUILD_ARGS="$BUILD_ARGS --build-arg APP_ENV=production"
BUILD_ARGS="$BUILD_ARGS --build-arg DEBUG=false"
;;
esac
# Add common build args
BUILD_ARGS="$BUILD_ARGS --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
BUILD_ARGS="$BUILD_ARGS --build-arg VCS_REF=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
BUILD_ARGS="$BUILD_ARGS --build-arg VERSION=$TAG"
log_success "Build arguments prepared"
}
build_image() {
log_info "Building Docker image..."
log_info "Image: $IMAGE_NAME:$TAG"
log_info "Environment: $ENVIRONMENT"
log_info "Context: $CONTEXT"
log_info "Dockerfile: $DOCKERFILE"
# Construct docker build command
DOCKER_CMD="docker build"
# Add platform if specified
if [ -n "$PLATFORM" ]; then
DOCKER_CMD="$DOCKER_CMD --platform $PLATFORM"
fi
# Add no-cache flag if specified
if [ "$NO_CACHE" = true ]; then
DOCKER_CMD="$DOCKER_CMD --no-cache"
fi
# Add quiet flag if specified
if [ "$QUIET" = true ]; then
DOCKER_CMD="$DOCKER_CMD --quiet"
fi
# Add build arguments
DOCKER_CMD="$DOCKER_CMD $BUILD_ARGS"
# Add tag and file
DOCKER_CMD="$DOCKER_CMD -t $IMAGE_NAME:$TAG"
DOCKER_CMD="$DOCKER_CMD -f $DOCKERFILE"
DOCKER_CMD="$DOCKER_CMD $CONTEXT"
log_info "Executing: $DOCKER_CMD"
# Execute build
if eval "$DOCKER_CMD"; then
log_success "Docker image built successfully: $IMAGE_NAME:$TAG"
else
log_error "Docker build failed"
exit 1
fi
}
push_image() {
if [ "$PUSH" = true ]; then
log_info "Pushing image to registry..."
if docker push "$IMAGE_NAME:$TAG"; then
log_success "Image pushed successfully: $IMAGE_NAME:$TAG"
else
log_error "Failed to push image"
exit 1
fi
fi
}
show_image_info() {
log_info "Image information:"
docker images "$IMAGE_NAME:$TAG" --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedAt}}\t{{.Size}}"
# Show image layers (if not quiet)
if [ "$QUIET" != true ]; then
echo ""
log_info "Image layers:"
docker history "$IMAGE_NAME:$TAG" --format "table {{.CreatedBy}}\t{{.Size}}" | head -10
fi
}
main() {
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-t|--tag)
TAG="$2"
shift 2
;;
-e|--env)
ENVIRONMENT="$2"
shift 2
;;
-f|--file)
DOCKERFILE="$2"
shift 2
;;
-c|--context)
CONTEXT="$2"
shift 2
;;
-p|--platform)
PLATFORM="$2"
shift 2
;;
--push)
PUSH=true
shift
;;
--no-cache)
NO_CACHE=true
shift
;;
--quiet)
QUIET=true
shift
;;
--build-arg)
BUILD_ARGS="$BUILD_ARGS --build-arg $2"
shift 2
;;
--clean)
CLEAN=true
shift
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Validate environment
if [[ "$ENVIRONMENT" != "development" && "$ENVIRONMENT" != "production" ]]; then
log_error "Invalid environment: $ENVIRONMENT. Must be 'development' or 'production'"
exit 1
fi
# Start build process
log_info "Starting TankStopp Docker build process..."
check_requirements
if [ "$CLEAN" = true ]; then
clean_old_images
fi
prepare_build_args
build_image
push_image
show_image_info
log_success "Build process completed successfully!"
log_info "You can now run the container with:"
log_info " docker run -p 8080:8080 $IMAGE_NAME:$TAG"
log_info ""
log_info "Or use docker-compose:"
log_info " docker-compose up -d"
}
# Execute main function
main "$@"
+687
View File
@@ -0,0 +1,687 @@
#!/bin/bash
# TankStopp Docker Deployment Script
# This script deploys the TankStopp application using Docker Compose
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# Default values
ENVIRONMENT="production"
ACTION="deploy"
SERVICE_NAME="tankstopp"
COMPOSE_FILE="docker-compose.yml"
COMPOSE_OVERRIDE=""
PROJECT_NAME="tankstopp"
BACKUP_BEFORE_DEPLOY=true
WAIT_TIMEOUT=300
HEALTH_CHECK_RETRIES=10
ROLLBACK_ON_FAILURE=true
SCALE_REPLICAS=1
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DATA_DIR="/var/lib/tankstopp"
BACKUP_DIR="/var/lib/tankstopp/backups"
LOG_DIR="/var/log/tankstopp"
# Functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
}
log_debug() {
if [ "$DEBUG" = true ]; then
echo -e "${PURPLE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"
fi
}
show_help() {
cat << EOF
TankStopp Docker Deployment Script
Usage: $0 [ACTION] [OPTIONS]
ACTIONS:
deploy Deploy the application (default)
start Start existing containers
stop Stop running containers
restart Restart containers
update Update to latest images
rollback Rollback to previous version
status Show deployment status
logs Show application logs
backup Create database backup
restore Restore from backup
scale Scale service replicas
cleanup Clean up unused resources
OPTIONS:
-h, --help Show this help message
-e, --env ENV Environment: development|production|staging (default: production)
-f, --file FILE Docker compose file (default: docker-compose.yml)
-o, --override FILE Docker compose override file
-p, --project NAME Project name (default: tankstopp)
-s, --service NAME Service name (default: tankstopp)
-r, --replicas COUNT Number of replicas for scaling (default: 1)
-t, --timeout SECONDS Wait timeout in seconds (default: 300)
--no-backup Skip backup before deployment
--no-rollback Don't rollback on failure
--force Force action without confirmation
--debug Enable debug logging
EXAMPLES:
# Deploy to production
$0 deploy --env production
# Deploy with custom compose file
$0 deploy --file docker-compose.prod.yml
# Start development environment
$0 start --env development
# Scale production service
$0 scale --replicas 3
# View logs
$0 logs --service tankstopp
# Backup database
$0 backup
# Rollback deployment
$0 rollback
ENVIRONMENT CONFIGURATIONS:
development - Local development with debug enabled
staging - Staging environment for testing
production - Production deployment with optimizations
EOF
}
check_requirements() {
log_info "Checking deployment requirements..."
# Check Docker
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
exit 1
fi
# Check Docker Compose
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
log_error "Docker Compose is not installed or not in PATH"
exit 1
fi
# Set Docker Compose command
if docker compose version &> /dev/null; then
DOCKER_COMPOSE_CMD="docker compose"
else
DOCKER_COMPOSE_CMD="docker-compose"
fi
# Check Docker daemon
if ! docker info &> /dev/null; then
log_error "Docker daemon is not running"
exit 1
fi
# Check compose file
if [ ! -f "$PROJECT_ROOT/$COMPOSE_FILE" ]; then
log_error "Docker Compose file not found: $PROJECT_ROOT/$COMPOSE_FILE"
exit 1
fi
log_success "Requirements check passed"
}
setup_environment() {
log_info "Setting up environment: $ENVIRONMENT"
# Create necessary directories
create_directories
# Set compose files based on environment
case "$ENVIRONMENT" in
"development")
COMPOSE_OVERRIDE="docker-compose.override.yml"
;;
"staging")
COMPOSE_OVERRIDE="docker-compose.staging.yml"
;;
"production")
COMPOSE_OVERRIDE="docker-compose.prod.yml"
;;
esac
# Build compose command
COMPOSE_CMD="$DOCKER_COMPOSE_CMD -p $PROJECT_NAME -f $COMPOSE_FILE"
if [ -n "$COMPOSE_OVERRIDE" ] && [ -f "$PROJECT_ROOT/$COMPOSE_OVERRIDE" ]; then
COMPOSE_CMD="$COMPOSE_CMD -f $COMPOSE_OVERRIDE"
log_info "Using override file: $COMPOSE_OVERRIDE"
fi
log_success "Environment setup completed"
}
create_directories() {
log_info "Creating necessary directories..."
# Create directories with proper permissions
sudo mkdir -p "$DATA_DIR/data" "$BACKUP_DIR" "$LOG_DIR"
sudo chown -R 1001:1001 "$DATA_DIR" 2>/dev/null || true
sudo chmod -R 755 "$DATA_DIR" 2>/dev/null || true
log_success "Directories created"
}
backup_database() {
if [ "$BACKUP_BEFORE_DEPLOY" = false ]; then
log_info "Skipping backup (disabled)"
return 0
fi
log_info "Creating database backup..."
local backup_file="$BACKUP_DIR/fuel_stops_$(date +%Y%m%d_%H%M%S).db"
# Check if database exists
if [ ! -f "$DATA_DIR/data/fuel_stops.db" ]; then
log_warning "Database file not found, skipping backup"
return 0
fi
# Create backup
if cp "$DATA_DIR/data/fuel_stops.db" "$backup_file"; then
log_success "Database backup created: $backup_file"
# Keep only last 10 backups
find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | tail -n +11 | xargs rm -f 2>/dev/null || true
else
log_error "Failed to create database backup"
return 1
fi
}
pull_images() {
log_info "Pulling latest images..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD pull; then
log_success "Images pulled successfully"
else
log_error "Failed to pull images"
return 1
fi
}
deploy_application() {
log_info "Deploying TankStopp application..."
cd "$PROJECT_ROOT"
# Backup database
backup_database
# Pull latest images
pull_images
# Deploy with docker-compose
log_info "Starting containers..."
if $COMPOSE_CMD up -d --remove-orphans; then
log_success "Containers started successfully"
else
log_error "Failed to start containers"
if [ "$ROLLBACK_ON_FAILURE" = true ]; then
log_info "Attempting rollback..."
rollback_deployment
fi
return 1
fi
# Wait for services to be healthy
wait_for_health
# Run post-deployment checks
post_deployment_checks
log_success "Application deployed successfully"
}
wait_for_health() {
log_info "Waiting for services to become healthy..."
local retries=0
local max_retries=$HEALTH_CHECK_RETRIES
while [ $retries -lt $max_retries ]; do
if check_service_health; then
log_success "All services are healthy"
return 0
fi
retries=$((retries + 1))
log_info "Health check attempt $retries/$max_retries..."
sleep 10
done
log_error "Services failed to become healthy within timeout"
return 1
}
check_service_health() {
cd "$PROJECT_ROOT"
# Check container status
local unhealthy_containers=$($COMPOSE_CMD ps --filter "status=unhealthy" --format "{{.Service}}" 2>/dev/null | wc -l)
local running_containers=$($COMPOSE_CMD ps --filter "status=running" --format "{{.Service}}" 2>/dev/null | wc -l)
log_debug "Running containers: $running_containers, Unhealthy containers: $unhealthy_containers"
if [ "$unhealthy_containers" -gt 0 ]; then
log_warning "Found $unhealthy_containers unhealthy containers"
return 1
fi
if [ "$running_containers" -eq 0 ]; then
log_warning "No running containers found"
return 1
fi
# Test HTTP endpoint
if command -v curl &> /dev/null; then
local port=$(get_service_port)
if curl -sf "http://localhost:$port/" > /dev/null 2>&1; then
return 0
else
log_debug "HTTP health check failed"
return 1
fi
fi
return 0
}
get_service_port() {
case "$ENVIRONMENT" in
"development")
echo "8081"
;;
*)
echo "8080"
;;
esac
}
post_deployment_checks() {
log_info "Running post-deployment checks..."
cd "$PROJECT_ROOT"
# Check container logs for errors
local error_count=$($COMPOSE_CMD logs --tail=50 "$SERVICE_NAME" 2>/dev/null | grep -i "error\|panic\|fatal" | wc -l)
if [ "$error_count" -gt 0 ]; then
log_warning "Found $error_count error messages in logs"
log_info "Recent errors:"
$COMPOSE_CMD logs --tail=10 "$SERVICE_NAME" | grep -i "error\|panic\|fatal" || true
fi
# Check resource usage
check_resource_usage
log_success "Post-deployment checks completed"
}
check_resource_usage() {
log_info "Checking resource usage..."
cd "$PROJECT_ROOT"
# Get container stats
local stats=$($COMPOSE_CMD exec -T "$SERVICE_NAME" sh -c "
echo 'Memory usage:'
free -h | grep Mem
echo 'Disk usage:'
df -h /app/data
echo 'CPU info:'
uptime
" 2>/dev/null || echo "Could not retrieve stats")
log_debug "Resource stats: $stats"
}
start_services() {
log_info "Starting services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD start; then
log_success "Services started successfully"
wait_for_health
else
log_error "Failed to start services"
return 1
fi
}
stop_services() {
log_info "Stopping services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD stop; then
log_success "Services stopped successfully"
else
log_error "Failed to stop services"
return 1
fi
}
restart_services() {
log_info "Restarting services..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD restart; then
log_success "Services restarted successfully"
wait_for_health
else
log_error "Failed to restart services"
return 1
fi
}
update_application() {
log_info "Updating application..."
backup_database
pull_images
cd "$PROJECT_ROOT"
if $COMPOSE_CMD up -d --remove-orphans; then
log_success "Application updated successfully"
wait_for_health
post_deployment_checks
else
log_error "Failed to update application"
return 1
fi
}
rollback_deployment() {
log_info "Rolling back deployment..."
cd "$PROJECT_ROOT"
# Stop current containers
$COMPOSE_CMD down
# Restore from latest backup
local latest_backup=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
if [ -n "$latest_backup" ] && [ -f "$latest_backup" ]; then
log_info "Restoring database from: $latest_backup"
cp "$latest_backup" "$DATA_DIR/data/fuel_stops.db"
log_success "Database restored"
else
log_warning "No backup found for rollback"
fi
# Start with previous configuration
if $COMPOSE_CMD up -d; then
log_success "Rollback completed successfully"
else
log_error "Rollback failed"
return 1
fi
}
show_status() {
log_info "Deployment status:"
cd "$PROJECT_ROOT"
echo ""
echo "=== Container Status ==="
$COMPOSE_CMD ps
echo ""
echo "=== Service Health ==="
if check_service_health; then
echo -e "${GREEN}✓ Services are healthy${NC}"
else
echo -e "${RED}✗ Services are unhealthy${NC}"
fi
echo ""
echo "=== Resource Usage ==="
check_resource_usage
echo ""
echo "=== Recent Logs ==="
$COMPOSE_CMD logs --tail=5 "$SERVICE_NAME"
}
show_logs() {
log_info "Showing application logs..."
cd "$PROJECT_ROOT"
$COMPOSE_CMD logs -f "$SERVICE_NAME"
}
scale_service() {
log_info "Scaling service to $SCALE_REPLICAS replicas..."
cd "$PROJECT_ROOT"
if $COMPOSE_CMD up -d --scale "$SERVICE_NAME=$SCALE_REPLICAS"; then
log_success "Service scaled to $SCALE_REPLICAS replicas"
wait_for_health
else
log_error "Failed to scale service"
return 1
fi
}
cleanup_resources() {
log_info "Cleaning up unused resources..."
# Remove unused containers
docker container prune -f
# Remove unused images
docker image prune -f
# Remove unused volumes (be careful!)
if [ "$FORCE" = true ]; then
docker volume prune -f
fi
# Remove unused networks
docker network prune -f
log_success "Cleanup completed"
}
restore_backup() {
log_info "Available backups:"
ls -la "$BACKUP_DIR"/fuel_stops_*.db 2>/dev/null || {
log_error "No backups found in $BACKUP_DIR"
return 1
}
echo ""
read -p "Enter backup filename to restore (or 'latest' for most recent): " backup_choice
local backup_file
if [ "$backup_choice" = "latest" ]; then
backup_file=$(find "$BACKUP_DIR" -name "fuel_stops_*.db" -type f | sort -r | head -n 1)
else
backup_file="$BACKUP_DIR/$backup_choice"
fi
if [ ! -f "$backup_file" ]; then
log_error "Backup file not found: $backup_file"
return 1
fi
log_info "Restoring from: $backup_file"
# Stop services
stop_services
# Restore database
cp "$backup_file" "$DATA_DIR/data/fuel_stops.db"
# Start services
start_services
log_success "Database restored successfully"
}
main() {
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
deploy|start|stop|restart|update|rollback|status|logs|backup|restore|scale|cleanup)
ACTION="$1"
shift
;;
-h|--help)
show_help
exit 0
;;
-e|--env)
ENVIRONMENT="$2"
shift 2
;;
-f|--file)
COMPOSE_FILE="$2"
shift 2
;;
-o|--override)
COMPOSE_OVERRIDE="$2"
shift 2
;;
-p|--project)
PROJECT_NAME="$2"
shift 2
;;
-s|--service)
SERVICE_NAME="$2"
shift 2
;;
-r|--replicas)
SCALE_REPLICAS="$2"
shift 2
;;
-t|--timeout)
WAIT_TIMEOUT="$2"
shift 2
;;
--no-backup)
BACKUP_BEFORE_DEPLOY=false
shift
;;
--no-rollback)
ROLLBACK_ON_FAILURE=false
shift
;;
--force)
FORCE=true
shift
;;
--debug)
DEBUG=true
shift
;;
*)
log_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Validate environment
if [[ "$ENVIRONMENT" != "development" && "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]]; then
log_error "Invalid environment: $ENVIRONMENT"
exit 1
fi
# Start deployment process
log_info "Starting TankStopp deployment process..."
log_info "Action: $ACTION"
log_info "Environment: $ENVIRONMENT"
log_info "Project: $PROJECT_NAME"
check_requirements
setup_environment
# Execute action
case "$ACTION" in
"deploy")
deploy_application
;;
"start")
start_services
;;
"stop")
stop_services
;;
"restart")
restart_services
;;
"update")
update_application
;;
"rollback")
rollback_deployment
;;
"status")
show_status
;;
"logs")
show_logs
;;
"backup")
backup_database
;;
"restore")
restore_backup
;;
"scale")
scale_service
;;
"cleanup")
cleanup_resources
;;
*)
log_error "Unknown action: $ACTION"
show_help
exit 1
;;
esac
log_success "Deployment process completed successfully!"
}
# Execute main function
main "$@"
+473
View File
@@ -0,0 +1,473 @@
#!/bin/bash
# TankStopp Dockerfile Validation Script
# This script validates Dockerfile syntax and best practices without requiring Docker daemon
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
DOCKERFILE_PATH="Dockerfile"
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
VALIDATION_ERRORS=0
VALIDATION_WARNINGS=0
# Functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
((VALIDATION_WARNINGS++))
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
((VALIDATION_ERRORS++))
}
check_dockerfile_exists() {
log_info "Checking Dockerfile existence..."
if [ ! -f "$PROJECT_ROOT/$DOCKERFILE_PATH" ]; then
log_error "Dockerfile not found at $PROJECT_ROOT/$DOCKERFILE_PATH"
return 1
fi
log_success "Dockerfile found"
return 0
}
validate_dockerfile_syntax() {
log_info "Validating Dockerfile syntax..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check for FROM instruction
if ! grep -q "^FROM " "$dockerfile"; then
log_error "No FROM instruction found"
else
log_success "FROM instruction found"
fi
# Check for multi-stage build
local from_count=$(grep -c "^FROM " "$dockerfile")
if [ "$from_count" -gt 1 ]; then
log_success "Multi-stage build detected ($from_count stages)"
else
log_warning "Single-stage build detected. Consider multi-stage for optimization"
fi
# Check for WORKDIR
if grep -q "^WORKDIR " "$dockerfile"; then
log_success "WORKDIR instruction found"
else
log_warning "No WORKDIR instruction found"
fi
# Check for USER instruction
if grep -q "^USER " "$dockerfile"; then
log_success "USER instruction found (security best practice)"
else
log_warning "No USER instruction found. Running as root is not recommended"
fi
# Check for EXPOSE instruction
if grep -q "^EXPOSE " "$dockerfile"; then
log_success "EXPOSE instruction found"
else
log_warning "No EXPOSE instruction found"
fi
# Check for HEALTHCHECK
if grep -q "^HEALTHCHECK " "$dockerfile"; then
log_success "HEALTHCHECK instruction found"
else
log_warning "No HEALTHCHECK instruction found"
fi
# Check for VOLUME
if grep -q "^VOLUME " "$dockerfile"; then
log_success "VOLUME instruction found"
else
log_warning "No VOLUME instruction found"
fi
}
validate_base_images() {
log_info "Validating base images..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Extract base images
local base_images=$(grep "^FROM " "$dockerfile" | awk '{print $2}')
for image in $base_images; do
if [[ "$image" == *":latest" ]]; then
log_warning "Base image '$image' uses 'latest' tag. Consider pinning to specific version"
elif [[ "$image" =~ :[0-9]+\.[0-9]+ ]]; then
log_success "Base image '$image' uses versioned tag"
else
log_info "Base image: $image"
fi
# Check for official images
if [[ "$image" =~ ^(alpine|golang|node|nginx|redis|postgres|mysql):.* ]]; then
log_success "Using official base image: $image"
fi
done
}
validate_security_practices() {
log_info "Validating security practices..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check if running as root
if grep -q "USER root" "$dockerfile"; then
log_error "Explicitly running as root user"
fi
# Check for package manager cache cleanup
if grep -q "rm -rf /var/cache/apk/\*\|apt-get clean\|yum clean" "$dockerfile"; then
log_success "Package manager cache cleanup found"
else
log_warning "Consider cleaning package manager cache to reduce image size"
fi
# Check for unnecessary packages
if grep -q "curl\|wget" "$dockerfile"; then
local curl_line=$(grep -n "curl\|wget" "$dockerfile" | head -1)
log_info "Network tools found: $curl_line"
fi
# Check for secrets in Dockerfile
if grep -qi "password\|secret\|key\|token" "$dockerfile"; then
log_warning "Potential secrets found in Dockerfile. Ensure no hardcoded credentials"
fi
}
validate_build_optimization() {
log_info "Validating build optimization..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check for layer optimization
local run_count=$(grep -c "^RUN " "$dockerfile")
if [ "$run_count" -gt 10 ]; then
log_warning "Many RUN instructions ($run_count). Consider combining for fewer layers"
else
log_success "Reasonable number of RUN instructions ($run_count)"
fi
# Check for COPY vs ADD
if grep -q "^ADD " "$dockerfile"; then
log_warning "ADD instruction found. Consider using COPY unless URL/tar extraction is needed"
fi
# Check for .dockerignore
if [ -f "$PROJECT_ROOT/.dockerignore" ]; then
log_success ".dockerignore file found"
else
log_warning ".dockerignore file not found. Consider adding to reduce build context"
fi
# Check for build args
if grep -q "^ARG " "$dockerfile"; then
log_success "Build arguments found for flexibility"
fi
}
validate_required_files() {
log_info "Validating required files..."
# Check for Go module files
if [ -f "$PROJECT_ROOT/go.mod" ]; then
log_success "go.mod found"
else
log_error "go.mod not found"
fi
if [ -f "$PROJECT_ROOT/go.sum" ]; then
log_success "go.sum found"
else
log_warning "go.sum not found"
fi
# Check for main.go or cmd directory
if [ -f "$PROJECT_ROOT/main.go" ] || [ -d "$PROJECT_ROOT/cmd" ]; then
log_success "Main Go file or cmd directory found"
else
log_error "No main.go or cmd directory found"
fi
# Check for static files
if [ -d "$PROJECT_ROOT/static" ]; then
log_success "Static directory found"
else
log_warning "Static directory not found"
fi
# Check for configuration files
if ls "$PROJECT_ROOT"/config*.yaml >/dev/null 2>&1; then
log_success "Configuration files found"
else
log_warning "No configuration files found"
fi
}
validate_environment_variables() {
log_info "Validating environment variables..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check for ENV instructions
local env_count=$(grep -c "^ENV " "$dockerfile")
if [ "$env_count" -gt 0 ]; then
log_success "Environment variables defined ($env_count)"
# Show environment variables
grep "^ENV " "$dockerfile" | while read line; do
log_info " $line"
done
else
log_info "No ENV instructions found"
fi
# Check for common application variables
if grep -q "TANKSTOPP_" "$dockerfile"; then
log_success "Application-specific environment variables found"
fi
}
validate_ports_and_volumes() {
log_info "Validating ports and volumes..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check exposed ports
if grep -q "^EXPOSE " "$dockerfile"; then
local ports=$(grep "^EXPOSE " "$dockerfile" | awk '{print $2}')
log_success "Exposed ports: $ports"
fi
# Check volumes
if grep -q "^VOLUME " "$dockerfile"; then
local volumes=$(grep "^VOLUME " "$dockerfile" | sed 's/VOLUME //')
log_success "Volumes defined: $volumes"
fi
}
validate_labels_and_metadata() {
log_info "Validating labels and metadata..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check for labels
if grep -q "^LABEL " "$dockerfile"; then
log_success "Labels found"
grep "^LABEL " "$dockerfile" | while read line; do
log_info " $line"
done
else
log_warning "No labels found. Consider adding metadata labels"
fi
}
validate_entrypoint_and_cmd() {
log_info "Validating entrypoint and command..."
local dockerfile="$PROJECT_ROOT/$DOCKERFILE_PATH"
# Check for ENTRYPOINT
if grep -q "^ENTRYPOINT " "$dockerfile"; then
log_success "ENTRYPOINT instruction found"
fi
# Check for CMD
if grep -q "^CMD " "$dockerfile"; then
log_success "CMD instruction found"
local cmd=$(grep "^CMD " "$dockerfile" | tail -1)
log_info " $cmd"
else
log_warning "No CMD instruction found"
fi
}
check_docker_compose_files() {
log_info "Checking Docker Compose files..."
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
log_success "docker-compose.yml found"
else
log_warning "docker-compose.yml not found"
fi
if [ -f "$PROJECT_ROOT/docker-compose.prod.yml" ]; then
log_success "docker-compose.prod.yml found"
fi
if [ -f "$PROJECT_ROOT/docker-compose.override.yml" ]; then
log_info "docker-compose.override.yml found"
fi
}
validate_build_scripts() {
log_info "Checking build scripts..."
if [ -f "$PROJECT_ROOT/scripts/docker/build.sh" ]; then
log_success "Docker build script found"
if [ -x "$PROJECT_ROOT/scripts/docker/build.sh" ]; then
log_success "Build script is executable"
else
log_warning "Build script is not executable"
fi
else
log_warning "Docker build script not found"
fi
if [ -f "$PROJECT_ROOT/scripts/docker/deploy.sh" ]; then
log_success "Docker deploy script found"
if [ -x "$PROJECT_ROOT/scripts/docker/deploy.sh" ]; then
log_success "Deploy script is executable"
else
log_warning "Deploy script is not executable"
fi
else
log_warning "Docker deploy script not found"
fi
}
generate_recommendations() {
log_info "Generating recommendations..."
echo ""
echo "=== RECOMMENDATIONS ==="
if [ "$VALIDATION_ERRORS" -eq 0 ] && [ "$VALIDATION_WARNINGS" -eq 0 ]; then
log_success "Dockerfile validation passed with no issues!"
else
echo ""
echo "Consider the following improvements:"
echo ""
if grep -q "USER root" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
echo "• Create and use a non-root user for security"
fi
if ! grep -q "HEALTHCHECK" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
echo "• Add HEALTHCHECK instruction for container monitoring"
fi
if ! grep -q "LABEL" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
echo "• Add metadata labels (version, maintainer, description)"
fi
if [ ! -f "$PROJECT_ROOT/.dockerignore" ]; then
echo "• Create .dockerignore to optimize build context"
fi
if ! grep -q "VOLUME" "$PROJECT_ROOT/$DOCKERFILE_PATH" 2>/dev/null; then
echo "• Consider adding VOLUME for data persistence"
fi
echo "• Pin base image versions for reproducible builds"
echo "• Minimize the number of layers by combining RUN commands"
echo "• Remove package manager caches to reduce image size"
echo "• Use multi-stage builds to reduce final image size"
fi
}
show_summary() {
echo ""
echo "=== VALIDATION SUMMARY ==="
echo "Dockerfile: $PROJECT_ROOT/$DOCKERFILE_PATH"
echo "Errors: $VALIDATION_ERRORS"
echo "Warnings: $VALIDATION_WARNINGS"
if [ "$VALIDATION_ERRORS" -eq 0 ]; then
log_success "Dockerfile validation completed successfully!"
echo "Your Dockerfile is ready for building."
else
log_error "Dockerfile validation failed with $VALIDATION_ERRORS errors"
echo "Please fix the errors before building the Docker image."
exit 1
fi
}
main() {
echo "TankStopp Dockerfile Validation"
echo "==============================="
echo ""
cd "$PROJECT_ROOT"
# Run all validations
check_dockerfile_exists || exit 1
validate_dockerfile_syntax
validate_base_images
validate_security_practices
validate_build_optimization
validate_required_files
validate_environment_variables
validate_ports_and_volumes
validate_labels_and_metadata
validate_entrypoint_and_cmd
check_docker_compose_files
validate_build_scripts
# Generate recommendations and summary
generate_recommendations
show_summary
}
# Show help if requested
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
cat << EOF
TankStopp Dockerfile Validation Script
Usage: $0 [OPTIONS]
This script validates your Dockerfile against best practices and security guidelines.
It checks syntax, security practices, optimization, and required files.
OPTIONS:
-h, --help Show this help message
CHECKS PERFORMED:
• Dockerfile syntax validation
• Base image version pinning
• Security best practices
• Build optimization
• Required files presence
• Environment variables
• Port and volume configuration
• Labels and metadata
• Entrypoint and command setup
• Docker Compose files
• Build scripts
EXAMPLES:
# Run validation
$0
# Show help
$0 --help
EOF
exit 0
fi
# Run main function
main "$@"