Server Sizing & Tuning Guide¶
To ensure optimal performance and resource efficiency, choose your infrastructure tier based on the scale of your workload. Mdlwr uses a dedicated multi-container architecture isolating the web application, separate background workers (job, row, email), schedulers, streams, and database engines.
Infrastructure Tiers (S to XXL)¶
| Size | Spec (vCPU/RAM) | App Workers (Gunicorn) | Worker (Job/Row) | DB Max Conn | DB Shared Buffers | Docker Mem Limit (Total) |
|---|---|---|---|---|---|---|
| XS* | 2 / 2GB | -w 1 |
-c 1 / -c 1 |
100 | 64 MB | 1.5 GB |
| S | 4 / 4GB | -w 2 |
-c 2 / -c 2 |
150 | 512 MB | 3.5 GB |
| M | 4 / 8GB | -w 4 |
-c 2 / -c 4 |
250 | 2 GB | 7 GB |
| L | 8 / 16GB | -w 6 |
-c 4 / -c 8 |
500 | 4 GB | 14 GB |
| XL | 32 / 64GB | -w 17 |
-c 16 / -c 32 |
2000 | 16 GB | 58 GB |
IMPORTANT NOTES ON TIERS:
Tier XS (Dev/Testing Only): This tier is intended ONLY for development environments, light staging, or local testing purposes. It is strictly not recommended for Production use.
When deploying on Tier XS (2GB RAM), the host OS must configure at least a 2 GB Swap file to prevent container terminations caused by OOM (Out-Of-Memory) kills during sudden memory spikes.
Production Docker Compose Template¶
Use the standard docker-compose.yml template below for production deployment. You can adjust resource limits (mem_limit, cpus) and concurrency parameters (-w / --concurrency) on each service according to your chosen server tier.
x-logging: &default-logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
app:
<<: *default-logging
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
user: root
command: gunicorn run:app -w 1 --threads 2 -b 0.0.0.0:5000 --timeout 120 --graceful-timeout 5
stop_grace_period: 5s
env_file:
- .env
environment:
TZ: Asia/Jakarta
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
- ${MDLWR_BASE}/apps/static:/app/apps/static
- ${MDLWR_BASE}/apps/static/custom:/app/apps/static/custom
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 1
restart: always
mem_limit: 600m
cpus: "0.7"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/"]
interval: 3s
timeout: 2s
retries: 5
start_period: 3s
terminal:
<<: *default-logging
container_name: terminal
image: ghcr.io/ikhsanrasyidi/mdlwr-terminal:latest
stop_grace_period: 3s
env_file:
- .env
environment:
TZ: Asia/Jakarta
ports:
- "5001:5001"
- "5002:5002"
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
- ${MDLWR_BASE}/apps/static:/app/static
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
app:
condition: service_healthy
restart: always
nginx:
<<: *default-logging
container_name: nginx
image: nginx:alpine
command: ["nginx", "-g", "daemon off;", "-c", "/app/nginx/nginx.conf"]
stop_grace_period: 2s
ports:
- "80:80"
- "443:443"
volumes:
- ${MDLWR_BASE}/nginx/nginx.conf:/app/nginx/nginx.conf:ro
- /etc/letsencrypt/live:/etc/letsencrypt/live:ro
- /etc/letsencrypt/archive:/etc/letsencrypt/archive:ro
- ${MDLWR_BASE}/apps/static:/app/apps/static:ro
- ${MDLWR_BASE}/logs:/app/logs
depends_on:
app:
condition: service_healthy
restart: always
mem_limit: 100m
cpus: "0.2"
job:
<<: *default-logging
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: celery -A apps.workers.job_worker worker --loglevel=info -Q job_queue --concurrency=1
stop_grace_period: 3s
env_file:
- .env
environment:
TZ: Asia/Jakarta
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/1
C_FORCE_ROOT: "true"
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
restart: always
mem_limit: 300m
cpus: "0.5"
row:
<<: *default-logging
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: celery -A apps.workers.row_worker worker --loglevel=info -Q row_queue --concurrency=1
stop_grace_period: 3s
env_file:
- .env
environment:
TZ: Asia/Jakarta
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/1
C_FORCE_ROOT: "true"
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
- /var/run/docker.sock:/var/run/docker.sock
restart: always
mem_limit: 300m
cpus: "0.5"
email:
<<: *default-logging
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: celery -A apps.workers.email_worker worker -Q email_queue --concurrency=1
stop_grace_period: 3s
env_file:
- .env
environment:
TZ: Asia/Jakarta
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/1
C_FORCE_ROOT: "true"
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
restart: always
scheduler:
<<: *default-logging
container_name: scheduler
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: python -m apps.workers.run_scheduler
stop_grace_period: 2s
env_file:
- .env
environment:
TZ: Asia/Jakarta
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
restart: always
mem_limit: 150m
cpus: "0.3"
db:
<<: *default-logging
container_name: db
image: postgres:14
restart: always
stop_grace_period: 5s
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- ${MDLWR_BASE}/volumes/postgres:/var/lib/postgresql/data
command:
- postgres
- -c
- shared_buffers=64MB
- -c
- work_mem=4MB
- -c
- maintenance_work_mem=32MB
- -c
- max_connections=50
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 3s
timeout: 2s
retries: 5
start_period: 3s
mem_limit: 400m
cpus: "0.5"
redis:
<<: *default-logging
container_name: redis
image: redis:7-alpine
restart: always
stop_grace_period: 3s
volumes:
- ${MDLWR_BASE}/volumes/redis:/data
command:
- redis-server
- --bind
- 0.0.0.0
- --protected-mode
- "no"
- --save
- ""
- --appendonly
- "no"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 3s
timeout: 2s
retries: 5
start_period: 2s
mem_limit: 150m
cpus: "0.2"
stream:
<<: *default-logging
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: python -m apps.workers.run_stream_worker
stop_grace_period: 2s
env_file:
- .env
environment:
TZ: Asia/Jakarta
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
restart: always
mem_limit: 200m
cpus: "0.4"
# == ACTIVATE ONLY FOR SITE REMOTE MDLWR ==
sync:
<<: *default-logging
container_name: sync
image: ghcr.io/ikhsanrasyidi/mdlwr:latest
command: python -m apps.workers.run_sync_worker
stop_grace_period: 3s
env_file:
- .env
environment:
TZ: Asia/Jakarta
C_FORCE_ROOT: "true"
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ${MDLWR_BASE}/logs:/app/logs
- ${MDLWR_BASE}/tmp/incoming:/app/tmp/incoming
- ${MDLWR_BASE}/apps/static:/app/static
- ${MDLWR_BASE}/migrations:/app/migrations
restart: always
mem_limit: 400m
cpus: "0.5"