The Problem Nobody Sees#
Two months ago I deployed a FastAPI API to production and the server was behaving strangely. CPU stable at 40-50% for no apparent reason. I thought it was a memory leak, that it was the logs, that it was the database. It was --reload.
It turns out that copy-pasting the development command directly into the Docker container is more common than it should be. And yes, uvicorn with --reload works. The server responds. Requests go fast. But there’s a cost you don’t see until you have 10K requests daily.
What exactly is the file-watcher#
The --reload flag in uvicorn starts an additional process that monitors all Python files in your project. Not just your code. All of them.
When you activate --reload, uvicorn:
- Starts a process manager (watchfiles by default)
- Every X seconds (default 0.4s) scans all .py files in the directory
- Calculates checksums or hashes of each file
- If it detects changes, restarts the complete worker
- Meanwhile, keeps scanning on every cycle, even without changes
This scanning isn’t free. In a medium-sized project with 200 Python files distributed across vendor, libraries, and your own modules, each watchfiles cycle touches disk and CPU.
How it consumes CPU on each request#
The criminal part is that the file-watcher doesn’t pause during requests. While your API is processing a request:
- The monitor keeps scanning files in the background
- Competes for disk I/O with your application
- In containers, if you don’t have resource limits, it can consume more CPU than the request logic itself
I measured this on my server. With a simple request to an endpoint that takes 50ms:
Without --reload:
CPU: 2-3% por request
I/O wait: <1%With --reload:
CPU: 8-12% por request
I/O wait: 3-5%It doesn’t look like much in an isolated request. But with 100 concurrent requests, that overhead multiplies.
How to detect it on your server#
1. Look at running processes#
ps aux | grep uvicornIf you see two uvicorn processes (or uvicorn + watchfiles), you have --reload active.
2. Check your startup command#
# MAL - esto es lo que probablemente tienes
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# BIEN - así debe estar en producción
uvicorn main:app --host 0.0.0.0 --port 80003. Monitor CPU during a load test#
# Terminal 1: corre tu servidor
docker run -it tu-contenedor
# Terminal 2: genera carga
ab -n 1000 -c 10 http://localhost:8000/endpointWatch top or docker stats. If you see unexplained spikes, suspect --reload.
4. Check uvicorn logs#
With --reload active, you’ll see messages like:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Will watch for changes in these directories: ...If you see “Will watch for changes”, you have a problem.
The solution (it’s obvious, but important)#
In Docker, make sure your Dockerfile does NOT use --reload:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Aquí no va --reload
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]For local development, use --reload without guilt:
uvicorn main:app --reloadWhat I learned#
The file-watcher in uvicorn is excellent for development. It’s transparent, works well, and speeds up your cycle. But in production it’s like leaving your computer continuously scanning with antivirus.
I reviewed my other containers after this. I found three more with --reload active. After removing it, CPU consumption dropped between 30-45%.
It’s one of those bugs that isn’t a bug. Your application works. Requests are processed. But your server is doing invisible work it doesn’t need to do.
Next time before deploying to production, grep for --reload. It will save you a troubleshooting session.
Recommended Equipment#
- Raspberry Pi 3 B+ — Lightweight low-power server to start your homelab
- Raspberry Pi 4 (4GB) — The perfect foundation for homelab, Docker and monitoring
Affiliate links. No extra cost for you.