Lightweight docker process supervisor
As mentioned in a previous post, I used to use supervisord
service to run several daemon processes continuously.
did the job, but felt like overkill. It is written in Python and provides a lot of functionality I don't really need.
All I actually need are two things:
- Restart an application if it crashes or exits.
- Capture the application's output and send it to stdout.
It turns out this can easily be done with a simple Bash script:
#!/bin/bash
# ==========================
# Configuration: list of services
# Each entry: "name command"
# ==========================
services=(
"imapfilter-my-super-mailbox /usr/bin/imapfilter -c /path-to-config/_main.lua"
"http-api-server gunicorn -w 1 -k gthread --threads 1 --preload my-super-server:app --log-level error --bind 0.0.0.0:8888"
# "unstable-service sleep 10"
)
# Array to keep track of PIDs
pids=()
# Function to supervise a single service
run_service() {
local name="$1"
shift
local cmd="$@"
while true; do
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting $name..."
cd /jobs/bin
# Run command and wait for it
$cmd
exit_code=$?
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $name exited with code $exit_code. Restarting..."
sleep 3 # optional backoff
done
}
# Function to stop all background services gracefully
shutdown() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Caught termination signal. Stopping services..."
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
wait
echo "[$(date '+%Y-%m-%d %H:%M:%S')] All services stopped. Exiting."
exit 0
}
# Trap termination signals
trap shutdown SIGTERM SIGINT
# Start all services in background
for svc in "${services[@]}"; do
name=$(echo $svc | awk '{print $1}')
cmd=$(echo $svc | cut -d' ' -f2-)
run_service "$name" $cmd &
pids+=($!)
done
# Wait for all background services
wait
Run it with tini, and you have a container that can run your custom daemons:
FROM debian:trixie-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
tini ca-certificates curl bc \
python3 python3-requests \
imapfilter \
lego \
gunicorn python3-flask \
python3-feedparser python3-feedgen \
python3-websocket \
python3-redis \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/my/path/to/supervisor.sh"]
The only feature missing for now is log separation by process. The services I am running are stable enough that I haven't bothered implementing it.