Skip to content

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"