Minimal Docker image with Supervisor, cron, logrotate and tools to run commands in containers.
10K+
A lightweight, Alpine-based Docker image designed to run and manage long-running processes using Supervisor, with built-in support for:
docker exec wrappers (pexe & dexe) with TTY auto-detectionlogrotate (worker loop + persisted state)cronie (logs to Docker stdout)| Registry | Image Name |
|---|---|
| Docker Hub | docker.io/infocyph/runner |
| GitHub Container | ghcr.io/infocyph/runner |
The container starts Supervisor as PID 1 which manages:
crond (foreground, logs to Docker stdout)logrotate worker loop that runs periodicallyYou can mount additional Supervisor programs via /etc/supervisor/conf.d.
The image includes a healthcheck that verifies Supervisor is responsive:
If Supervisor becomes unresponsive, the container will be reported as unhealthy.
dexeRun any command inside a container (TTY auto-detected):
dexe <container_name> <command> [...args]
Example:
dexe my-app echo "Hello from inside"
pexeRun PHP inside a container (TTY auto-detected):
pexe <container_name> <php_args...>
Example:
pexe my-php-app artisan migrate
Note:
pexeis a thin wrapper overdocker exec ... php ...
LOGROTATE_INTERVAL (seconds) — default: 3600LOGROTATE_STATE_FILE — default: /var/lib/logrotate/statusTZ — optional timezone for consistent scheduling/log timestampsExample:
-e TZ=Asia/Dhaka \
-e LOGROTATE_INTERVAL=3600
/etc/logrotate.d//etc/logrotate.conf if present; otherwise it rotates each file in /etc/logrotate.d/*LOGROTATE_STATE_FILE to avoid repeated rotations across restarts/global/logMount any directory into /global/log/* and any *.log inside (including subdirs) will rotate daily.
-v $(pwd)/logs:/global/log/my-app
/global/movelog ➜ /global/oldlogsMount logs that you want rotated daily and moved into /global/oldlogs.
-v $(pwd)/movelogs:/global/movelog/my-app
-v $(pwd)/oldlogs:/global/oldlogs
/var/log/supervisorSupervisor’s own logs (and any program logs you write there) rotate daily.
-v $(pwd)/logs/runner:/var/log/supervisor
Your logrotate configs emit a post-rotate line into Docker logs (stdout of PID 1), e.g.:
[logrotate] rotated /global/log (daily) at ...[logrotate] rotated /global/movelog -> /global/oldlogs (daily) at ...[logrotate] rotated /var/log/supervisor (daily) at ...This is done by writing to /proc/1/fd/1 to reliably land in docker logs.
Cron runs in the foreground with logging enabled:
crond -f -l 2 -L /dev/stdoutTo add cron jobs, mount /etc/cron.d:
-v ./cron-jobs:/etc/cron.d:ro
Example file: my-cron:
* * * * * root echo "Cron ran at $(date)" >> /global/log/my-cron.log 2>&1
docker run -d \
--name runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ./supervisor:/etc/supervisor/conf.d:ro \
-v ./cron-jobs:/etc/cron.d:ro \
-v ./logs/runner:/var/log/supervisor \
-v $(pwd)/logs:/global/log \
-v $(pwd)/movelogs:/global/movelog \
-v $(pwd)/oldlogs:/global/oldlogs \
infocyph/runner
Mount /etc/supervisor/conf.d to add Supervisor programs.
Example: my-task.conf
[program:my-task]
command=/bin/sh -c 'echo Hello World && sleep 60'
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/my-task.err.log
stdout_logfile=/var/log/supervisor/my-task.out.log
[program:scheduler]
command=pexe MY_PHP_CONTAINER artisan schedule:run
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/scheduler.err.log
stdout_logfile=/var/log/supervisor/scheduler.out.log
Tip: If you want scheduler to run periodically, prefer cron calling
pexe ... schedule:run.
docker ps --format "table {{.Names}}\t{{.Status}}"
docker inspect --format '{{json .State.Health}}' runner | jq .
If the container is unhealthy, it means supervisorctl status failed (Supervisor not responding).
docker exec -it runner supervisorctl -c /etc/supervisor/supervisord.conf status
Docker logs (recommended):
docker logs -f runner
You should see cron logs and logrotate worker logs here.
Supervisor log file (if you mounted it):
/var/log/supervisor/supervisord.logCommon causes:
The file in /etc/cron.d has wrong permissions/format. Keep it simple:
root)Ensure your cron job writes somewhere writable (example uses /global/log/...).
Quick validation:
docker exec -it runner ls -la /etc/cron.d
docker logs -f runner | grep -i cron
Things to check:
Confirm the worker is running:
docker exec -it runner supervisorctl -c /etc/supervisor/supervisord.conf status
Confirm your logs match the patterns:
/global/log/**/*.log/global/movelog/**/*.log/var/log/supervisor/*.logConfirm state file exists (rotation is stateful):
docker exec -it runner ls -la /var/lib/logrotate/status
Force a single manual run (for testing only):
docker exec -it runner /usr/sbin/logrotate -v -s /var/lib/logrotate/status /etc/logrotate.d/daily
These two issues cause 80% of “cron/logrotate not working” reports:
Symptoms: “bad minute”, “^M”, jobs ignored, scripts not executed.
Fix on host:
sed -i 's/\r$//' ./cron-jobs/* ./supervisor/*.conf 2>/dev/null || true
/etc/cron.d/* should be readable by root.Quick checks:
docker exec -it runner sh -lc 'ls -la /etc/cron.d /etc/supervisor/conf.d /global/log /global/movelog /global/oldlogs /var/log/supervisor'
Most common mistake: mounting the wrong path.
Correct examples:
-v $(pwd)/logs:/global/log/my-app
-v $(pwd)/movelogs:/global/movelog/my-app
-v $(pwd)/oldlogs:/global/oldlogs
docker logsYour configs write to /proc/1/fd/1. If you run the container without Supervisor as PID 1 (custom entrypoint), this won’t work.
Make sure you’re using the default CMD:
supervisord -c /etc/supervisor/supervisord.conf
/var/run/docker.sock allows pexe and dexe to exec into other containers./etc/supervisor/conf.d and /etc/cron.d should typically be mounted read-only (:ro).MIT © infocyph
Content type
Image
Digest
sha256:a5e9e9670…
Size
37 MB
Last updated
about 21 hours ago
docker pull infocyph/runner