docker.recipes
Fundamentals10 min read

Docker Compose Performance Tuning

Optimize your Docker Compose deployments for better performance, lower resource usage, and faster startup times.

01Introduction

Default Docker configurations prioritize compatibility over performance. With some tuning, you can significantly reduce resource usage, speed up container startup, and improve application responsiveness. This guide covers practical optimizations for self-hosted setups.

02Optimize Your Images

Smaller images start faster and use less disk I/O. Choose minimal base images and remove unnecessary components.
1# BAD: Full OS images
2services:
3 app:
4 image: ubuntu:22.04 # 77MB
5
6# GOOD: Minimal images
7services:
8 app:
9 image: alpine:3.19 # 7MB
10
11# BETTER: Distroless (for compiled apps)
12# FROM gcr.io/distroless/static # ~2MB
13
14# Compare image sizes
15docker images --format "table {{.Repository}}: {{.Tag}} {{.Size}}"
16
17# Common lightweight alternatives:
18# python:3.11 -> python:3.11-slim or python:3.11-alpine
19# node:20 -> node:20-slim or node:20-alpine
20# postgres:15 -> postgres:15-alpine
21# nginx:latest -> nginx:alpine

Alpine-based images are typically 10x smaller than Debian-based. But test compatibility—some apps need glibc instead of musl.

03Set Resource Limits

Without limits, one container can starve others. Set appropriate limits based on actual usage.
1services:
2 app:
3 image: myapp:1.0
4 deploy:
5 resources:
6 limits:
7 cpus: '2' # Max 2 CPU cores
8 memory: 1G # Max 1GB RAM
9 reservations:
10 cpus: '0.5' # Guaranteed 0.5 cores
11 memory: 256M # Guaranteed 256MB RAM
12
13 # Database with higher memory
14 postgres:
15 image: postgres:15-alpine
16 deploy:
17 resources:
18 limits:
19 cpus: '2'
20 memory: 2G
21 reservations:
22 memory: 512M
23 # Postgres-specific tuning in postgresql.conf

04Establish Resource Baseline

Before tuning, understand current resource usage. Monitor over time to set appropriate limits.
1# Real-time container stats
2docker stats
3
4# Container-specific detailed stats
5docker stats --no-stream --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}} {{.BlockIO}}"
6
7# Historical monitoring with ctop
8docker run --rm -it \
9 -v /var/run/docker.sock:/var/run/docker.sock \
10 quay.io/vektorlab/ctop
11
12# Or use Prometheus + Grafana stack
13services:
14 cadvisor:
15 image: gcr.io/cadvisor/cadvisor:latest
16 volumes:
17 - /:/rootfs:ro
18 - /var/run:/var/run:ro
19 - /sys:/sys:ro
20 - /var/lib/docker/:/var/lib/docker:ro

05Storage Optimization

Docker's storage driver affects performance significantly. Use the right driver and optimize volume mounts.
1# Check current storage driver
2docker info | grep "Storage Driver"
3# overlay2 is recommended for most setups
4
5# For database workloads, use named volumes with proper mount options
6services:
7 postgres:
8 volumes:
9 - type: volume
10 source: postgres_data
11 target: /var/lib/postgresql/data
12 volume:
13 nocopy: true # Skip initial copy, faster start
14
15volumes:
16 postgres_data:
17 driver: local
18 driver_opts:
19 type: none
20 device: /mnt/fast-ssd/postgres # Put on fast storage
21 o: bind
22
23# Avoid storing database data on overlay filesystem
24# Use bind mounts to fast local storage

For databases, use local NVMe/SSD storage with bind mounts. Don't rely on Docker's storage driver for I/O-heavy workloads.

06Network Optimization

Docker's default bridge network adds overhead. For performance-critical services, consider host networking or optimized bridge settings.
1# For maximum network performance: host mode
2# (Removes network isolation!)
3services:
4 high-perf-app:
5 network_mode: host
6
7# For bridge mode, use custom networks with optimal settings
8networks:
9 optimized:
10 driver: bridge
11 driver_opts:
12 com.docker.network.driver.mtu: 9000 # Jumbo frames if supported
13
14# Enable IPv6 if needed (can improve certain workloads)
15# In /etc/docker/daemon.json:
16{
17 "ipv6": true,
18 "fixed-cidr-v6": "fd00::/64"
19}

07Logging Optimization

Default JSON logging is slow for high-volume applications. Choose the right logging driver.
1services:
2 high-volume-app:
3 logging:
4 driver: "local" # Faster than json-file
5 options:
6 max-size: "10m"
7 max-file: "3"
8 compress: "true"
9
10 # For apps where you don't need logs in Docker
11 batch-job:
12 logging:
13 driver: "none" # Completely disable
14
15# Global configuration in /etc/docker/daemon.json
16{
17 "log-driver": "local",
18 "log-opts": {
19 "max-size": "10m",
20 "max-file": "3"
21 }
22}

The 'none' driver means no logs via docker logs. Only use for containers with external logging.

08Faster Container Startup

Reduce container startup time with proper dependencies and health checks.
1services:
2 app:
3 depends_on:
4 db:
5 condition: service_healthy # Wait for DB to be ready
6 # Don't wait for everything, just essentials
7
8 db:
9 image: postgres:15-alpine
10 healthcheck:
11 test: ["CMD-SHELL", "pg_isready -U postgres"]
12 interval: 2s # Check frequently
13 timeout: 1s # Fail fast
14 retries: 10 # Allow slow starts
15 start_period: 5s
16
17# Parallel startup where possible
18# Services without dependencies start simultaneously
19
20# Avoid unnecessary restarts
21restart: unless-stopped
22# Not 'always' - prevents restart loops

Use start_period in healthchecks to give slow-starting containers time to initialize before health checks begin.

09Database-Specific Tuning

Databases benefit from specific tuning. Here are common optimizations for popular databases.
1services:
2 postgres:
3 image: postgres:15-alpine
4 environment:
5 # Basic performance tuning
6 - POSTGRES_INITDB_ARGS=--data-checksums
7 volumes:
8 - ./postgresql.conf:/etc/postgresql/postgresql.conf:ro
9 command: postgres -c config_file=/etc/postgresql/postgresql.conf
10
11# postgresql.conf tuning for 4GB RAM server:
12# shared_buffers = 1GB # 25% of RAM
13# effective_cache_size = 3GB # 75% of RAM
14# work_mem = 64MB
15# maintenance_work_mem = 256MB
16# max_connections = 100
17
18 redis:
19 image: redis:alpine
20 command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
21
22 mysql:
23 image: mysql:8
24 command: --innodb-buffer-pool-size=1G --innodb-log-file-size=256M

10Add Caching Layers

Reduce load on databases and APIs with caching. Redis is the go-to choice for Docker deployments.
1services:
2 app:
3 environment:
4 - REDIS_URL=redis://redis:6379
5 depends_on:
6 - redis
7
8 redis:
9 image: redis:alpine
10 command: >
11 redis-server
12 --maxmemory 512mb
13 --maxmemory-policy allkeys-lru
14 --appendonly no
15 volumes:
16 - redis_data:/data
17 deploy:
18 resources:
19 limits:
20 memory: 768M # More than maxmemory for overhead
21
22 # Or use Varnish for HTTP caching
23 varnish:
24 image: varnish:stable
25 volumes:
26 - ./default.vcl:/etc/varnish/default.vcl:ro
27 environment:
28 - VARNISH_SIZE=256M
29
30volumes:
31 redis_data:

11Docker Daemon Tuning

Global Docker settings that affect all containers.
1# /etc/docker/daemon.json
2{
3 "storage-driver": "overlay2",
4 "log-driver": "local",
5 "log-opts": {
6 "max-size": "10m",
7 "max-file": "3"
8 },
9 "default-ulimits": {
10 "nofile": {
11 "Name": "nofile",
12 "Hard": 65536,
13 "Soft": 65536
14 }
15 },
16 "live-restore": true,
17 "userland-proxy": false,
18 "default-shm-size": "128M"
19}
20
21# Apply changes
22sudo systemctl restart docker
23
24# Verify
25docker info

live-restore keeps containers running during Docker daemon restarts—great for updates.

12Continuous Monitoring

Performance tuning is ongoing. Set up monitoring to catch issues early.
1# Lightweight monitoring stack
2services:
3 prometheus:
4 image: prom/prometheus:latest
5 volumes:
6 - ./prometheus.yml:/etc/prometheus/prometheus.yml
7 - prometheus_data:/prometheus
8 command:
9 - '--config.file=/etc/prometheus/prometheus.yml'
10 - '--storage.tsdb.retention.time=15d'
11
12 grafana:
13 image: grafana/grafana:latest
14 volumes:
15 - grafana_data:/var/lib/grafana
16 environment:
17 - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
18
19 node-exporter:
20 image: prom/node-exporter:latest
21 pid: host
22 volumes:
23 - /proc:/host/proc:ro
24 - /sys:/host/sys:ro
25 command:
26 - '--path.procfs=/host/proc'
27 - '--path.sysfs=/host/sys'
28
29volumes:
30 prometheus_data:
31 grafana_data: