Back openDesk Edu for a sovereign, open-source education â every vote counts.
Vote nowSave products you love by clicking the heart icon.
Comprehensive guide to modern container image building tools, patterns, and security hardening techniques for production deployments.
You've seen the symptoms:
vm.swappiness is too aggressivekernel.shm* isn't tuned for shared memoryModern infrastructure layers introduce countless tunable knobs: kernel parameters, Docker daemon settings, container limits, Kubernetes resource policies, and workload-specific adjustments. Getting these right is the difference between a system that holds 10Ă the load and one that collapses at scale.
This guide codifies the tuning patterns from production experience, organized by isolation layer with workload-specific cheat sheets.
Resource controls operate at distinct layers, each with its own knobs and trade-offs:
Each layer can override the layer above it, but defaults flow down incorrectly if not configured intentionally.
| Parameter | Default | Production | Purpose |
|---|---|---|---|
net.core.somaxconn | 128 | 4096 | Max pending SYN queue |
net.core.netdev_max_backlog | 1000 | 5000 | Max packets in NIC backlog |
net.ipv4.tcp_max_syn_backlog | 1024 | 8192 | Max SYN requests |
net.ipv4.tcp_tw_reuse | 0 | 1 | Reuse TIME_WAIT sockets |
net.ipv4.ip_local_port_range | 32768-60999 | 1024-65535 | Ephemeral port range |
net.ipv4.tcp_fin_timeout | 60 | 30 | FIN timeout seconds |
net.ipv4.tcp_keepalive_time | 7200 | 600 | Keepalive idle seconds |
net.ipv4.tcp_keepalive_intvl | 75 | 30 | Keepalive probe interval |
net.ipv4.tcp_keepalive_probes | 9 | 5 | Keepalive probe count |
# /etc/sysctl.d/99-production.conf
net.core.somaxconn = 4096
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# Security
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
Apply immediately:
sudo sysctl -p /etc/sysctl.d/99-production.conf
| Parameter | Default | production | Benchmark guidance |
|---|---|---|---|
vm.swappiness | 60 | 1-10 (DB), 30 (app) | Lower for memory-pressure sensitive workloads |
vm.overcommit_memory | 0 | 1 (K8s nodes) | Allow allocator overcommit (kubelet recomputes) |
vm.overcommit_ratio | 50 | 100 | Percentage of RAM+swap to overcommit |
vm.dirty_ratio | 30 | 20 | % memory where dirty pages start blocking |
vm.dirty_background_ratio | 10 | 5 | % memory where background writeback starts |
vm.min_free_kbytes | 65536 | 1-2% of RAM | Emergency reserve against OOM |
# /etc/sysctl.d/99-memory.conf
vm.swappiness = 10 # Don't swap unless critical
vm.overcommit_memory = 1 # K8s nodes expect this
vm.overcommit_ratio = 100 # Full overcommit
vm.dirty_ratio = 20 # Start blocking writes at 20%
vm.dirty_background_ratio = 5 # Background writeback at 5%
vm.min_free_kbytes = 1048576 # 1GB reserve on 64GB node
# /etc/sysctl.d/99-fs.conf
fs.file-max = 2097152 # Global open file limit
fs.inotify.max_user_instances = 1024
fs.inotify.max_user_watches = 819200 # For etcd, collectd
# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 65535
* hard nproc 65535
root soft nofile 1048576
root hard nofile 1048576
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 1048576, "Soft": 65535 },
"nproc": { "Name": "nproc", "Hard": 65535, "Soft": 4096 }
},
"live-restore": true,
"userns-remap": "default",
"no-new-privileges": false,
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 10,
"data-root": "/var/lib/docker"
}
Docker's default resource roots are generous but can lead to noisy neighbors:
| Resource | Default | Recommended | When to Override |
|---|---|---|---|
--pids-limit | (none) | 100-500 per container | Fork-prone apps need higher |
--memory-swap | unlimited | memory Ă 2 | Disable with --memory-swap -1 to prevent swapping into disk for latency-sensitive DBs |
--memory-reservation | equals limit | 70-80% of limit | Prevent "burst then die" patterns |
--kernel-memory | unlimited | 100-200Mi | Controls non-resident page tables; low defaults cause container crashes under high connection counts |
security-opt no-new-privileges | false | true (recommended) | Blocks setuid escalation |
docker run -d \
--name myapp \
--memory=512m \
--memory-reservation=400m \
--memory-swap=-1 \
--pids-limit=200 \
--ulimit nofile=65535:65535 \
--ulimit nproc=4096:8192 \
--security-opt no-new-privileges \
--security-opt seccomp=default.json \
myapp:latest
Kubernetes assigns QoS based on requests and limits:
| QoS Class | Requirements | Eviction Priority |
|---|---|---|
| Guaranteed | requests.cpu == limits.cpu <br/> requests.memory == limits.memory | Last to evict |
| Burstable | Any requests set <br/> requests != limits | Medium |
| BestEffort | No requests <br/> No limits | First to evict |
# Guaranteed (best for DBs, latency-critical services)
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "2"
memory: "8Gi"
# Burstable (typical for stateless services)
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "4Gi"
# â BestEffort (avoid in production)
resources: {} # or omit entirely
Production rule: Always set requests. At least assign requests.memory = limits.memory / 2 for non-latency-critical workloads.
On cgroup v2, memory.swap accounting is enabled. Ensure kubelet config:
# /var/lib/kubelet/config.yaml
memorySwap:
swapBehavior: NoSwap # NoSwap, LimitedSwap, UnlimitedSwap
limits.memory is ignored for swapapiVersion: v1
kind: Pod
metadata:
name: hugepages-pod
spec:
containers:
- name: app
image: myapp:latest
resources:
limits:
Hugepages-2Mi: 512Mi # 256 Ă 2Mi pages
memory: "1Gi"
volumeMounts:
- mountPath: /hugepages
name: hugepage
volumes:
- name: hugepage
emptyDir:
medium: HugePages
Use for: databases (PostgreSQL, MySQL), DPDK applications, in-memory caches.
# /var/lib/kubelet/config.yaml
cpuManagerPolicy: static # none (default), static
cpuManagerReconcilePeriod: 5s
requests.cpu = integer cores get exclusive cores. Ideal for low-latency workloads.# Pod requesting exclusive CPU 0-1
resources:
requests:
cpu: "2" # Must be integer
limits:
cpu: "2"
# /var/lib/kubelet/config.yaml
topologyManagerPolicy: best-effort # none, best-effort, restricted, single-numa-node
# Pods requesting aligned resources
resources:
requests:
cpu: "4"
memory: "8Gi"
cpu: "4" # Topology manager aligns with same NUMA node
devices.kubernetes.com/mock: "1" # Device affinity
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "50m"
memory: "64Mi"
maxLimitRequestRatio:
cpu: "10"
memory: "4"
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-tuning
data:
# Postgres expects shared memory segments aligned with kernel
# Adjust kernel.shm* if deploying to bare metal VM
# /etc/sysctl.d/99-postgres.conf:
# kernel.shmmax = 4294967296 # 4GB
# kernel.shmall = 1048576 # 4GB pages
# kernel.sem = 250 32000 100 128
postgresql.conf: |
shared_buffers = 4GB # 25% of RAM
effective_cache_size = 12GB # 75% of RAM
work_mem = 64MB # per-operation sort memory
maintenance_work_mem = 1GB
max_connections = 200
wal_buffers = 128MB
checkpoint_completion_target = 0.9
---
apiVersion: v1
kind: Pod
metadata:
name: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi" # OOM target is 2Ă requests
securityContext:
fsGroup: 999 # Fix permission issues on /var/run/postgresql
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: data
- mountPath: /var/run/postgresql
name: run
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-pvc
- name: run
emptyDir: {}
resources:
requests:
cpu: "500m"
memory: "4Gi"
limits:
cpu: "2"
memory: "8Gi"
# In redis.conf:
# maxmemory 6gb
# maxmemory-policy allkeys-lru
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "1"
memory: "512Mi"
# In nginx.conf:
# worker_processes auto;
# worker_connections 4096; # Watch open file limits
# worker_rlimit_nofile 65535;
# multi_accept on;
# access_log off; # For high-throughput, pipe to syslog
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "8"
memory: "32Gi"
# In server.properties:
# log.dirs = /kafka/data
# num.network.threads = 8
# num.io.threads = 16
# socket.send.buffer.bytes = 102400
# socket.receive.buffer.bytes = 102400
# log.flush.interval.messages = 10000
# log.flush.interval.ms = 1000
# num Partitions: 2Ă broker count
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi"
# In jvm.options:
# -Xms8g
# -Xmx8g # Don't exceed 50% of pod memory for heap
# In elasticsearch.yml:
# bootstrap.memory_lock: true
# indices.queries.cache.size: 10%
# indices.fielddata.cache.size: 20%
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
# JVM args:
# -Xms512m # Start with 512MB heap
# -Xmx1536m # Limit heap to 50% of pod memory (non-heap overhead)
# -XX:+UseG1GC
# -XX:MaxGCPauseMillis=200
# -XX:+PrintGCDetails
# -XX:+PrintGCDateStamps
# -Xloggc:/logs/gc.log
apiVersion: v1
kind: Pod
metadata:
name: ml-worker
spec:
runtimeClassName: nvidia # For NVIDIA GPU support
containers:
- name: ml-app
image: pytorch:24.01-cuda12.1
resources:
requests:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: 1
limits:
cpu: "8"
memory: "32Gi"
nvidia.com/gpu: 1
env:
- name: CUDA_VISIBLE_DEVICES
value: "0"
- name: NVIDIA_VISIBLE_DEVICES
value: "all"
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # If needed
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
ephemeral-storage: "1Gi"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: run
mountPath: /var/run
volumes:
- name: tmp
emptyDir:
sizeLimit: "100Mi"
- name: run
emptyDir:
sizeLimit: "50Mi"
The RuntimeDefault profile blocks dangerous syscalls:
kexec_load, module_load, delete_module (kernel persistence)ptrace, process_vm_readv/writev (process manipulation)mount, umount, pivot_root (filesystem remapping)clock_settime, settimeofday (system time manipulation)bpf (unprivileged BPF is blocked by kernel.unprivileged_bpf_disabled=1)Use custom profiles only when required (e.g., some FUSE workloads).
# Network
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.ipv4.ip_local_port_range
# Memory
sysctl vm.swappiness vm.dirty_ratio vm.min_free_kbytes
# Filesystem
sysctl fs.file-max fs.inotify.max_user_watches
# Per-node
cat /proc/sys/vm/swappiness
cat /proc/sys/net/core/somaxconn
# For a container, find its cgroup path
docker inspect --format '{{.State.Pid}}' myapp | xargs cat /proc/$$/cgroup
# Check limits
cat /sys/fs/cgroup/memory.max # Memory limit
cat /sys/fs/cgroup/cpu.max # CPU quota (e.g., "200000 100000")
cat /sys/fs/cgroup/pids.max # PID limit
cat /sys/fs/cgroup/memory.swap.max # Swap limit
kubectl get pod -o jsonpath='{.metadata.name}{"\t"}{.status.qosClass}{"\n"}'
# Guaranteed? Look at .spec.containers[].resources
kubectl get pod -o json | jq '.items[] | select(.metadata.name=="myapp") |
{
name: .metadata.name,
qos: .status.qosClass,
resources: .spec.containers[].resources
}'
# From Prometheus
rate(container_cpu_cfs_throttled_periods_total[5m]) / rate(container_cpu_cfs_periods_total[5m])
# Alert if > 0.5 (50% throttled)
# Check per-container fd count
find /proc/*/fd 2>/dev/null | xargs -I{} sh -c 'echo $(dirname $(dirname {})): $(ls {}/fd | wc -l)' | sort -rn | head
# Or use cgroup
cat /proc/{PID}/limits | grep "Max open files"
# On host
dmesg | grep -i "killed process"
# On K8s
kubectl get pod -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}{end}' | grep OOMKilled
requests on All Pods# Bad
resources:
limits:
memory: "512Mi"
cpu: "1"
# becomes BestEffort if requests not set
Result: First to evict; unpredictable scheduling; node resource exhaustion.
kernel.shmmax Too Low# PostgreSQL fails to start:
# FATAL: could not create shared memory segment: Invalid argument
Fix: Adjust kernel parameters for database pods:
# /etc/sysctl.d/99-database.conf
kernel.shmmax = 68719476736 # 64GB
kernel.shmall = 16777216 # 64GB / 4096 (page size)
--pids-limit Not Set# Fork bomb in one container can crash the entire node
Fix: Set limits on daemon or container:
{
"default-pids-limit": 200
}
Or per-container:
docker run --pids-limit 200 myapp
# Default: limits.memory=512Mi, memory-swap=unlimited
# â Container can swap to disk (slow) and exhaust host swap
Fix: Set --memory-swap=-1 to disable swapping, or kubelet memorySwap.swapBehavior: NoSwap.
runAsNonRoot + fsGroup Mismatch# PostgreSQL sleeps at startup, can't read/write /var/run/postgresql
Fix: Set fsGroup to match DB user:
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999
# Kubernetes operations break when clocks are set back in time
Fix: Use NTP with maxslewrate or makestep to allow large forward jumps but reject backward jumps:
# /etc/chrony.conf
maxslewrate 1000 # Allow 1000x faster clock catch-up
makestep 1.0 -1 # Step clock if offset > 1 second (forward only)
# Security review fails: "RuntimeDefault not set"
Fix: Explicitly set profile:
podSecurityContext:
seccompProfile:
type: RuntimeDefault
# Alert: Pod Memory Usage Nearing Limit
- alert: PodMemoryHigh
expr: container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} memory > 90% of limit"
# Alert: OOM Kill
- alert: PodOOMKilling
expr: rate(kube_pod_container_status_restarts_total{reason="OOMKilled"}[5m]) > 0
for: 0m
annotations:
summary: "Container {{ $labels.container }} in pod {{ $labels.pod }} OOM killed"
RCA:
limits.memory is appropriate (is app memory-intensive?)kubectl logs ... + heap dump (JVM) or ps + pmap (native)requests.memory and limits.memory# Alert: High CPU Throttling
- alert: HighCPUThrottling
expr: |
rate(container_cpu_cfs_throttled_periods_total[5m]) /
rate(container_cpu_cfs_periods_total[5m]) > 0.5
for: 10m
annotations:
summary: "Container {{ $labels.container }} throttled > 50%"
RCA:
limits.cpu)nodeSelectorstatic policy for guaranteed exclusive cores# Alert: TCP Listen Drops / SYN Flood
- alert: TCPListenDrops
expr: rate(netstat_Tcp_ListenDrops[5m]) > 10
for: 5m
annotations:
summary: "High TCP listen drops (increase net.core.somaxconn?)"
RCA:
net.core.somaxconn on nodenet.ipv4.tcp_max_syn_backlogsomaxconn=4096, tcp_max_syn_backlog=8192, tw_reuse=1swappiness=10 (DB), dirty_ratio=20, min_free_kbytes=1-2%file-max=2097152, inotify.max_user_watches=819200kexec_load_disabled=1, unprivileged_bpf_disabled=1, yama.ptrace_scope=2default-ulimits set (nofile, nproc)userns-remap=default (UID remap)live-restore=true (daemon restart resilience)log-opts.max-size=50m (log rotation)LimitRange defined (default requests + limits)ResourceQuota defined (prevent namespace exhaustion)pod-security.kubernetes.io/enforce=restrictedNetworkPolicy default-deny + explicit allowrequests.cpu and requests.memory set alwayslimits.cpu and limits.memory set (or reason documented)securityContext.runAsNonRootallowPrivilegeEscalation=falsereadOnlyRootFilesystem=true with tmpfs mountsseccompProfile.type=RuntimeDefaultpids-limit set (on daemon or container)volumeMounts for /tmp, /var/run, /var/cachekernel.shm* tuned via host sysctlsmaxmemory + maxmemory-policy setworker_connections, worker_rlimit_nofile aligned-Xms/-Xmx = 25-50% of pod memoryruntimeClassName: nvidia, nvidia.com/gpu requestsrequests: BestEffort is first to evict and unpredictablecap-drop ALL, no-new-privileges, seccomp=RuntimeDefault reduce attack surface without performance costdmesg, sysctl, and container logs to identify bottlenecksmaxmemory, JVM needs headroom beyond heap--file=/etc/sysctl.d/*.conf) and kubelet configsA well-tuned environment holds 10Ă the traffic, recovers gracefully from failures, and remains secure against common container escape vectors.
man sysctl, man cgroupsbcc-tools, bpftrace for advanced containers debug