#!/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 "$@"