🐳 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
| # 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
|
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
- Minimal Base Images - Use alpine or distroless images
- Non-Root Users - Run as least-privilege user
- Multi-Stage Builds - Separate build from runtime
- Health Checks - Implement proper liveness/readiness probes
- Signal Handling - Gracefully handle TERM/INT signals
- Resource Limits - Set memory/CPU constraints
- Security Scanning - Scan images for vulnerabilities
- Logging Standards - Output to stdout/stderr
- Configuration - Use environment variables
- 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