Przejdź do treści

🐳 Advanced Shell in Containers

Master shell scripting within containerized environments, addressing unique challenges and leveraging container-specific optimizations.

🧭 Container Environment Constraints

Containers impose specific limitations that affect shell scripting:

Filesystem Isolation

  • Root filesystem is layered and ephemeral
  • No persistent state between container runs
  • Limited write access to certain directories
  • Mount points must be explicitly configured

Process Model

  • PID 1 is the main container process
  • No traditional init system
  • Signal handling differs from bare metal
  • Process lifecycle tied to container lifecycle

Network Isolation

  • Network namespace separation
  • Limited outbound connectivity
  • Service discovery through DNS or environment
  • Port mapping required for external access

🧪 Container-Specific Scripting Patterns

Entrypoint and CMD Design

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env bash
# Container entrypoint script

set -euo pipefail

# Signal handling for graceful shutdown
shutdown_requested=false

graceful_shutdown() {
    echo "Shutdown signal received"
    shutdown_requested=true
}

trap graceful_shutdown TERM INT

# Initialize application
initialize_app() {
    echo "Initializing application..."

    # Create necessary directories
    mkdir -p /app/logs /app/data /app/tmp

    # Set proper permissions
    chown -R app:app /app

    # Load configuration
    if [ -f /config/app.conf ]; then
        source /config/app.conf
    fi

    # Validate environment
    : "${DATABASE_URL:?DATABASE_URL is required}"
    : "${REDIS_URL:?REDIS_URL is required}"
}

# Application main loop
run_app() {
    echo "Starting application..."

    # Start application in background
    exec /app/bin/application &
    local app_pid=$!

    # Monitor application
    while [ "$shutdown_requested" = false ]; do
        if ! kill -0 $app_pid 2>/dev/null; then
            echo "Application process died"
            exit 1
        fi
        sleep 1
    done

    # Graceful shutdown
    echo "Shutting down application..."
    kill -TERM $app_pid
    wait $app_pid
}

# Main execution
initialize_app
run_app

🧠 Multi-Stage Build Optimization

Dockerfile Best Practices

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Multi-stage build for optimal size
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine AS production

# Install only runtime dependencies
RUN apk add --no-cache \
    curl \
    jq \
    bash

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs

WORKDIR /app

# Copy built artifacts
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs package.json .

# Copy shell scripts
COPY --chown=nodejs:nodejs scripts/ ./scripts/

# Switch to non-root user
USER nodejs

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

EXPOSE 3000
ENTRYPOINT ["./scripts/entrypoint.sh"]
CMD ["npm", "start"]

🧪 Container Initialization Scripts

Comprehensive Init Script

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/usr/bin/env bash
# Container initialization script

set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly APP_USER="app"
readonly APP_GROUP="app"

# Logging functions
log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }
log_warn() { echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }
log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2; }

# Error handling
error_exit() {
    local line_no=$1
    local exit_code=$2
    local message=$3
    log_error "Error on line $line_no: $message (exit code: $exit_code)"
    exit $exit_code
}

trap 'error_exit $LINENO $? "Unexpected error"' ERR

# Validate environment
validate_environment() {
    log_info "Validating environment..."

    # Required environment variables
    local required_vars=(
        "DATABASE_URL"
        "REDIS_URL"
        "JWT_SECRET"
    )

    for var in "${required_vars[@]}"; do
        if [ -z "${!var:-}" ]; then
            log_error "Required environment variable $var is not set"
            exit 1
        fi
    done

    # Validate URLs
    if ! echo "$DATABASE_URL" | grep -E '^postgres://|^mysql://' >/dev/null; then
        log_error "Invalid DATABASE_URL format"
        exit 1
    fi

    log_info "Environment validation passed"
}

# Setup filesystem
setup_filesystem() {
    log_info "Setting up filesystem..."

    # Create application directories
    local dirs=(
        "/app/logs"
        "/app/data"
        "/app/tmp"
        "/app/uploads"
    )

    for dir in "${dirs[@]}"; do
        mkdir -p "$dir"
        chown "$APP_USER:$APP_GROUP" "$dir"
        chmod 755 "$dir"
    done

    # Set up temporary directory
    export TMPDIR="/app/tmp"

    # Create log files
    touch /app/logs/app.log /app/logs/error.log
    chown "$APP_USER:$APP_GROUP" /app/logs/*.log
    chmod 644 /app/logs/*.log

    log_info "Filesystem setup completed"
}

# Configure application
configure_application() {
    log_info "Configuring application..."

    # Generate config file from environment
    cat > /app/config/app.json <<EOF
{
  "database": {
    "url": "$DATABASE_URL",
    "pool": {
      "min": ${DB_POOL_MIN:-2},
      "max": ${DB_POOL_MAX:-10}
    }
  },
  "redis": {
    "url": "$REDIS_URL",
    "ttl": ${REDIS_TTL:-3600}
  },
  "server": {
    "port": ${PORT:-3000},
    "host": "0.0.0.0"
  },
  "logging": {
    "level": "${LOG_LEVEL:-info}",
    "file": "/app/logs/app.log"
  }
}
EOF

    # Validate generated config
    if ! jq empty /app/config/app.json; then
        log_error "Generated config is invalid JSON"
        exit 1
    fi

    chown "$APP_USER:$APP_GROUP" /app/config/app.json
    chmod 644 /app/config/app.json

    log_info "Application configuration completed"
}

# Run database migrations
run_migrations() {
    if [ "${RUN_MIGRATIONS:-false}" = "true" ]; then
        log_info "Running database migrations..."

        # Switch to app user for migration
        su -s /bin/bash "$APP_USER" -c "
            cd /app
            npm run migrate
        "

        log_info "Database migrations completed"
    else
        log_info "Skipping database migrations"
    fi
}

# Health check setup
setup_health_check() {
    log_info "Setting up health check..."

    # Create health check script
    cat > /app/scripts/health-check.sh <<'EOF'
#!/bin/bash
set -e

# Check if application is listening
if ! nc -z localhost ${PORT:-3000}; then
    echo "Application not listening on port ${PORT:-3000}"
    exit 1
fi

# Check database connectivity
if ! node -e "
    const url = require('url');
    const dbUrl = process.env.DATABASE_URL;
    const parsed = url.parse(dbUrl);
    require('net').connect(parsed.port, parsed.hostname)
        .on('error', () => process.exit(1))
        .on('connect', () => process.exit(0));
"; then
    echo "Database connection failed"
    exit 1
fi

# Check Redis connectivity
if ! redis-cli ping >/dev/null; then
    echo "Redis connection failed"
    exit 1
fi

echo "Health check passed"
exit 0
EOF

    chmod +x /app/scripts/health-check.sh
    chown "$APP_USER:$APP_GROUP" /app/scripts/health-check.sh

    log_info "Health check setup completed"
}

# Main initialization
main() {
    log_info "Starting container initialization..."

    validate_environment
    setup_filesystem
    configure_application
    run_migrations
    setup_health_check

    log_info "Container initialization completed successfully"
}

# Run main function
main "$@"

🧠 Container Debugging and Troubleshooting

Interactive Debugging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Enter running container for debugging
docker exec -it container_name /bin/bash

# Or with specific user
docker exec -it -u app container_name /bin/bash

# Run one-off debug commands
docker exec container_name ps aux
docker exec container_name netstat -tlnp
docker exec container_name df -h

Container-Specific Diagnostics

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env bash
# Container diagnostics script

diagnose_container() {
    echo "=== Container Diagnostics ==="

    # System information
    echo "Date: $(date)"
    echo "Uptime: $(uptime)"
    echo "Load average: $(cat /proc/loadavg)"

    # Memory usage
    echo "Memory usage:"
    free -h

    # Disk usage
    echo "Disk usage:"
    df -h

    # Network interfaces
    echo "Network interfaces:"
    ip addr show

    # Running processes
    echo "Top processes:"
    ps aux --sort=-%cpu | head -10

    # Environment variables
    echo "Key environment variables:"
    env | grep -E '^(DATABASE|REDIS|PORT|LOG)' | sort

    # Application logs
    echo "Recent application logs:"
    tail -20 /app/logs/app.log 2>/dev/null || echo "No application logs found"

    # Health check status
    echo "Health check result:"
    /app/scripts/health-check.sh 2>&1 || echo "Health check failed"

    echo "=== End Diagnostics ==="
}

# Run diagnosis
diagnose_container

🧪 Container Resource Management

Resource Limiting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Docker run with resource limits
docker run \
    --memory=512m \
    --memory-swap=1g \
    --cpus=0.5 \
    --pids-limit=100 \
    --ulimit nofile=1024:2048 \
    my-app:latest

# Kubernetes resource limits
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    image: my-app:latest
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"

Memory Optimization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Container memory profiling script
profile_memory() {
    echo "=== Memory Profile ==="

    # Current memory usage
    echo "Current memory usage:"
    cat /sys/fs/cgroup/memory/memory.usage_in_bytes
    echo "Memory limit:"
    cat /sys/fs/cgroup/memory/memory.limit_in_bytes

    # Process memory usage
    echo "Top memory consumers:"
    ps aux --sort=-%mem | head -10

    # JVM heap usage (if applicable)
    if pgrep java >/dev/null; then
        echo "JVM heap usage:"
        jstat -gc $(pgrep java) 2>/dev/null || echo "jstat not available"
    fi

    echo "=== End Memory Profile ==="
}

🧠 Container Security Best Practices

Security-Enhanced Container Scripts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env bash
# Security-focused container script

set -euo pipefail

# Drop privileges immediately
if [ "$(id -u)" -eq 0 ]; then
    echo "Dropping root privileges..."
    exec su -s /bin/bash app -c "exec $0 $*"
fi

# Validate user context
if [ "$(id -un)" != "app" ]; then
    echo "Error: Script must run as 'app' user"
    exit 1
fi

# Set restrictive umask
umask 077

# Secure temporary directory
export TMPDIR="/app/tmp"
mkdir -p "$TMPDIR"
chmod 700 "$TMPDIR"

# Validate input parameters
validate_input() {
    local input="$1"

    # Prevent path traversal
    if echo "$input" | grep -E '[/\\.][/.]' >/dev/null; then
        echo "Error: Invalid input - potential path traversal"
        exit 1
    fi

    # Prevent command injection
    if echo "$input" | grep -E '[;&|`$()]' >/dev/null; then
        echo "Error: Invalid input - potential command injection"
        exit 1
    fi
}

# Secure file operations
secure_copy() {
    local src="$1"
    local dst="$2"

    # Validate source
    if [ ! -f "$src" ]; then
        echo "Error: Source file not found: $src"
        exit 1
    fi

    # Validate destination path
    local dst_dir
    dst_dir=$(dirname "$dst")
    if [ ! -w "$dst_dir" ]; then
        echo "Error: Cannot write to destination directory: $dst_dir"
        exit 1
    fi

    # Copy with validation
    cp "$src" "$dst"
    chmod 600 "$dst"
}

# Main application logic would go here

🧪 Container Orchestration Integration

Kubernetes Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env bash
# Kubernetes-aware container script

set -euo pipefail

# Detect Kubernetes environment
is_kubernetes() {
    [ -n "${KUBERNETES_SERVICE_HOST:-}" ]
}

# Get pod information
get_pod_info() {
    if is_kubernetes; then
        echo "Pod Name: ${HOSTNAME:-unknown}"
        echo "Namespace: ${POD_NAMESPACE:-default}"
        echo "Node Name: ${NODE_NAME:-unknown}"
        echo "Pod IP: ${POD_IP:-unknown}"
    else
        echo "Not running in Kubernetes"
    fi
}

# Kubernetes liveness probe
liveness_probe() {
    # Check if main process is running
    if ! pgrep -f "application" >/dev/null; then
        echo "Main application process not found"
        exit 1
    fi

    # Check if port is listening
    if ! nc -z localhost ${PORT:-3000} 2>/dev/null; then
        echo "Application port not listening"
        exit 1
    fi

    echo "Liveness probe passed"
}

# Kubernetes readiness probe
readiness_probe() {
    # Check if application is ready to serve requests
    if ! curl -sf http://localhost:${PORT:-3000}/ready >/dev/null; then
        echo "Application not ready"
        exit 1
    fi

    echo "Readiness probe passed"
}

# Handle Kubernetes lifecycle hooks
pre_stop_hook() {
    echo "Pre-stop hook triggered"

    # Graceful shutdown
    echo "Shutting down gracefully..."
    # Application-specific shutdown logic here

    # Wait for connections to drain
    sleep ${GRACEFUL_SHUTDOWN_PERIOD:-30}

    echo "Pre-stop hook completed"
}

# Main execution based on command
case "${1:-}" in
    "liveness")
        liveness_probe
        ;;
    "readiness")
        readiness_probe
        ;;
    "pre-stop")
        pre_stop_hook
        ;;
    "info")
        get_pod_info
        ;;
    *)
        # Default application startup
        exec "$@"
        ;;
esac

🧠 Container Performance Optimization

Startup Optimization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env bash
# Optimized container startup script

set -euo pipefail

# Measure startup time
START_TIME=$(date +%s.%N)

log_startup_time() {
    local message="$1"
    local current_time
    current_time=$(date +%s.%N)
    local elapsed
    elapsed=$(echo "$current_time - $START_TIME" | bc)
    echo "[STARTUP] ${elapsed}s - $message" >&2
}

# Fast initialization
fast_init() {
    log_startup_time "Starting fast initialization"

    # Use built-in commands instead of external tools
    local app_dir="/app"

    # Check directory existence with test instead of ls
    if [ ! -d "$app_dir/data" ]; then
        mkdir -p "$app_dir/data"
    fi

    # Use parameter expansion instead of external commands
    local config_file="${CONFIG_FILE:-$app_dir/config.json}"

    # Validate with built-in test
    if [ ! -f "$config_file" ]; then
        echo "Config file not found: $config_file" >&2
        exit 1
    fi

    log_startup_time "Fast initialization completed"
}

# Lazy loading pattern
lazy_load_dependency() {
    local dep_name="$1"
    local dep_var="${dep_name}_loaded"

    if [ "${!dep_var:-false}" != "true" ]; then
        log_startup_time "Loading $dep_name"
        # Load dependency here
        declare "$dep_var=true"
    fi
}

# Main execution
main() {
    log_startup_time "Container startup beginning"

    fast_init

    # Lazy load dependencies as needed
    # lazy_load_dependency "database"
    # lazy_load_dependency "cache"

    log_startup_time "Container ready"

    # Execute main application
    exec "$@"
}

main "$@"

🧾 Container Best Practices Summary

Key Principles

  1. Minimal Base Images - Use alpine or distroless images
  2. Non-Root Users - Run as least-privilege user
  3. Multi-Stage Builds - Separate build from runtime
  4. Health Checks - Implement proper liveness/readiness probes
  5. Signal Handling - Gracefully handle TERM/INT signals
  6. Resource Limits - Set memory/CPU constraints
  7. Security Scanning - Scan images for vulnerabilities
  8. Logging Standards - Output to stdout/stderr
  9. Configuration - Use environment variables
  10. Statelessness - Avoid persistent local state

Common Anti-Patterns to Avoid

  • Running as root user
  • Installing unnecessary packages
  • Using latest tag in production
  • Hardcoding configuration
  • Ignoring signals
  • Not validating inputs
  • Poor error handling
  • Inefficient startup scripts

🧾 Summary

  • Understand container-specific constraints and limitations
  • Design proper entrypoint and initialization scripts
  • Optimize Dockerfiles with multi-stage builds
  • Implement comprehensive health checks
  • Master container debugging techniques
  • Apply security best practices
  • Integrate with orchestration platforms
  • Optimize performance and startup time
  • Follow container-native design patterns
  • Avoid common container anti-patterns

👉 Continue to: Performance and FD Usage