Exec
lastmod 2026-06-10created 2026-06-101 edits
exec is a shell built-in command that replaces the current shell process with a new command. When you run a command without exec, the shell spawns a new child process and stays alive itself, waiting for that child. With exec, the shell "transforms" into the new command and keeps the same PID.
Process tree difference
Without exec
With exec
Practical differences
This matters especially in deployment environments (Render, Docker, etc.).
1. Signal delivery (most important)
When Render or Docker stops a container, it sends SIGTERM to PID 1 (the startup process).
- Without exec: SIGTERM is delivered to
bash. By default bash does not forward this signal to the child uvicorn, so uvicorn gets no chance at a graceful shutdown and is forcibly killed with SIGKILL after a timeout. In-flight requests are cut off and connections are not cleaned up. - With exec: uvicorn is the process itself, so it receives SIGTERM directly and shuts down cleanly. It finishes in-flight requests before exiting.
2. Exit code
- Without exec: bash returns its own exit code after the child finishes. If bash logic sits in between, uvicorn's actual exit code can be masked.
- With exec: uvicorn's exit code is exposed as is, so Render's health/restart decisions become accurate.
3. Saves one process
No bash process is left behind that does nothing but wait. Minor, but clean.
When to use
| Situation | Use exec? |
| The shell launches a single server/command at the end and has nothing more to do | Use (recommended) |
| Cleanup after running, handling signals yourself with a trap handler, or managing multiple processes is needed | Don't use |
When the shell launches a single server at the end and has nothing more to do, exec is the standard approach. It is recommended especially in deployment environments because of graceful shutdown.
Conversely, if the shell still has to do something after running uvicorn (e.g., cleanup after exit, handling signals yourself with a trap handler, managing multiple processes), you should leave out exec.
Example: start-uvicorn
The script above has no cleanup or trap handler, so exec is appropriate.
Note: The earlier read ... < <(...) (host/port resolution) comes before exec, so it is not affected. That part runs first, and the shell is replaced only at the point where uvicorn finally runs.