Your home server was snappy last month and now a single web app crawls, while the rest of the box seems fine. That pattern, one slow service on an otherwise healthy machine, has a familiar cause. A self-hosted app running slow is usually one container quietly eating more CPU, RAM or disk than its share, and two commands will show you which in seconds.
Quick Answer
Run docker stats and htop. docker stats live-streams each container's CPU, memory and I/O so you can spot the one that is saturated, while htop shows the host-wide picture. The fix is whichever resource is maxed out: cap the greedy container with --cpus or --memory, add resources, or move a disk-heavy workload to faster storage like an SSD.
Find the starved resource first
Do not guess and do not start tweaking configs blindly. Performance problems come down to one of three resources being exhausted, CPU, memory or disk I/O, and your job is to identify which before you change anything. Guessing wastes an evening; measuring takes a minute.
Start with docker stats. It is the container-scoped equivalent of top or htop, live-streaming CPU usage, memory usage against each container's limit, and network and block I/O for every running container at once. Watch it for a moment under the slow conditions and the offender usually stands out. Pair it with htop on the host to see the whole machine, since sometimes the problem is total load rather than one container. A capable home-lab box from the mini PC range at Evetech handles several containers comfortably, but any machine has limits worth watching.
Reading the three signals
Each resource shows a distinct signature once you know what to look for.
CPU
In docker stats the CPU column is your first health check. Steady usage is fine; erratic spikes or a container pinned near 100 percent needs attention. When a container consistently sits at 80 to 90 percent CPU, it is either under-resourced or doing more work than the host can give it. Frequent CPU throttling is a clear red flag that the container wants more processing power than it is allowed.
Memory
The memory column shows two numbers separated by a slash: current usage and the maximum the container may use. If you never set a limit, that maximum defaults to the host's total RAM, which means one leaky container can starve everything else. A container steadily climbing toward its limit, or toward the host total, points to a memory problem or a leak.
Disk I/O
Disk is the sneaky one. A container can show modest CPU and memory while everything still feels slow because it is waiting on storage. High I/O wait means the container is spinning its wheels waiting for the disk. On the host, iostat tells the story: a device sitting above 80 percent utilisation, or high await times, is your bottleneck. This is common on older mechanical drives running database-style workloads.
Fixing what you found
Once you know the culprit, the fix follows directly. If one container hogs CPU, cap it with the --cpus flag so it cannot starve the others, or move it to a host with more cores if it genuinely needs them. If memory is the issue, set an explicit --memory limit so a leak cannot consume the whole machine, or add RAM. If disk I/O is the wall, the highest-impact fix is moving that workload to faster storage. Shifting a database or a busy app off a slow mechanical drive onto an SSD from the SSD best sellers often transforms a sluggish service into a responsive one.
Frequently Asked Questions
What is the fastest way to find a slow container?
Run docker stats. It live-streams CPU, memory and I/O for every running container at once, so the saturated one usually stands out immediately. Pair it with htop on the host for the whole-machine view.
Why does my app feel slow when CPU and RAM look fine?
It is probably disk I/O. A container can be waiting on slow storage while showing modest CPU and memory. Check I/O wait and use iostat; a drive above 80 percent utilisation or with high await times is the bottleneck.
How do I stop one container starving the others?
Set explicit limits. Use --cpus to cap processor use and --memory to cap RAM, so a single greedy or leaky container cannot consume the whole host. Without a memory limit, the default ceiling is the host's total RAM.
Will moving to an SSD actually help?
If disk I/O is your bottleneck, dramatically. Database-style and busy read-write workloads on slow mechanical drives are a classic cause of sluggishness, and shifting them to an SSD often turns a crawling service responsive.
What if the whole machine is overloaded, not one container?
htop on the host shows total load across all containers. If everything is competing for too few resources, you need to either reduce what is running or move to hardware with more cores, RAM or faster storage. Browsing the best-selling PCs gives a practical view of capable systems that handle multi-container home labs without resource contention.
Tired of a home server that drags? Diagnose the bottleneck with docker stats, then give the heavy workloads room to breathe with hardware from the mini PC range at Evetech built for a steady home lab.