When examining Supervisor configurations across production environments, you'll frequently encounter patterns like:
[program:my_service]
command=bash -c "/path/to/launch_script.sh"
This differs fundamentally from the simpler:
[program:my_service]
command=/path/to/launch_script.sh
The key distinction lies in execution context. When using bash -c
, your command:
- Gains full shell interpretation capabilities (variable expansion, globbing, pipes)
- Receives proper signal handling through the shell
- Maintains consistent environment variable inheritance
- Allows complex command chaining when needed
Consider this real-world scenario where direct execution fails:
[program:log_processor]
# This won't work as expected without shell interpretation
command=find /var/log -name "*.log" | xargs -n 1 process_log
Versus the functional version:
[program:log_processor]
command=bash -c 'find /var/log -name "*.log" | xargs -n 1 process_log'
Shell execution provides proper signal propagation. A script containing:
#!/bin/bash
trap "echo Cleaning up; exit" SIGTERM
# Main process here
Will behave differently when invoked via bash -c
versus direct execution under Supervisor's process management.
The shell context ensures consistent environment variable handling across different Linux distributions. Compare:
[program:app]
# May fail due to missing PATH components
command=/app/bin/startup
With the more robust:
[program:app]
# Uses shell's path resolution
command=bash -c "/app/bin/startup"
There are cases where direct execution is preferable:
- When running compiled binaries with strict signal requirements
- For minimal overhead process spawning
- When environment isolation is specifically desired
Example of appropriate direct execution:
[program:redis]
command=/usr/local/bin/redis-server
When examining Supervisor configurations, the bash -c
approach creates an explicit shell interpretation layer. Consider these differences in execution:
# Direct execution (shebang reliant)
command=/path/to/script.sh
# Explicit shell invocation
command=bash -c "/path/to/script.sh"
The latter guarantees script execution under a specific shell environment, which becomes crucial when:
- The script lacks proper shebang (#!/bin/bash)
- System default shell isn't Bash
- Environment variables need proper expansion
Supervisor launches processes in a minimally populated environment. Using bash -c
ensures consistent environment initialization:
[program:demo]
# Without bash -c may miss .bashrc initialization
command=bash -c "source ~/.bashrc && /app/startup.sh"
The -c
flag enables command combinations that would otherwise require separate shell scripts:
[program:deploy]
# Single-line command pipeline
command=bash -c "git pull && npm install && pm2 restart all"
Process trees behave differently when spawned directly versus through a shell:
[program:signal_test]
# Bash becomes the process group leader
command=bash -c "trap 'echo SIGTERM' TERM; while true; do sleep 1; done"
Common patterns in production environments:
[program:data_processor]
# Environment variable expansion
command=bash -c "export PATH=/custom/bin:$PATH && processor --config $CONFIG_FILE"
[program:multi_step]
# Conditional execution
command=bash -c "[ -f /tmp/lockfile ] || (./init_db && ./start_server)"
Simple cases where direct invocation works perfectly:
[program:static_binary]
# No shell features needed
command=/usr/local/bin/compiled_app
Key considerations for choosing the approach:
- Script complexity and shell features required
- Environment initialization needs
- Signal handling requirements
- Process tree structure preferences